From d10c32ec6715a0aea6d35b15a7726231b0cd8cf6 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 14 Jan 2022 22:02:58 +0300 Subject: [PATCH 001/204] Enable some settings --- src/Core/Settings.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index d55be808aa8..5d19de73e74 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -120,7 +120,7 @@ class IColumn; M(UInt64, group_by_two_level_threshold_bytes, 50000000, "From what size of the aggregation state in bytes, a two-level aggregation begins to be used. 0 - the threshold is not set. Two-level aggregation is used when at least one of the thresholds is triggered.", 0) \ M(Bool, distributed_aggregation_memory_efficient, true, "Is the memory-saving mode of distributed aggregation enabled.", 0) \ M(UInt64, aggregation_memory_efficient_merge_threads, 0, "Number of threads to use for merge intermediate aggregation results in memory efficient mode. When bigger, then more memory is consumed. 0 means - same as 'max_threads'.", 0) \ - M(Bool, enable_positional_arguments, false, "Enable positional arguments in ORDER BY, GROUP BY and LIMIT BY", 0) \ + M(Bool, enable_positional_arguments, true, "Enable positional arguments in ORDER BY, GROUP BY and LIMIT BY", 0) \ \ M(UInt64, max_parallel_replicas, 1, "The maximum number of replicas of each shard used when the query is executed. For consistency (to get different parts of the same partition), this option only works for the specified sampling key. The lag of the replicas is not controlled.", 0) \ M(UInt64, parallel_replicas_count, 0, "", 0) \ @@ -437,7 +437,7 @@ class IColumn; M(Bool, optimize_if_chain_to_multiif, false, "Replace if(cond1, then1, if(cond2, ...)) chains to multiIf. Currently it's not beneficial for numeric types.", 0) \ M(Bool, optimize_if_transform_strings_to_enum, false, "Replaces string-type arguments in If and Transform to enum. Disabled by default cause it could make inconsistent change in distributed query that would lead to its fail.", 0) \ M(Bool, optimize_monotonous_functions_in_order_by, true, "Replace monotonous function with its argument in ORDER BY", 0) \ - M(Bool, optimize_functions_to_subcolumns, false, "Transform functions to subcolumns, if possible, to reduce amount of read data. E.g. 'length(arr)' -> 'arr.size0', 'col IS NULL' -> 'col.null' ", 0) \ + M(Bool, optimize_functions_to_subcolumns, true, "Transform functions to subcolumns, if possible, to reduce amount of read data. E.g. 'length(arr)' -> 'arr.size0', 'col IS NULL' -> 'col.null' ", 0) \ M(Bool, optimize_using_constraints, false, "Use constraints for query optimization", 0) \ M(Bool, optimize_substitute_columns, false, "Use constraints for column substitution", 0) \ M(Bool, optimize_append_index, false, "Use constraints in order to append index condition (indexHint)", 0) \ @@ -455,7 +455,7 @@ class IColumn; M(Seconds, lock_acquire_timeout, DBMS_DEFAULT_LOCK_ACQUIRE_TIMEOUT_SEC, "How long locking request should wait before failing", 0) \ M(Bool, materialize_ttl_after_modify, true, "Apply TTL for old data, after ALTER MODIFY TTL query", 0) \ M(String, function_implementation, "", "Choose function implementation for specific target or variant (experimental). If empty enable all of them.", 0) \ - M(Bool, allow_experimental_geo_types, false, "Allow geo data types such as Point, Ring, Polygon, MultiPolygon", 0) \ + M(Bool, allow_experimental_geo_types, true, "Allow geo data types such as Point, Ring, Polygon, MultiPolygon", 0) \ M(Bool, data_type_default_nullable, false, "Data types without NULL or NOT NULL will make Nullable", 0) \ M(Bool, cast_keep_nullable, false, "CAST operator keep Nullable for result data type", 0) \ M(Bool, alter_partition_verbose_result, false, "Output information about affected parts. Currently works only for FREEZE and ATTACH commands.", 0) \ @@ -467,8 +467,8 @@ class IColumn; M(Bool, allow_non_metadata_alters, true, "Allow to execute alters which affects not only tables metadata, but also data on disk", 0) \ M(Bool, enable_global_with_statement, true, "Propagate WITH statements to UNION queries and all subqueries", 0) \ M(Bool, aggregate_functions_null_for_empty, false, "Rewrite all aggregate functions in a query, adding -OrNull suffix to them", 0) \ - M(Bool, optimize_syntax_fuse_functions, false, "Allow apply syntax optimisation: fuse aggregate functions", 0) \ - M(Bool, optimize_fuse_sum_count_avg, false, "Fuse functions `sum, avg, count` with identical arguments into one `sumCount` (`optimize_syntax_fuse_functions should be enabled)", 0) \ + M(Bool, optimize_syntax_fuse_functions, true, "Allow apply syntax optimisation: fuse aggregate functions", 0) \ + M(Bool, optimize_fuse_sum_count_avg, true, "Fuse functions `sum, avg, count` with identical arguments into one `sumCount` (`optimize_syntax_fuse_functions should be enabled)", 0) \ M(Bool, flatten_nested, true, "If true, columns of type Nested will be flatten to separate array columns instead of one array of tuples", 0) \ M(Bool, asterisk_include_materialized_columns, false, "Include MATERIALIZED columns for wildcard query", 0) \ M(Bool, asterisk_include_alias_columns, false, "Include ALIAS columns for wildcard query", 0) \ @@ -518,7 +518,7 @@ class IColumn; M(UInt64, function_range_max_elements_in_block, 500000000, "Maximum number of values generated by function 'range' per block of data (sum of array sizes for every row in a block, see also 'max_block_size' and 'min_insert_block_size_rows'). It is a safety threshold.", 0) \ M(ShortCircuitFunctionEvaluation, short_circuit_function_evaluation, ShortCircuitFunctionEvaluation::ENABLE, "Setting for short-circuit function evaluation configuration. Possible values: 'enable' - use short-circuit function evaluation for functions that are suitable for it, 'disable' - disable short-circuit function evaluation, 'force_enable' - use short-circuit function evaluation for all functions.", 0) \ \ - M(String, local_filesystem_read_method, "pread", "Method of reading data from local filesystem, one of: read, pread, mmap, pread_threadpool.", 0) \ + M(String, local_filesystem_read_method, "pread_threadpool", "Method of reading data from local filesystem, one of: read, pread, mmap, pread_threadpool.", 0) \ M(String, remote_filesystem_read_method, "threadpool", "Method of reading data from remote filesystem, one of: read, threadpool.", 0) \ M(Bool, local_filesystem_read_prefetch, false, "Should use prefetching when reading data from local filesystem.", 0) \ M(Bool, remote_filesystem_read_prefetch, true, "Should use prefetching when reading data from remote filesystem.", 0) \ From 7f2ffb277eb9ab2ed5a9b0d9d828951cefb0b725 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 24 Mar 2022 02:36:11 +0100 Subject: [PATCH 002/204] Fix test --- tests/queries/0_stateless/01710_projections.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/01710_projections.sql b/tests/queries/0_stateless/01710_projections.sql index 54581b5ae11..5df16a93980 100644 --- a/tests/queries/0_stateless/01710_projections.sql +++ b/tests/queries/0_stateless/01710_projections.sql @@ -40,6 +40,7 @@ select toStartOfMinute(datetime) dt_m, domain, sum(retry_count) / sum(duration), select toStartOfHour(toStartOfMinute(datetime)) dt_h, uniqHLL12(x_id), uniqHLL12(y_id) from projection_test group by dt_h order by dt_h; -- found by fuzzer +SET enable_positional_arguments = 0; SELECT 2, -1 FROM projection_test PREWHERE domain_alias = 1. WHERE domain = NULL GROUP BY -9223372036854775808 ORDER BY countIf(first_time = 0) / count(-2147483649) DESC NULLS LAST, 1048576 DESC NULLS LAST; drop table if exists projection_test; From 98012c32c88f69d1f6f9a8891d41fa651fe8063d Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 24 Mar 2022 02:39:28 +0100 Subject: [PATCH 003/204] Fix test --- tests/queries/0_stateless/00209_insert_select_extremes.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/00209_insert_select_extremes.sql b/tests/queries/0_stateless/00209_insert_select_extremes.sql index 8c6e547131c..98dfe8e2658 100644 --- a/tests/queries/0_stateless/00209_insert_select_extremes.sql +++ b/tests/queries/0_stateless/00209_insert_select_extremes.sql @@ -1,6 +1,8 @@ DROP TABLE IF EXISTS test_00209; CREATE TABLE test_00209 (x UInt8) ENGINE = Log; +SET enable_positional_arguments = 0; + INSERT INTO test_00209 SELECT 1 AS x; INSERT INTO test_00209 SELECT 1 AS x SETTINGS extremes = 1; INSERT INTO test_00209 SELECT 1 AS x GROUP BY 1 WITH TOTALS; From 15ca875ba79a69347a5fb69d08980287f4421b65 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 24 Mar 2022 02:43:51 +0100 Subject: [PATCH 004/204] Give up with one setting --- src/Core/Settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 9e6ab27f251..1d1a255054a 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -453,7 +453,7 @@ class IColumn; M(Bool, optimize_if_chain_to_multiif, false, "Replace if(cond1, then1, if(cond2, ...)) chains to multiIf. Currently it's not beneficial for numeric types.", 0) \ M(Bool, optimize_if_transform_strings_to_enum, false, "Replaces string-type arguments in If and Transform to enum. Disabled by default cause it could make inconsistent change in distributed query that would lead to its fail.", 0) \ M(Bool, optimize_monotonous_functions_in_order_by, true, "Replace monotonous function with its argument in ORDER BY", 0) \ - M(Bool, optimize_functions_to_subcolumns, true, "Transform functions to subcolumns, if possible, to reduce amount of read data. E.g. 'length(arr)' -> 'arr.size0', 'col IS NULL' -> 'col.null' ", 0) \ + M(Bool, optimize_functions_to_subcolumns, false, "Transform functions to subcolumns, if possible, to reduce amount of read data. E.g. 'length(arr)' -> 'arr.size0', 'col IS NULL' -> 'col.null' ", 0) \ M(Bool, optimize_using_constraints, false, "Use constraints for query optimization", 0) \ M(Bool, optimize_substitute_columns, false, "Use constraints for column substitution", 0) \ M(Bool, optimize_append_index, false, "Use constraints in order to append index condition (indexHint)", 0) \ From 28e5d8bef8b6bed858e4a77a8438b1e6fe54d958 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 24 Mar 2022 21:31:49 +0100 Subject: [PATCH 005/204] Disable trash optimizations --- src/Core/Settings.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 1d1a255054a..593bcab43d6 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -484,8 +484,8 @@ class IColumn; M(Bool, allow_non_metadata_alters, true, "Allow to execute alters which affects not only tables metadata, but also data on disk", 0) \ M(Bool, enable_global_with_statement, true, "Propagate WITH statements to UNION queries and all subqueries", 0) \ M(Bool, aggregate_functions_null_for_empty, false, "Rewrite all aggregate functions in a query, adding -OrNull suffix to them", 0) \ - M(Bool, optimize_syntax_fuse_functions, true, "Allow apply syntax optimisation: fuse aggregate functions", 0) \ - M(Bool, optimize_fuse_sum_count_avg, true, "Fuse functions `sum, avg, count` with identical arguments into one `sumCount` (`optimize_syntax_fuse_functions should be enabled)", 0) \ + M(Bool, optimize_syntax_fuse_functions, false, "Not ready for production, do not use. Allow apply syntax optimisation: fuse aggregate functions", 0) \ + M(Bool, optimize_fuse_sum_count_avg, false, "Not ready for production, do not use. Fuse functions `sum, avg, count` with identical arguments into one `sumCount` (`optimize_syntax_fuse_functions should be enabled)", 0) \ M(Bool, flatten_nested, true, "If true, columns of type Nested will be flatten to separate array columns instead of one array of tuples", 0) \ M(Bool, asterisk_include_materialized_columns, false, "Include MATERIALIZED columns for wildcard query", 0) \ M(Bool, asterisk_include_alias_columns, false, "Include ALIAS columns for wildcard query", 0) \ From 642082826d6d45ff3468d314b73a9462204ea3e2 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 24 Mar 2022 21:31:59 +0100 Subject: [PATCH 006/204] Update test --- .../00019_shard_quantiles_totals_distributed.sql | 3 ++- tests/queries/0_stateless/01162_strange_mutations.sh | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/queries/0_stateless/00019_shard_quantiles_totals_distributed.sql b/tests/queries/0_stateless/00019_shard_quantiles_totals_distributed.sql index 84956f0d97e..e712b028a82 100644 --- a/tests/queries/0_stateless/00019_shard_quantiles_totals_distributed.sql +++ b/tests/queries/0_stateless/00019_shard_quantiles_totals_distributed.sql @@ -1,3 +1,4 @@ -- Tags: distributed -SELECT quantilesTiming(0.1, 0.5, 0.9)(dummy) FROM remote('127.0.0.{2,3}', system, one) GROUP BY 1 WITH TOTALS +SET enable_positional_arguments = 0; +SELECT quantilesTiming(0.1, 0.5, 0.9)(dummy) FROM remote('127.0.0.{2,3}', system, one) GROUP BY 1 WITH TOTALS; diff --git a/tests/queries/0_stateless/01162_strange_mutations.sh b/tests/queries/0_stateless/01162_strange_mutations.sh index c759d113f84..ce75b584189 100755 --- a/tests/queries/0_stateless/01162_strange_mutations.sh +++ b/tests/queries/0_stateless/01162_strange_mutations.sh @@ -28,15 +28,15 @@ do $CLICKHOUSE_CLIENT -q "CREATE TABLE test ENGINE=$engine AS SELECT number + 100 AS n, 0 AS test FROM numbers(50)" 2>&1| grep -Ev "Removing leftovers from table|removed by another replica" $CLICKHOUSE_CLIENT -q "select count(), sum(n), sum(test) from test" if [[ $engine == *"ReplicatedMergeTree"* ]]; then - $CLICKHOUSE_CLIENT -q "ALTER TABLE test + $CLICKHOUSE_CLIENT --enable_positional_arguments 0 -q "ALTER TABLE test UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1" 2>&1| grep -Fa "DB::Exception: " | grep -Fv "statement with subquery may be nondeterministic" - $CLICKHOUSE_CLIENT --allow_nondeterministic_mutations=1 --mutations_sync=1 -q "ALTER TABLE test + $CLICKHOUSE_CLIENT --enable_positional_arguments 0 --allow_nondeterministic_mutations=1 --mutations_sync=1 -q "ALTER TABLE test UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1" elif [[ $engine == *"Join"* ]]; then - $CLICKHOUSE_CLIENT -q "ALTER TABLE test + $CLICKHOUSE_CLIENT --enable_positional_arguments 0 -q "ALTER TABLE test UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1" 2>&1| grep -Fa "DB::Exception: " | grep -Fv "Table engine Join supports only DELETE mutations" else - $CLICKHOUSE_CLIENT --mutations_sync=1 -q "ALTER TABLE test + $CLICKHOUSE_CLIENT --enable_positional_arguments 0 --mutations_sync=1 -q "ALTER TABLE test UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1" fi $CLICKHOUSE_CLIENT -q "select count(), sum(n), sum(test) from test" From 58c5dcfd125d6e758c70f298b69d2924e9429d2a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 24 Mar 2022 21:36:41 +0100 Subject: [PATCH 007/204] Disable one more trash setting --- src/Core/Settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 593bcab43d6..916f156c034 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -129,7 +129,7 @@ class IColumn; M(UInt64, group_by_two_level_threshold_bytes, 50000000, "From what size of the aggregation state in bytes, a two-level aggregation begins to be used. 0 - the threshold is not set. Two-level aggregation is used when at least one of the thresholds is triggered.", 0) \ M(Bool, distributed_aggregation_memory_efficient, true, "Is the memory-saving mode of distributed aggregation enabled.", 0) \ M(UInt64, aggregation_memory_efficient_merge_threads, 0, "Number of threads to use for merge intermediate aggregation results in memory efficient mode. When bigger, then more memory is consumed. 0 means - same as 'max_threads'.", 0) \ - M(Bool, enable_positional_arguments, true, "Enable positional arguments in ORDER BY, GROUP BY and LIMIT BY", 0) \ + M(Bool, enable_positional_arguments, false, "Not ready for production, do not use. Enable positional arguments in ORDER BY, GROUP BY and LIMIT BY", 0) \ \ M(UInt64, max_parallel_replicas, 1, "The maximum number of replicas of each shard used when the query is executed. For consistency (to get different parts of the same partition), this option only works for the specified sampling key. The lag of the replicas is not controlled.", 0) \ M(UInt64, parallel_replicas_count, 0, "", 0) \ From e8f2d33c881191bc441e998abb6df2e3138a035c Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 18 Apr 2022 02:30:45 +0200 Subject: [PATCH 008/204] Adapt test --- src/Core/Settings.h | 2 +- tests/queries/0_stateless/01323_too_many_threads_bug.sql | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index e6f11f4bf2d..b549c92ed4f 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -129,7 +129,7 @@ class IColumn; M(UInt64, group_by_two_level_threshold_bytes, 50000000, "From what size of the aggregation state in bytes, a two-level aggregation begins to be used. 0 - the threshold is not set. Two-level aggregation is used when at least one of the thresholds is triggered.", 0) \ M(Bool, distributed_aggregation_memory_efficient, true, "Is the memory-saving mode of distributed aggregation enabled.", 0) \ M(UInt64, aggregation_memory_efficient_merge_threads, 0, "Number of threads to use for merge intermediate aggregation results in memory efficient mode. When bigger, then more memory is consumed. 0 means - same as 'max_threads'.", 0) \ - M(Bool, enable_positional_arguments, false, "Not ready for production, do not use. Enable positional arguments in ORDER BY, GROUP BY and LIMIT BY", 0) \ + M(Bool, enable_positional_arguments, false, "Enable positional arguments in ORDER BY, GROUP BY and LIMIT BY", 0) \ \ M(UInt64, max_parallel_replicas, 1, "The maximum number of replicas of each shard used when the query is executed. For consistency (to get different parts of the same partition), this option only works for the specified sampling key. The lag of the replicas is not controlled.", 0) \ M(UInt64, parallel_replicas_count, 0, "", 0) \ diff --git a/tests/queries/0_stateless/01323_too_many_threads_bug.sql b/tests/queries/0_stateless/01323_too_many_threads_bug.sql index 5dbb5aca2ec..d3254d49728 100644 --- a/tests/queries/0_stateless/01323_too_many_threads_bug.sql +++ b/tests/queries/0_stateless/01323_too_many_threads_bug.sql @@ -1,6 +1,7 @@ drop table if exists table_01323_many_parts; -set remote_filesystem_read_method='read'; +set remote_filesystem_read_method = 'read'; +set local_filesystem_read_method = 'pread'; create table table_01323_many_parts (x UInt64) engine = MergeTree order by x partition by x % 100; set max_partitions_per_insert_block = 100; From a73d681854c9b6f32ce35f9a7bbd2df44ed7b9ca Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 18 Apr 2022 02:34:12 +0200 Subject: [PATCH 009/204] Update tests --- .../01283_max_threads_simple_query_optimization.sql | 3 ++- .../01524_do_not_merge_across_partitions_select_final.sql | 1 + .../0_stateless/01641_memory_tracking_insert_optimize.sql | 4 +++- tests/queries/0_stateless/02226_s3_with_cache.sql | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/01283_max_threads_simple_query_optimization.sql b/tests/queries/0_stateless/01283_max_threads_simple_query_optimization.sql index 59d8605ba1c..a75789ee726 100644 --- a/tests/queries/0_stateless/01283_max_threads_simple_query_optimization.sql +++ b/tests/queries/0_stateless/01283_max_threads_simple_query_optimization.sql @@ -1,6 +1,7 @@ DROP TABLE IF EXISTS data_01283; -set remote_filesystem_read_method='read'; +set remote_filesystem_read_method = 'read'; +set local_filesystem_read_method = 'pread'; CREATE TABLE data_01283 engine=MergeTree() ORDER BY key diff --git a/tests/queries/0_stateless/01524_do_not_merge_across_partitions_select_final.sql b/tests/queries/0_stateless/01524_do_not_merge_across_partitions_select_final.sql index 90975b0d9c4..f3bc026a415 100644 --- a/tests/queries/0_stateless/01524_do_not_merge_across_partitions_select_final.sql +++ b/tests/queries/0_stateless/01524_do_not_merge_across_partitions_select_final.sql @@ -33,6 +33,7 @@ INSERT INTO select_final SELECT toDate('2000-01-01'), number, '' FROM numbers(50 OPTIMIZE TABLE select_final FINAL; SET remote_filesystem_read_method = 'read'; +SET local_filesystem_read_method = 'pread'; SELECT max(x) FROM select_final FINAL; diff --git a/tests/queries/0_stateless/01641_memory_tracking_insert_optimize.sql b/tests/queries/0_stateless/01641_memory_tracking_insert_optimize.sql index 36b6c97460c..1c29ea83efc 100644 --- a/tests/queries/0_stateless/01641_memory_tracking_insert_optimize.sql +++ b/tests/queries/0_stateless/01641_memory_tracking_insert_optimize.sql @@ -4,7 +4,9 @@ drop table if exists data_01641; -- Disable cache for s3 storage tests because it increases memory usage. set enable_filesystem_cache=0; -set remote_filesystem_read_method='read'; + +set remote_filesystem_read_method = 'read'; +set local_filesystem_read_method = 'pread'; create table data_01641 (key Int, value String) engine=MergeTree order by (key, repeat(value, 40)) settings old_parts_lifetime=0, min_bytes_for_wide_part=0; diff --git a/tests/queries/0_stateless/02226_s3_with_cache.sql b/tests/queries/0_stateless/02226_s3_with_cache.sql index d470f2ef140..7237512e27f 100644 --- a/tests/queries/0_stateless/02226_s3_with_cache.sql +++ b/tests/queries/0_stateless/02226_s3_with_cache.sql @@ -23,7 +23,8 @@ AND current_database = currentDatabase() ORDER BY query_start_time DESC LIMIT 1; -SET remote_filesystem_read_method='read'; +set remote_filesystem_read_method = 'read'; +set local_filesystem_read_method = 'pread'; SELECT 2, * FROM test LIMIT 10 FORMAT Null; From 58755cb156fcade11af5151114220536dcf12615 Mon Sep 17 00:00:00 2001 From: wuxiaobai24 Date: Sun, 8 May 2022 14:02:40 +0800 Subject: [PATCH 010/204] add zstd_window_log_max setting --- src/Core/Settings.h | 2 ++ src/IO/CompressionMethod.cpp | 10 ++++++++++ src/IO/CompressionMethod.h | 10 +++++++++- src/IO/ZstdInflatingReadBuffer.cpp | 8 +++++++- src/IO/ZstdInflatingReadBuffer.h | 3 ++- src/Server/HTTPHandler.cpp | 2 +- src/Storages/HDFS/StorageHDFS.cpp | 6 ++++-- src/Storages/StorageFile.cpp | 4 ++-- src/Storages/StorageS3.cpp | 7 +++++-- src/Storages/StorageURL.cpp | 6 ++++-- 10 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index be73465eea0..895e10659e5 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -176,6 +176,8 @@ class IColumn; \ M(Int64, network_zstd_compression_level, 1, "Allows you to select the level of ZSTD compression.", 0) \ \ + M(Int64, zstd_window_log_max, 0, "Allows you to select the max window log of ZSTD", 0) \ + \ M(UInt64, priority, 0, "Priority of the query. 1 - the highest, higher value - lower priority; 0 - do not use priorities.", 0) \ M(Int64, os_thread_priority, 0, "If non zero - set corresponding 'nice' value for query processing threads. Can be used to adjust query priority for OS scheduler.", 0) \ \ diff --git a/src/IO/CompressionMethod.cpp b/src/IO/CompressionMethod.cpp index fe4772948ad..dc955322e16 100644 --- a/src/IO/CompressionMethod.cpp +++ b/src/IO/CompressionMethod.cpp @@ -125,6 +125,16 @@ static std::unique_ptr createCompressedWrapper( throw Exception("Unsupported compression method", ErrorCodes::NOT_IMPLEMENTED); } +std::unique_ptr wrapReadBufferWithCompressionMethod( + std::unique_ptr nested, CompressionMethod method, const Settings &settings, size_t buf_size, char * existing_memory, size_t alignment) +{ + if (method == CompressionMethod::None) + return nested; + else if (method == CompressionMethod::Zstd) + return std::make_unique(std::move(nested), buf_size, existing_memory, alignment, settings.zstd_window_log_max); + return createCompressedWrapper(std::move(nested), method, buf_size, existing_memory, alignment); +} + std::unique_ptr wrapReadBufferWithCompressionMethod( std::unique_ptr nested, CompressionMethod method, size_t buf_size, char * existing_memory, size_t alignment) { diff --git a/src/IO/CompressionMethod.h b/src/IO/CompressionMethod.h index 3953ba9d212..e87c7e5e7a1 100644 --- a/src/IO/CompressionMethod.h +++ b/src/IO/CompressionMethod.h @@ -4,7 +4,7 @@ #include #include - +#include namespace DB { @@ -47,6 +47,14 @@ std::string toContentEncodingName(CompressionMethod method); */ CompressionMethod chooseCompressionMethod(const std::string & path, const std::string & hint); +std::unique_ptr wrapReadBufferWithCompressionMethod( + std::unique_ptr nested, + CompressionMethod method, + const Settings &settings, + size_t buf_size = DBMS_DEFAULT_BUFFER_SIZE, + char * existing_memory = nullptr, + size_t alignment = 0); + std::unique_ptr wrapReadBufferWithCompressionMethod( std::unique_ptr nested, CompressionMethod method, diff --git a/src/IO/ZstdInflatingReadBuffer.cpp b/src/IO/ZstdInflatingReadBuffer.cpp index 712ea6960ef..0d026cdab9a 100644 --- a/src/IO/ZstdInflatingReadBuffer.cpp +++ b/src/IO/ZstdInflatingReadBuffer.cpp @@ -8,7 +8,7 @@ namespace ErrorCodes extern const int ZSTD_DECODER_FAILED; } -ZstdInflatingReadBuffer::ZstdInflatingReadBuffer(std::unique_ptr in_, size_t buf_size, char * existing_memory, size_t alignment) +ZstdInflatingReadBuffer::ZstdInflatingReadBuffer(std::unique_ptr in_, size_t buf_size, char * existing_memory, size_t alignment, int zstd_window_log_max) : CompressedReadBufferWrapper(std::move(in_), buf_size, existing_memory, alignment) { dctx = ZSTD_createDCtx(); @@ -19,6 +19,12 @@ ZstdInflatingReadBuffer::ZstdInflatingReadBuffer(std::unique_ptr in_ { throw Exception(ErrorCodes::ZSTD_DECODER_FAILED, "zstd_stream_decoder init failed: zstd version: {}", ZSTD_VERSION_STRING); } + + size_t ret = ZSTD_DCtx_setParameter(dctx, ZSTD_d_windowLogMax, zstd_window_log_max); + if (ZSTD_isError(ret)) + { + throw Exception(ErrorCodes::ZSTD_DECODER_FAILED, "zstd_stream_decoder init failed: {}", ZSTD_getErrorName(ret)); + } } ZstdInflatingReadBuffer::~ZstdInflatingReadBuffer() diff --git a/src/IO/ZstdInflatingReadBuffer.h b/src/IO/ZstdInflatingReadBuffer.h index a0c20b79d80..faa6231d4e2 100644 --- a/src/IO/ZstdInflatingReadBuffer.h +++ b/src/IO/ZstdInflatingReadBuffer.h @@ -20,7 +20,8 @@ public: std::unique_ptr in_, size_t buf_size = DBMS_DEFAULT_BUFFER_SIZE, char * existing_memory = nullptr, - size_t alignment = 0); + size_t alignment = 0, + int zstd_window_log_max = 0); ~ZstdInflatingReadBuffer() override; diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index 0ce81ec7be4..cac62a18ce4 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -642,7 +642,7 @@ void HTTPHandler::processQuery( /// Request body can be compressed using algorithm specified in the Content-Encoding header. String http_request_compression_method_str = request.get("Content-Encoding", ""); auto in_post = wrapReadBufferWithCompressionMethod( - wrapReadBufferReference(request.getStream()), chooseCompressionMethod({}, http_request_compression_method_str)); + wrapReadBufferReference(request.getStream()), chooseCompressionMethod({}, http_request_compression_method_str), context->getSettingsRef()); /// The data can also be compressed using incompatible internal algorithm. This is indicated by /// 'decompress' query parameter. diff --git a/src/Storages/HDFS/StorageHDFS.cpp b/src/Storages/HDFS/StorageHDFS.cpp index 441504a3e7e..762cb513c14 100644 --- a/src/Storages/HDFS/StorageHDFS.cpp +++ b/src/Storages/HDFS/StorageHDFS.cpp @@ -194,8 +194,9 @@ ColumnsDescription StorageHDFS::getTableStructureFromData( if (it == paths.end()) return nullptr; auto compression = chooseCompressionMethod(*it, compression_method); + const auto &settings = ctx->getSettingsRef(); return wrapReadBufferWithCompressionMethod( - std::make_unique(uri_without_path, *it++, ctx->getGlobalContext()->getConfigRef()), compression); + std::make_unique(uri_without_path, *it++, ctx->getGlobalContext()->getConfigRef()), compression, settings); }; return readSchemaFromFormat(format, std::nullopt, read_buffer_iterator, paths.size() > 1, ctx); } @@ -324,7 +325,8 @@ bool HDFSSource::initialize() const auto [path_from_uri, uri_without_path] = getPathFromUriAndUriWithoutPath(current_path); auto compression = chooseCompressionMethod(path_from_uri, storage->compression_method); - read_buf = wrapReadBufferWithCompressionMethod(std::make_unique(uri_without_path, path_from_uri, getContext()->getGlobalContext()->getConfigRef()), compression); + const auto &settings = getContext()->getSettingsRef(); + read_buf = wrapReadBufferWithCompressionMethod(std::make_unique(uri_without_path, path_from_uri, getContext()->getGlobalContext()->getConfigRef()), compression, settings); auto input_format = getContext()->getInputFormat(storage->format_name, *read_buf, block_for_format, max_block_size); diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index c460b8a4c67..52ec0e4e48e 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -206,8 +206,8 @@ std::unique_ptr createReadBuffer( auto & in = static_cast(*nested_buffer); in.setProgressCallback(context); } - - return wrapReadBufferWithCompressionMethod(std::move(nested_buffer), method); + const auto &settings = context->getSettingsRef(); + return wrapReadBufferWithCompressionMethod(std::move(nested_buffer), method, settings); } } diff --git a/src/Storages/StorageS3.cpp b/src/Storages/StorageS3.cpp index 301f33cb309..72ca9d6401f 100644 --- a/src/Storages/StorageS3.cpp +++ b/src/Storages/StorageS3.cpp @@ -302,7 +302,8 @@ bool StorageS3Source::initialize() file_path = fs::path(bucket) / current_key; - read_buf = wrapReadBufferWithCompressionMethod(createS3ReadBuffer(current_key), chooseCompressionMethod(current_key, compression_hint)); + const auto &settings = getContext()->getSettingsRef(); + read_buf = wrapReadBufferWithCompressionMethod(createS3ReadBuffer(current_key), chooseCompressionMethod(current_key, compression_hint), settings); auto input_format = getContext()->getInputFormat(format, *read_buf, sample_block, max_block_size, format_settings); QueryPipelineBuilder builder; @@ -1017,10 +1018,12 @@ ColumnsDescription StorageS3::getTableStructureFromDataImpl( read_keys_in_distributed_processing->push_back(key); first = false; + const auto &settings = ctx->getSettingsRef(); return wrapReadBufferWithCompressionMethod( std::make_unique( s3_configuration.client, s3_configuration.uri.bucket, key, s3_configuration.uri.version_id, s3_configuration.rw_settings.max_single_read_retries, ctx->getReadSettings()), - chooseCompressionMethod(key, compression_method)); + chooseCompressionMethod(key, compression_method), + settings); }; return readSchemaFromFormat(format, format_settings, read_buffer_iterator, is_key_with_globs, ctx); diff --git a/src/Storages/StorageURL.cpp b/src/Storages/StorageURL.cpp index 35752835581..a3cdb36a652 100644 --- a/src/Storages/StorageURL.cpp +++ b/src/Storages/StorageURL.cpp @@ -349,7 +349,8 @@ namespace std::move(read_buffer_factory), threadPoolCallbackRunner(IOThreadPool::get()), download_threads), - chooseCompressionMethod(request_uri.getPath(), compression_method)); + chooseCompressionMethod(request_uri.getPath(), compression_method), + settings); } } catch (const Poco::Exception & e) @@ -380,7 +381,8 @@ namespace delay_initialization, /* use_external_buffer */ false, /* skip_url_not_found_error */ skip_url_not_found_error), - chooseCompressionMethod(request_uri.getPath(), compression_method)); + chooseCompressionMethod(request_uri.getPath(), compression_method), + settings); } catch (...) { From bb2b2bfa474ce775b422613cb3141d74b06a07b1 Mon Sep 17 00:00:00 2001 From: wuxiaobai24 Date: Mon, 9 May 2022 13:57:27 +0800 Subject: [PATCH 011/204] add test --- .../02293_test_zstd_window_log_max.reference | 2 ++ .../0_stateless/02293_test_zstd_window_log_max.sh | 11 +++++++++++ 2 files changed, 13 insertions(+) create mode 100644 tests/queries/0_stateless/02293_test_zstd_window_log_max.reference create mode 100755 tests/queries/0_stateless/02293_test_zstd_window_log_max.sh diff --git a/tests/queries/0_stateless/02293_test_zstd_window_log_max.reference b/tests/queries/0_stateless/02293_test_zstd_window_log_max.reference new file mode 100644 index 00000000000..98ca7fb2d29 --- /dev/null +++ b/tests/queries/0_stateless/02293_test_zstd_window_log_max.reference @@ -0,0 +1,2 @@ +1 +40 diff --git a/tests/queries/0_stateless/02293_test_zstd_window_log_max.sh b/tests/queries/0_stateless/02293_test_zstd_window_log_max.sh new file mode 100755 index 00000000000..764b0e8b2d3 --- /dev/null +++ b/tests/queries/0_stateless/02293_test_zstd_window_log_max.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Tags: no-parallel + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# reuse the test data in 02293_test_zstd_window_log_max.sh +$CLICKHOUSE_LOCAL --query "SELECT count() FROM file('$CUR_DIR/data_zstd/test_01946.zstd', JSONEachRow, 'foo String') SETTINGS zstd_window_log_max=20" 2>&1 | grep -c \ + "Code: 561. DB::Exception: Zstd stream encoding failed: error 'Frame requires too much memory for decoding'; zstd version: 1.5.0: While executing File. (ZSTD_DECODER_FAILED)" +$CLICKHOUSE_LOCAL --query "SELECT count() FROM file('$CUR_DIR/data_zstd/test_01946.zstd', JSONEachRow, 'foo String') SETTINGS zstd_window_log_max=21" \ No newline at end of file From f5f60eb9f363df3382ef6d5fc260f0d9ee1c8131 Mon Sep 17 00:00:00 2001 From: wuxiaobai24 Date: Mon, 9 May 2022 14:01:23 +0800 Subject: [PATCH 012/204] fix typo --- tests/queries/0_stateless/02293_test_zstd_window_log_max.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02293_test_zstd_window_log_max.sh b/tests/queries/0_stateless/02293_test_zstd_window_log_max.sh index 764b0e8b2d3..39d1b443739 100755 --- a/tests/queries/0_stateless/02293_test_zstd_window_log_max.sh +++ b/tests/queries/0_stateless/02293_test_zstd_window_log_max.sh @@ -5,7 +5,7 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CUR_DIR"/../shell_config.sh -# reuse the test data in 02293_test_zstd_window_log_max.sh +# reuse the test data in 01946_test_zstd_decompression_with_escape_sequence_at_the_end_of_buffer.sh $CLICKHOUSE_LOCAL --query "SELECT count() FROM file('$CUR_DIR/data_zstd/test_01946.zstd', JSONEachRow, 'foo String') SETTINGS zstd_window_log_max=20" 2>&1 | grep -c \ "Code: 561. DB::Exception: Zstd stream encoding failed: error 'Frame requires too much memory for decoding'; zstd version: 1.5.0: While executing File. (ZSTD_DECODER_FAILED)" $CLICKHOUSE_LOCAL --query "SELECT count() FROM file('$CUR_DIR/data_zstd/test_01946.zstd', JSONEachRow, 'foo String') SETTINGS zstd_window_log_max=21" \ No newline at end of file From 85356bbf6468e7c22265c6505d1b85f3d67b1d18 Mon Sep 17 00:00:00 2001 From: wuxiaobai24 Date: Wed, 11 May 2022 00:53:09 +0800 Subject: [PATCH 013/204] fix --- src/Core/Settings.h | 2 +- src/IO/CompressionMethod.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 895e10659e5..27450e634e3 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -176,7 +176,7 @@ class IColumn; \ M(Int64, network_zstd_compression_level, 1, "Allows you to select the level of ZSTD compression.", 0) \ \ - M(Int64, zstd_window_log_max, 0, "Allows you to select the max window log of ZSTD", 0) \ + M(Int64, zstd_window_log_max, 0, "Allows you to select the max window log of ZSTD (it will not be used for MergeTree family)", 0) \ \ M(UInt64, priority, 0, "Priority of the query. 1 - the highest, higher value - lower priority; 0 - do not use priorities.", 0) \ M(Int64, os_thread_priority, 0, "If non zero - set corresponding 'nice' value for query processing threads. Can be used to adjust query priority for OS scheduler.", 0) \ diff --git a/src/IO/CompressionMethod.h b/src/IO/CompressionMethod.h index e87c7e5e7a1..e99e9695837 100644 --- a/src/IO/CompressionMethod.h +++ b/src/IO/CompressionMethod.h @@ -4,12 +4,12 @@ #include #include -#include namespace DB { class ReadBuffer; class WriteBuffer; +struct Settings; /** These are "generally recognizable" compression methods for data import/export. * Do not mess with more efficient compression methods used by ClickHouse internally From ab5636a46a3e40715ca52248565409f516bea44c Mon Sep 17 00:00:00 2001 From: wuxiaobai24 Date: Fri, 13 May 2022 17:52:39 +0800 Subject: [PATCH 014/204] fix --- src/IO/CompressionMethod.cpp | 19 ++++--------------- src/IO/CompressionMethod.h | 9 +-------- src/Server/HTTPHandler.cpp | 2 +- src/Storages/HDFS/StorageHDFS.cpp | 8 ++++---- src/Storages/StorageFile.cpp | 5 +++-- src/Storages/StorageS3.cpp | 8 ++++---- src/Storages/StorageURL.cpp | 4 ++-- 7 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/IO/CompressionMethod.cpp b/src/IO/CompressionMethod.cpp index dc955322e16..0da235c074c 100644 --- a/src/IO/CompressionMethod.cpp +++ b/src/IO/CompressionMethod.cpp @@ -99,7 +99,7 @@ CompressionMethod chooseCompressionMethod(const std::string & path, const std::s } static std::unique_ptr createCompressedWrapper( - std::unique_ptr nested, CompressionMethod method, size_t buf_size, char * existing_memory, size_t alignment) + std::unique_ptr nested, CompressionMethod method, size_t buf_size, char * existing_memory, size_t alignment, int zstd_window_log_max) { if (method == CompressionMethod::Gzip || method == CompressionMethod::Zlib) return std::make_unique(std::move(nested), method, buf_size, existing_memory, alignment); @@ -110,7 +110,7 @@ static std::unique_ptr createCompressedWrapper( if (method == CompressionMethod::Xz) return std::make_unique(std::move(nested), buf_size, existing_memory, alignment); if (method == CompressionMethod::Zstd) - return std::make_unique(std::move(nested), buf_size, existing_memory, alignment); + return std::make_unique(std::move(nested), buf_size, existing_memory, alignment, zstd_window_log_max); if (method == CompressionMethod::Lz4) return std::make_unique(std::move(nested), buf_size, existing_memory, alignment); #if USE_BZIP2 @@ -126,24 +126,13 @@ static std::unique_ptr createCompressedWrapper( } std::unique_ptr wrapReadBufferWithCompressionMethod( - std::unique_ptr nested, CompressionMethod method, const Settings &settings, size_t buf_size, char * existing_memory, size_t alignment) + std::unique_ptr nested, CompressionMethod method, int zstd_window_log_max, size_t buf_size, char * existing_memory, size_t alignment) { if (method == CompressionMethod::None) return nested; - else if (method == CompressionMethod::Zstd) - return std::make_unique(std::move(nested), buf_size, existing_memory, alignment, settings.zstd_window_log_max); - return createCompressedWrapper(std::move(nested), method, buf_size, existing_memory, alignment); + return createCompressedWrapper(std::move(nested), method, buf_size, existing_memory, alignment, zstd_window_log_max); } -std::unique_ptr wrapReadBufferWithCompressionMethod( - std::unique_ptr nested, CompressionMethod method, size_t buf_size, char * existing_memory, size_t alignment) -{ - if (method == CompressionMethod::None) - return nested; - return createCompressedWrapper(std::move(nested), method, buf_size, existing_memory, alignment); -} - - std::unique_ptr wrapWriteBufferWithCompressionMethod( std::unique_ptr nested, CompressionMethod method, int level, size_t buf_size, char * existing_memory, size_t alignment) { diff --git a/src/IO/CompressionMethod.h b/src/IO/CompressionMethod.h index e99e9695837..a399a756c13 100644 --- a/src/IO/CompressionMethod.h +++ b/src/IO/CompressionMethod.h @@ -9,7 +9,6 @@ namespace DB { class ReadBuffer; class WriteBuffer; -struct Settings; /** These are "generally recognizable" compression methods for data import/export. * Do not mess with more efficient compression methods used by ClickHouse internally @@ -50,17 +49,11 @@ CompressionMethod chooseCompressionMethod(const std::string & path, const std::s std::unique_ptr wrapReadBufferWithCompressionMethod( std::unique_ptr nested, CompressionMethod method, - const Settings &settings, + int zstd_window_log_max = 0, size_t buf_size = DBMS_DEFAULT_BUFFER_SIZE, char * existing_memory = nullptr, size_t alignment = 0); -std::unique_ptr wrapReadBufferWithCompressionMethod( - std::unique_ptr nested, - CompressionMethod method, - size_t buf_size = DBMS_DEFAULT_BUFFER_SIZE, - char * existing_memory = nullptr, - size_t alignment = 0); std::unique_ptr wrapWriteBufferWithCompressionMethod( std::unique_ptr nested, diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index cac62a18ce4..4193491ec9e 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -642,7 +642,7 @@ void HTTPHandler::processQuery( /// Request body can be compressed using algorithm specified in the Content-Encoding header. String http_request_compression_method_str = request.get("Content-Encoding", ""); auto in_post = wrapReadBufferWithCompressionMethod( - wrapReadBufferReference(request.getStream()), chooseCompressionMethod({}, http_request_compression_method_str), context->getSettingsRef()); + wrapReadBufferReference(request.getStream()), chooseCompressionMethod({}, http_request_compression_method_str), context->getSettingsRef().zstd_window_log_max); /// The data can also be compressed using incompatible internal algorithm. This is indicated by /// 'decompress' query parameter. diff --git a/src/Storages/HDFS/StorageHDFS.cpp b/src/Storages/HDFS/StorageHDFS.cpp index 762cb513c14..d9de785e60a 100644 --- a/src/Storages/HDFS/StorageHDFS.cpp +++ b/src/Storages/HDFS/StorageHDFS.cpp @@ -194,9 +194,9 @@ ColumnsDescription StorageHDFS::getTableStructureFromData( if (it == paths.end()) return nullptr; auto compression = chooseCompressionMethod(*it, compression_method); - const auto &settings = ctx->getSettingsRef(); + auto zstd_window_log_max = ctx->getSettingsRef().zstd_window_log_max; return wrapReadBufferWithCompressionMethod( - std::make_unique(uri_without_path, *it++, ctx->getGlobalContext()->getConfigRef()), compression, settings); + std::make_unique(uri_without_path, *it++, ctx->getGlobalContext()->getConfigRef()), compression, zstd_window_log_max); }; return readSchemaFromFormat(format, std::nullopt, read_buffer_iterator, paths.size() > 1, ctx); } @@ -325,8 +325,8 @@ bool HDFSSource::initialize() const auto [path_from_uri, uri_without_path] = getPathFromUriAndUriWithoutPath(current_path); auto compression = chooseCompressionMethod(path_from_uri, storage->compression_method); - const auto &settings = getContext()->getSettingsRef(); - read_buf = wrapReadBufferWithCompressionMethod(std::make_unique(uri_without_path, path_from_uri, getContext()->getGlobalContext()->getConfigRef()), compression, settings); + const auto zstd_window_log_max = getContext()->getSettingsRef().zstd_window_log_max; + read_buf = wrapReadBufferWithCompressionMethod(std::make_unique(uri_without_path, path_from_uri, getContext()->getGlobalContext()->getConfigRef()), compression, zstd_window_log_max); auto input_format = getContext()->getInputFormat(storage->format_name, *read_buf, block_for_format, max_block_size); diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index 52ec0e4e48e..9ba3d809d47 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -206,8 +206,9 @@ std::unique_ptr createReadBuffer( auto & in = static_cast(*nested_buffer); in.setProgressCallback(context); } - const auto &settings = context->getSettingsRef(); - return wrapReadBufferWithCompressionMethod(std::move(nested_buffer), method, settings); + + auto zstd_window_log_max = context->getSettingsRef().zstd_window_log_max; + return wrapReadBufferWithCompressionMethod(std::move(nested_buffer), method, zstd_window_log_max); } } diff --git a/src/Storages/StorageS3.cpp b/src/Storages/StorageS3.cpp index 72ca9d6401f..d307517afdf 100644 --- a/src/Storages/StorageS3.cpp +++ b/src/Storages/StorageS3.cpp @@ -302,8 +302,8 @@ bool StorageS3Source::initialize() file_path = fs::path(bucket) / current_key; - const auto &settings = getContext()->getSettingsRef(); - read_buf = wrapReadBufferWithCompressionMethod(createS3ReadBuffer(current_key), chooseCompressionMethod(current_key, compression_hint), settings); + auto zstd_window_log_max = getContext()->getSettingsRef().zstd_window_log_max; + read_buf = wrapReadBufferWithCompressionMethod(createS3ReadBuffer(current_key), chooseCompressionMethod(current_key, compression_hint), zstd_window_log_max); auto input_format = getContext()->getInputFormat(format, *read_buf, sample_block, max_block_size, format_settings); QueryPipelineBuilder builder; @@ -1018,12 +1018,12 @@ ColumnsDescription StorageS3::getTableStructureFromDataImpl( read_keys_in_distributed_processing->push_back(key); first = false; - const auto &settings = ctx->getSettingsRef(); + const auto zstd_window_log_max = ctx->getSettingsRef().zstd_window_log_max; return wrapReadBufferWithCompressionMethod( std::make_unique( s3_configuration.client, s3_configuration.uri.bucket, key, s3_configuration.uri.version_id, s3_configuration.rw_settings.max_single_read_retries, ctx->getReadSettings()), chooseCompressionMethod(key, compression_method), - settings); + zstd_window_log_max); }; return readSchemaFromFormat(format, format_settings, read_buffer_iterator, is_key_with_globs, ctx); diff --git a/src/Storages/StorageURL.cpp b/src/Storages/StorageURL.cpp index a3cdb36a652..74d44912251 100644 --- a/src/Storages/StorageURL.cpp +++ b/src/Storages/StorageURL.cpp @@ -350,7 +350,7 @@ namespace threadPoolCallbackRunner(IOThreadPool::get()), download_threads), chooseCompressionMethod(request_uri.getPath(), compression_method), - settings); + settings.zstd_window_log_max); } } catch (const Poco::Exception & e) @@ -382,7 +382,7 @@ namespace /* use_external_buffer */ false, /* skip_url_not_found_error */ skip_url_not_found_error), chooseCompressionMethod(request_uri.getPath(), compression_method), - settings); + settings.zstd_window_log_max); } catch (...) { From 4cd7e65d972319af3b3b0eeb5daf6fedb2254197 Mon Sep 17 00:00:00 2001 From: wuxiaobai24 Date: Fri, 13 May 2022 18:24:50 +0800 Subject: [PATCH 015/204] fix style check --- src/Storages/StorageFile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index 9ba3d809d47..504d71bb6c0 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -206,7 +206,7 @@ std::unique_ptr createReadBuffer( auto & in = static_cast(*nested_buffer); in.setProgressCallback(context); } - + auto zstd_window_log_max = context->getSettingsRef().zstd_window_log_max; return wrapReadBufferWithCompressionMethod(std::move(nested_buffer), method, zstd_window_log_max); } From 0dccafda2e114ffc175cc42f70b99fb309046508 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 12 May 2022 08:58:36 +0000 Subject: [PATCH 016/204] Fetch zxid independently of log idx --- src/Coordination/KeeperDispatcher.cpp | 4 +- src/Coordination/KeeperServer.cpp | 52 ++++++++++++---- src/Coordination/KeeperStateMachine.cpp | 80 +++++++++++++++---------- src/Coordination/KeeperStateMachine.h | 5 +- src/Coordination/KeeperStorage.cpp | 32 +++++++++- src/Coordination/KeeperStorage.h | 14 ++++- 6 files changed, 138 insertions(+), 49 deletions(-) diff --git a/src/Coordination/KeeperDispatcher.cpp b/src/Coordination/KeeperDispatcher.cpp index f97c65e7056..8fa6e4ec3f9 100644 --- a/src/Coordination/KeeperDispatcher.cpp +++ b/src/Coordination/KeeperDispatcher.cpp @@ -442,12 +442,12 @@ void KeeperDispatcher::finishSession(int64_t session_id) void KeeperDispatcher::addErrorResponses(const KeeperStorage::RequestsForSessions & requests_for_sessions, Coordination::Error error) { - for (const auto & [session_id, time, request] : requests_for_sessions) + for (const auto & [session_id, time, request, zxid] : requests_for_sessions) { KeeperStorage::ResponsesForSessions responses; auto response = request->makeResponse(); response->xid = request->xid; - response->zxid = 0; + response->zxid = zxid; response->error = error; if (!responses_queue.push(DB::KeeperStorage::ResponseForSession{session_id, response})) throw Exception(ErrorCodes::SYSTEM_ERROR, diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 1f089ba2cb7..3eb1e290590 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -1,9 +1,12 @@ #include #include +#include "Coordination/Changelog.h" +#include "IO/WriteBuffer.h" #include "config_core.h" #include +#include #include #include #include @@ -16,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -326,7 +330,7 @@ void KeeperServer::startup(const Poco::Util::AbstractConfiguration & config, boo for (const auto & entry : *log_entries) { if (entry && entry->get_val_type() == nuraft::log_val_type::app_log) - state_machine->preprocess(idx, entry->get_buf()); + state_machine->pre_commit(idx, entry->get_buf()); ++idx; } @@ -384,18 +388,18 @@ void KeeperServer::shutdown() namespace { -nuraft::ptr getZooKeeperLogEntry(int64_t session_id, int64_t time, const Coordination::ZooKeeperRequestPtr & request) +nuraft::ptr getZooKeeperLogEntry(const KeeperStorage::RequestForSession & request_for_session) { - DB::WriteBufferFromNuraftBuffer buf; - DB::writeIntBinary(session_id, buf); - request->write(buf); - DB::writeIntBinary(time, buf); - return buf.getBuffer(); + DB::WriteBufferFromNuraftBuffer write_buf; + DB::writeIntBinary(request_for_session.session_id, write_buf); + request_for_session.request->write(write_buf); + DB::writeIntBinary(request_for_session.time, write_buf); + DB::writeIntBinary(request_for_session.zxid, write_buf); + return write_buf.getBuffer(); } } - void KeeperServer::putLocalReadRequest(const KeeperStorage::RequestForSession & request_for_session) { if (!request_for_session.request->isReadRequest()) @@ -407,8 +411,10 @@ void KeeperServer::putLocalReadRequest(const KeeperStorage::RequestForSession & RaftAppendResult KeeperServer::putRequestBatch(const KeeperStorage::RequestsForSessions & requests_for_sessions) { std::vector> entries; - for (const auto & [session_id, time, request] : requests_for_sessions) - entries.push_back(getZooKeeperLogEntry(session_id, time, request)); + for (const auto & request_for_session : requests_for_sessions) + { + entries.push_back(getZooKeeperLogEntry(request_for_session)); + } std::lock_guard lock{server_write_mutex}; if (is_recovering) @@ -504,7 +510,33 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ } if (initialized_flag) + { + switch (type) + { + case nuraft::cb_func::PreAppendLogs: + { + nuraft::req_msg & req = *static_cast(param->ctx); + + for (auto & entry : req.log_entries()) + { + assert(entry->get_val_type() == nuraft::app_log); + auto next_zxid = state_machine->getNextZxid(); + + auto & entry_buf = entry->get_buf(); + auto request_for_session = state_machine->parseRequest(entry_buf); + request_for_session.zxid = next_zxid; + state_machine->preprocess(request_for_session); + + entry = nuraft::cs_new(entry->get_term(), getZooKeeperLogEntry(request_for_session), entry->get_val_type()); + } + break; + } + default: + break; + } + return nuraft::cb_func::ReturnCode::Ok; + } size_t last_commited = state_machine->last_commit_index(); size_t next_index = state_manager->getLogStore()->next_slot(); diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index fa3a5195226..8db7548ad74 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -19,33 +19,6 @@ namespace ErrorCodes namespace { - KeeperStorage::RequestForSession parseRequest(nuraft::buffer & data) - { - ReadBufferFromNuraftBuffer buffer(data); - KeeperStorage::RequestForSession request_for_session; - readIntBinary(request_for_session.session_id, buffer); - - int32_t length; - Coordination::read(length, buffer); - - int32_t xid; - Coordination::read(xid, buffer); - - Coordination::OpNum opnum; - - Coordination::read(opnum, buffer); - - request_for_session.request = Coordination::ZooKeeperRequestFactory::instance().get(opnum); - request_for_session.request->xid = xid; - request_for_session.request->readImpl(buffer); - - if (!buffer.eof()) - readIntBinary(request_for_session.time, buffer); - else /// backward compatibility - request_for_session.time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - - return request_for_session; - } } KeeperStateMachine::KeeperStateMachine( @@ -115,22 +88,59 @@ void KeeperStateMachine::init() nuraft::ptr KeeperStateMachine::pre_commit(uint64_t log_idx, nuraft::buffer & data) { - preprocess(log_idx, data); + auto request_for_session = parseRequest(data); + if (!request_for_session.zxid) + request_for_session.zxid = log_idx; + + preprocess(request_for_session); return nullptr; } -void KeeperStateMachine::preprocess(const uint64_t log_idx, nuraft::buffer & data) +KeeperStorage::RequestForSession KeeperStateMachine::parseRequest(nuraft::buffer & data) +{ + ReadBufferFromNuraftBuffer buffer(data); + KeeperStorage::RequestForSession request_for_session; + readIntBinary(request_for_session.session_id, buffer); + + int32_t length; + Coordination::read(length, buffer); + + int32_t xid; + Coordination::read(xid, buffer); + + Coordination::OpNum opnum; + + Coordination::read(opnum, buffer); + + request_for_session.request = Coordination::ZooKeeperRequestFactory::instance().get(opnum); + request_for_session.request->xid = xid; + request_for_session.request->readImpl(buffer); + + if (!buffer.eof()) + readIntBinary(request_for_session.time, buffer); + else /// backward compatibility + request_for_session.time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + if (!buffer.eof()) + readIntBinary(request_for_session.zxid, buffer); + + return request_for_session; +} + +void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & request_for_session) { - auto request_for_session = parseRequest(data); if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; std::lock_guard lock(storage_and_responses_lock); - storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, log_idx); + storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid); } nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data) { auto request_for_session = parseRequest(data); + if (!request_for_session.zxid) + request_for_session.zxid = log_idx; + /// Special processing of session_id request if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) { @@ -154,7 +164,7 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n else { std::lock_guard lock(storage_and_responses_lock); - KeeperStorage::ResponsesForSessions responses_for_sessions = storage->processRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, log_idx); + KeeperStorage::ResponsesForSessions responses_for_sessions = storage->processRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid); for (auto & response_for_session : responses_for_sessions) if (!responses_queue.push(response_for_session)) throw Exception(ErrorCodes::SYSTEM_ERROR, "Could not push response with session id {} into responses queue", response_for_session.session_id); @@ -381,6 +391,12 @@ std::vector KeeperStateMachine::getDeadSessions() return storage->getDeadSessions(); } +int64_t KeeperStateMachine::getNextZxid() const +{ + std::lock_guard lock(storage_and_responses_lock); + return storage->getNextZXID(); +} + uint64_t KeeperStateMachine::getLastProcessedZxid() const { std::lock_guard lock(storage_and_responses_lock); diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index aed96a59c13..18ad7ef70b2 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -27,7 +27,8 @@ public: /// Read state from the latest snapshot void init(); - void preprocess(uint64_t log_idx, nuraft::buffer & data); + KeeperStorage::RequestForSession parseRequest(nuraft::buffer & data); + void preprocess(const KeeperStorage::RequestForSession & request_for_session); nuraft::ptr pre_commit(uint64_t log_idx, nuraft::buffer & data) override; @@ -82,6 +83,8 @@ public: std::vector getDeadSessions(); + int64_t getNextZxid() const; + /// Introspection functions for 4lw commands uint64_t getLastProcessedZxid() const; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 1a7e3743948..400ce91c4fe 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -1549,6 +1549,21 @@ KeeperStorageRequestProcessorsFactory::KeeperStorageRequestProcessorsFactory() void KeeperStorage::preprocessRequest( const Coordination::ZooKeeperRequestPtr & zk_request, int64_t session_id, int64_t time, int64_t new_last_zxid, bool check_acl) { + int64_t last_zxid = uncommitted_zxids.empty() ? zxid : uncommitted_zxids.back(); + + if (new_last_zxid < last_zxid || (uncommitted_zxids.empty() && new_last_zxid == last_zxid)) + throw Exception( + ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, zxid); + + if (new_last_zxid == last_zxid) + // last uncommitted zxids is same as the current one so we are probably pre_committing on the leader + // but the leader already preprocessed the request while he appended the ZXID + // same ZXIDs in other cases should not happen but we can be more sure of that once we add the digest + // i.e. we will comapare both ZXID and the digest + return; + + uncommitted_zxids.push_back(new_last_zxid); + KeeperStorageRequestProcessorPtr request_processor = KeeperStorageRequestProcessorsFactory::instance().get(zk_request); if (zk_request->getOpNum() == Coordination::OpNum::Close) /// Close request is special @@ -1597,15 +1612,22 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( bool check_acl, bool is_local) { - KeeperStorage::ResponsesForSessions results; if (new_last_zxid) { - if (zxid >= *new_last_zxid) + if (uncommitted_zxids.empty()) throw Exception( - ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal than current {}. It's a bug", *new_last_zxid, zxid); + ErrorCodes::LOGICAL_ERROR, "Trying to commit a ZXID ({}) which was not preprocessed", *new_last_zxid); + + if (uncommitted_zxids.front() != *new_last_zxid) + throw Exception( + ErrorCodes::LOGICAL_ERROR, "Trying to commit a ZXID {} while the next ZXID to commit is {}", *new_last_zxid, uncommitted_zxids.front()); + zxid = *new_last_zxid; + uncommitted_zxids.pop_front(); } + KeeperStorage::ResponsesForSessions results; + /// ZooKeeper update sessions expirity for each request, not only for heartbeats session_expiry_queue.addNewSessionOrUpdate(session_id, session_and_timeout[session_id]); @@ -1711,6 +1733,10 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( void KeeperStorage::rollbackRequest(int64_t rollback_zxid) { + if (uncommitted_zxids.empty() || uncommitted_zxids.back() != rollback_zxid) + throw Exception( + ErrorCodes::LOGICAL_ERROR, "Trying to rollback invalid ZXID ({}). It should be the last preprocessed.", rollback_zxid); + // we can only rollback the last zxid (if there is any) // if there is a delta with a larger zxid, we have invalid state assert(uncommitted_state.deltas.empty() || uncommitted_state.deltas.back().zxid <= rollback_zxid); diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 09ca731f21e..f38efe36b7a 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -69,6 +69,7 @@ public: int64_t session_id; int64_t time; Coordination::ZooKeeperRequestPtr request; + int64_t zxid{0}; }; struct AuthID @@ -238,6 +239,8 @@ public: /// Global id of all requests applied to storage int64_t zxid{0}; + std::deque uncommitted_zxids; + int64_t uncommitted_zxid{0}; bool finalized{false}; /// Currently active watches (node_path -> subscribed sessions) @@ -246,9 +249,18 @@ public: void clearDeadWatches(int64_t session_id); - /// Get current zxid + /// Get current committed zxid int64_t getZXID() const { return zxid; } + int64_t getNextZXID() const + { + if (uncommitted_zxids.empty()) + return zxid + 1; + + return uncommitted_zxids.back() + 1; + } + + const String superdigest; KeeperStorage(int64_t tick_time_ms, const String & superdigest_); From 6bd0b0043d98b432a0ebfa30d64b73b45e1fd920 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 13 May 2022 08:24:44 +0000 Subject: [PATCH 017/204] Add zxid to snapshot --- src/Coordination/KeeperSnapshotManager.cpp | 12 +++++++++++- src/Coordination/KeeperSnapshotManager.h | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Coordination/KeeperSnapshotManager.cpp b/src/Coordination/KeeperSnapshotManager.cpp index 1481767add3..ddb44d2ca0b 100644 --- a/src/Coordination/KeeperSnapshotManager.cpp +++ b/src/Coordination/KeeperSnapshotManager.cpp @@ -147,6 +147,10 @@ void KeeperStorageSnapshot::serialize(const KeeperStorageSnapshot & snapshot, Wr { writeBinary(static_cast(snapshot.version), out); serializeSnapshotMetadata(snapshot.snapshot_meta, out); + + if (snapshot.version >= SnapshotVersion::V5) + writeBinary(snapshot.zxid, out); + writeBinary(snapshot.session_id, out); /// Better to sort before serialization, otherwise snapshots can be different on different replicas @@ -236,10 +240,14 @@ void KeeperStorageSnapshot::deserialize(SnapshotDeserializationResult & deserial deserialization_result.snapshot_meta = deserializeSnapshotMetadata(in); KeeperStorage & storage = *deserialization_result.storage; + if (version >= SnapshotVersion::V5) + readBinary(storage.zxid, in); + else + storage.zxid = deserialization_result.snapshot_meta->get_last_log_idx(); + int64_t session_id; readBinary(session_id, in); - storage.zxid = deserialization_result.snapshot_meta->get_last_log_idx(); storage.session_id_counter = session_id; /// Before V1 we serialized ACL without acl_map @@ -347,6 +355,7 @@ KeeperStorageSnapshot::KeeperStorageSnapshot(KeeperStorage * storage_, uint64_t , snapshot_meta(std::make_shared(up_to_log_idx_, 0, std::make_shared())) , session_id(storage->session_id_counter) , cluster_config(cluster_config_) + , zxid(storage->zxid) { auto [size, ver] = storage->container.snapshotSizeWithVersion(); snapshot_container_size = size; @@ -362,6 +371,7 @@ KeeperStorageSnapshot::KeeperStorageSnapshot(KeeperStorage * storage_, const Sna , snapshot_meta(snapshot_meta_) , session_id(storage->session_id_counter) , cluster_config(cluster_config_) + , zxid(storage->zxid) { auto [size, ver] = storage->container.snapshotSizeWithVersion(); snapshot_container_size = size; diff --git a/src/Coordination/KeeperSnapshotManager.h b/src/Coordination/KeeperSnapshotManager.h index 37341ef546a..c4ce9ee2937 100644 --- a/src/Coordination/KeeperSnapshotManager.h +++ b/src/Coordination/KeeperSnapshotManager.h @@ -21,6 +21,7 @@ enum SnapshotVersion : uint8_t V2 = 2, /// with 64 bit buffer header V3 = 3, /// compress snapshots with ZSTD codec V4 = 4, /// add Node size to snapshots + V5 = 5, /// add ZXID to snapshots }; static constexpr auto CURRENT_SNAPSHOT_VERSION = SnapshotVersion::V4; @@ -77,6 +78,8 @@ public: std::unordered_map acl_map; /// Cluster config from snapshot, can be empty ClusterConfigPtr cluster_config; + /// Last committed ZXID + int64_t zxid; }; using KeeperStorageSnapshotPtr = std::shared_ptr; From 2ec61a8d7fd26c2cb1cd051088a22380b651c594 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 13 May 2022 13:43:42 +0000 Subject: [PATCH 018/204] Initial implementation of digest --- src/Common/SipHash.h | 5 + src/Coordination/KeeperDispatcher.cpp | 2 +- src/Coordination/KeeperServer.cpp | 14 +- src/Coordination/KeeperStateMachine.cpp | 18 ++- src/Coordination/KeeperStateMachine.h | 2 + src/Coordination/KeeperStorage.cpp | 193 ++++++++++++++++++++---- src/Coordination/KeeperStorage.h | 46 ++++-- 7 files changed, 236 insertions(+), 44 deletions(-) diff --git a/src/Common/SipHash.h b/src/Common/SipHash.h index 108f7b31f43..38188a022f8 100644 --- a/src/Common/SipHash.h +++ b/src/Common/SipHash.h @@ -147,6 +147,11 @@ public: update(x.data(), x.length()); } + ALWAYS_INLINE void update(const std::string_view x) + { + update(x.data(), x.size()); + } + /// Get the result in some form. This can only be done once! void get128(char * out) diff --git a/src/Coordination/KeeperDispatcher.cpp b/src/Coordination/KeeperDispatcher.cpp index 8fa6e4ec3f9..c9ff30d7318 100644 --- a/src/Coordination/KeeperDispatcher.cpp +++ b/src/Coordination/KeeperDispatcher.cpp @@ -442,7 +442,7 @@ void KeeperDispatcher::finishSession(int64_t session_id) void KeeperDispatcher::addErrorResponses(const KeeperStorage::RequestsForSessions & requests_for_sessions, Coordination::Error error) { - for (const auto & [session_id, time, request, zxid] : requests_for_sessions) + for (const auto & [session_id, time, request, zxid, nodes_hash] : requests_for_sessions) { KeeperStorage::ResponsesForSessions responses; auto response = request->makeResponse(); diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 3eb1e290590..b99e31b0283 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -388,6 +388,15 @@ void KeeperServer::shutdown() namespace { +nuraft::ptr getZooKeeperRequestMessage(const KeeperStorage::RequestForSession & request_for_session) +{ + DB::WriteBufferFromNuraftBuffer write_buf; + DB::writeIntBinary(request_for_session.session_id, write_buf); + request_for_session.request->write(write_buf); + DB::writeIntBinary(request_for_session.time, write_buf); + return write_buf.getBuffer(); +} + nuraft::ptr getZooKeeperLogEntry(const KeeperStorage::RequestForSession & request_for_session) { DB::WriteBufferFromNuraftBuffer write_buf; @@ -395,6 +404,8 @@ nuraft::ptr getZooKeeperLogEntry(const KeeperStorage::RequestFor request_for_session.request->write(write_buf); DB::writeIntBinary(request_for_session.time, write_buf); DB::writeIntBinary(request_for_session.zxid, write_buf); + assert(request_for_session.nodes_hash); + DB::writeIntBinary(*request_for_session.nodes_hash, write_buf); return write_buf.getBuffer(); } @@ -413,7 +424,7 @@ RaftAppendResult KeeperServer::putRequestBatch(const KeeperStorage::RequestsForS std::vector> entries; for (const auto & request_for_session : requests_for_sessions) { - entries.push_back(getZooKeeperLogEntry(request_for_session)); + entries.push_back(getZooKeeperRequestMessage(request_for_session)); } std::lock_guard lock{server_write_mutex}; @@ -526,6 +537,7 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ auto request_for_session = state_machine->parseRequest(entry_buf); request_for_session.zxid = next_zxid; state_machine->preprocess(request_for_session); + request_for_session.nodes_hash = state_machine->getNodesHash(); entry = nuraft::cs_new(entry->get_term(), getZooKeeperLogEntry(request_for_session), entry->get_val_type()); } diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index 8db7548ad74..907ff255fe4 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -124,6 +124,13 @@ KeeperStorage::RequestForSession KeeperStateMachine::parseRequest(nuraft::buffer if (!buffer.eof()) readIntBinary(request_for_session.zxid, buffer); + if (!buffer.eof()) + { + UInt64 nodes_hash; + readIntBinary(nodes_hash, buffer); + request_for_session.nodes_hash.emplace(nodes_hash); + } + return request_for_session; } @@ -132,7 +139,7 @@ void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & req if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; std::lock_guard lock(storage_and_responses_lock); - storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid); + storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, request_for_session.nodes_hash); } nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data) @@ -170,6 +177,9 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n throw Exception(ErrorCodes::SYSTEM_ERROR, "Could not push response with session id {} into responses queue", response_for_session.session_id); } + if (request_for_session.nodes_hash) + assert(*request_for_session.nodes_hash == storage->getNodesHash(true)); + last_committed_idx = log_idx; return nullptr; } @@ -397,6 +407,12 @@ int64_t KeeperStateMachine::getNextZxid() const return storage->getNextZXID(); } +UInt64 KeeperStateMachine::getNodesHash() const +{ + std::lock_guard lock(storage_and_responses_lock); + return storage->getNodesHash(false); +} + uint64_t KeeperStateMachine::getLastProcessedZxid() const { std::lock_guard lock(storage_and_responses_lock); diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index 18ad7ef70b2..452e03cb8c3 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -85,6 +85,8 @@ public: int64_t getNextZxid() const; + UInt64 getNodesHash() const; + /// Introspection functions for 4lw commands uint64_t getLastProcessedZxid() const; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 400ce91c4fe..8b9ad837cc0 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -10,6 +10,7 @@ #include #include #include +#include "Common/SipHash.h" #include "Common/ZooKeeper/ZooKeeperConstants.h" #include #include @@ -155,6 +156,30 @@ KeeperStorage::ResponsesForSessions processWatchesImpl( return result; } +UInt64 calculateDigest(std::string_view path, std::string_view data, const Coordination::Stat & stat) +{ + SipHash hash; + + hash.update(path); + + hash.update(data); + + hash.update(stat.czxid); + hash.update(stat.czxid); + hash.update(stat.mzxid); + hash.update(stat.ctime); + hash.update(stat.mtime); + hash.update(stat.version); + hash.update(stat.cversion); + hash.update(stat.aversion); + hash.update(stat.ephemeralOwner); + hash.update(stat.dataLength); + hash.update(stat.numChildren); + hash.update(stat.pzxid); + + return hash.get64(); +} + } void KeeperStorage::Node::setData(String new_data) @@ -175,6 +200,24 @@ void KeeperStorage::Node::removeChild(StringRef child_path) children.erase(child_path); } +void KeeperStorage::Node::invalidateDigestCache() const +{ + cached_digest.reset(); +} + +UInt64 KeeperStorage::Node::getDigest(const std::string_view path) const +{ + if (!cached_digest) + cached_digest = calculateDigest(path, data, stat); + + return *cached_digest; +}; + +void KeeperStorage::Node::setDigest(UInt64 digest) +{ + cached_digest.emplace(digest); +} + KeeperStorage::KeeperStorage(int64_t tick_time_ms, const String & superdigest_) : session_expiry_queue(tick_time_ms), superdigest(superdigest_) { @@ -189,7 +232,7 @@ struct Overloaded : Ts... template Overloaded(Ts...) -> Overloaded; -std::shared_ptr KeeperStorage::UncommittedState::getNode(StringRef path) +std::shared_ptr KeeperStorage::UncommittedState::getNode(StringRef path, std::optional last_zxid) const { std::shared_ptr node{nullptr}; @@ -200,6 +243,7 @@ std::shared_ptr KeeperStorage::UncommittedState::getNode(St node->stat = committed_node.stat; node->seq_num = committed_node.seq_num; node->setData(committed_node.getData()); + node->setDigest(committed_node.getDigest(path.toView())); } applyDeltas( @@ -223,7 +267,8 @@ std::shared_ptr KeeperStorage::UncommittedState::getNode(St update_delta.update_fn(*node); }, [&](auto && /*delta*/) {}, - }); + }, + last_zxid); return node; } @@ -290,7 +335,9 @@ namespace [[noreturn]] void onStorageInconsistency() { - LOG_ERROR(&Poco::Logger::get("KeeperStorage"), "Inconsistency found between uncommitted and committed data. Keeper will terminate to avoid undefined behaviour."); + LOG_ERROR( + &Poco::Logger::get("KeeperStorage"), + "Inconsistency found between uncommitted and committed data. Keeper will terminate to avoid undefined behaviour."); std::terminate(); } @@ -330,7 +377,11 @@ Coordination::Error KeeperStorage::commit(int64_t commit_zxid, int64_t session_i if (operation.version != -1 && operation.version != node_it->value.stat.version) onStorageInconsistency(); - container.updateValue(path, operation.update_fn); + nodes_hash -= node_it->value.getDigest(path); + auto updated_node = container.updateValue(path, operation.update_fn); + node_it->value.invalidateDigestCache(); + nodes_hash += updated_node->value.getDigest(path); + return Coordination::Error::ZOK; } else if constexpr (std::same_as) @@ -426,6 +477,9 @@ bool KeeperStorage::createNode( if (is_ephemeral) ephemerals[session_id].emplace(path); + auto digest = map_key->getMapped()->value.getDigest(map_key->getKey().toView()); + nodes_hash += digest; + return true; }; @@ -457,6 +511,8 @@ bool KeeperStorage::removeNode(const std::string & path, int32_t version) [child_basename = getBaseName(node_it->key)](KeeperStorage::Node & parent) { parent.removeChild(child_basename); }); container.erase(path); + + nodes_hash -= prev_node.getDigest(path); return true; } @@ -926,17 +982,9 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce }, request.version}); - new_deltas.emplace_back( - parentPath(request.path).toString(), - zxid, - KeeperStorage::UpdateNodeDelta - { - [](KeeperStorage::Node & parent) - { - parent.stat.cversion++; - } - } - ); + new_deltas.emplace_back(parentPath(request.path).toString(), zxid, KeeperStorage::UpdateNodeDelta{[](KeeperStorage::Node & parent) { + parent.stat.cversion++; + }}); return new_deltas; } @@ -1546,23 +1594,95 @@ KeeperStorageRequestProcessorsFactory::KeeperStorageRequestProcessorsFactory() } -void KeeperStorage::preprocessRequest( - const Coordination::ZooKeeperRequestPtr & zk_request, int64_t session_id, int64_t time, int64_t new_last_zxid, bool check_acl) +UInt64 KeeperStorage::calculateNodesHash(UInt64 current_hash, int64_t current_zxid) const { - int64_t last_zxid = uncommitted_zxids.empty() ? zxid : uncommitted_zxids.back(); + std::unordered_map> updated_nodes; - if (new_last_zxid < last_zxid || (uncommitted_zxids.empty() && new_last_zxid == last_zxid)) + for (const auto & delta : uncommitted_state.deltas) + { + if (delta.zxid != current_zxid) + continue; + + std::visit( + Overloaded{ + [&](const CreateNodeDelta & create_delta) + { + auto node = std::make_shared(); + node->stat = create_delta.stat; + node->setData(create_delta.data); + updated_nodes.emplace(delta.path, node); + }, + [&](const RemoveNodeDelta & /* remove_delta */) + { + if (!updated_nodes.contains(delta.path)) + { + auto old_digest = uncommitted_state.getNode(delta.path, current_zxid)->getDigest(delta.path); + current_hash -= old_digest; + } + + updated_nodes.insert_or_assign(delta.path, nullptr); + }, + [&](const UpdateNodeDelta & update_delta) + { + std::shared_ptr node{nullptr}; + + auto updated_node_it = updated_nodes.find(delta.path); + if (updated_node_it == updated_nodes.end()) + { + node = uncommitted_state.getNode(delta.path, current_zxid); + current_hash -= node->getDigest(delta.path); + updated_nodes.emplace(delta.path, node); + } + else + node = updated_node_it->second; + + update_delta.update_fn(*node); + }, + [](auto && /* delta */) {}}, + delta.operation); + } + + for (const auto & [path, updated_node] : updated_nodes) + { + if (updated_node) + { + updated_node->invalidateDigestCache(); + current_hash += updated_node->getDigest(path); + } + } + + return current_hash; +} + +void KeeperStorage::preprocessRequest( + const Coordination::ZooKeeperRequestPtr & zk_request, + int64_t session_id, + int64_t time, + int64_t new_last_zxid, + std::optional expected_hash, + bool check_acl) +{ + int64_t last_zxid = getNextZXID() - 1; + + if (new_last_zxid < last_zxid || (uncommitted_transactions.empty() && new_last_zxid == last_zxid)) throw Exception( ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, zxid); - if (new_last_zxid == last_zxid) + if (new_last_zxid == last_zxid && expected_hash && *expected_hash == uncommitted_transactions.back().nodes_hash) + { + if (expected_hash && *expected_hash != uncommitted_transactions.back().nodes_hash) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} equal to current ZXID ({}). It's a bug", new_last_zxid, zxid); + // last uncommitted zxids is same as the current one so we are probably pre_committing on the leader // but the leader already preprocessed the request while he appended the ZXID - // same ZXIDs in other cases should not happen but we can be more sure of that once we add the digest - // i.e. we will comapare both ZXID and the digest return; + } - uncommitted_zxids.push_back(new_last_zxid); + TransactionInfo transaction{.zxid = new_last_zxid}; + SCOPE_EXIT({ + transaction.nodes_hash = expected_hash.value_or(calculateNodesHash(getNodesHash(false), transaction.zxid)); + uncommitted_transactions.emplace_back(transaction); + }); KeeperStorageRequestProcessorPtr request_processor = KeeperStorageRequestProcessorsFactory::instance().get(zk_request); @@ -1599,7 +1719,7 @@ void KeeperStorage::preprocessRequest( return; } - auto new_deltas = request_processor->preprocess(*this, new_last_zxid, session_id, time); + auto new_deltas = request_processor->preprocess(*this, transaction.zxid, session_id, time); uncommitted_state.deltas.insert( uncommitted_state.deltas.end(), std::make_move_iterator(new_deltas.begin()), std::make_move_iterator(new_deltas.end())); } @@ -1614,16 +1734,18 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( { if (new_last_zxid) { - if (uncommitted_zxids.empty()) - throw Exception( - ErrorCodes::LOGICAL_ERROR, "Trying to commit a ZXID ({}) which was not preprocessed", *new_last_zxid); + if (uncommitted_transactions.empty()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Trying to commit a ZXID ({}) which was not preprocessed", *new_last_zxid); - if (uncommitted_zxids.front() != *new_last_zxid) + if (uncommitted_transactions.front().zxid != *new_last_zxid) throw Exception( - ErrorCodes::LOGICAL_ERROR, "Trying to commit a ZXID {} while the next ZXID to commit is {}", *new_last_zxid, uncommitted_zxids.front()); + ErrorCodes::LOGICAL_ERROR, + "Trying to commit a ZXID {} while the next ZXID to commit is {}", + *new_last_zxid, + uncommitted_transactions.front().zxid); zxid = *new_last_zxid; - uncommitted_zxids.pop_front(); + uncommitted_transactions.pop_front(); } KeeperStorage::ResponsesForSessions results; @@ -1733,7 +1855,7 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( void KeeperStorage::rollbackRequest(int64_t rollback_zxid) { - if (uncommitted_zxids.empty() || uncommitted_zxids.back() != rollback_zxid) + if (uncommitted_transactions.empty() || uncommitted_transactions.back().zxid != rollback_zxid) throw Exception( ErrorCodes::LOGICAL_ERROR, "Trying to rollback invalid ZXID ({}). It should be the last preprocessed.", rollback_zxid); @@ -1741,6 +1863,15 @@ void KeeperStorage::rollbackRequest(int64_t rollback_zxid) // if there is a delta with a larger zxid, we have invalid state assert(uncommitted_state.deltas.empty() || uncommitted_state.deltas.back().zxid <= rollback_zxid); std::erase_if(uncommitted_state.deltas, [rollback_zxid](const auto & delta) { return delta.zxid == rollback_zxid; }); + uncommitted_transactions.pop_back(); +} + +uint64_t KeeperStorage::getNodesHash(bool committed) const +{ + if (committed || uncommitted_transactions.empty()) + return nodes_hash; + + return uncommitted_transactions.back().nodes_hash; } void KeeperStorage::clearDeadWatches(int64_t session_id) diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index f38efe36b7a..c62d0718134 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -52,9 +52,15 @@ public: const auto & getChildren() const noexcept { return children; } + void invalidateDigestCache() const; + UInt64 getDigest(std::string_view path) const; + void setDigest(UInt64 digest); + private: String data; ChildrenSet children{}; + + mutable std::optional cached_digest; }; struct ResponseForSession @@ -70,6 +76,7 @@ public: int64_t time; Coordination::ZooKeeperRequestPtr request; int64_t zxid{0}; + std::optional nodes_hash = std::nullopt; }; struct AuthID @@ -148,8 +155,8 @@ public: AuthID auth_id; }; - using Operation - = std::variant; + using Operation = std:: + variant; struct Delta { @@ -169,10 +176,13 @@ public: explicit UncommittedState(KeeperStorage & storage_) : storage(storage_) { } template - void applyDeltas(StringRef path, const Visitor & visitor) const + void applyDeltas(StringRef path, const Visitor & visitor, std::optional last_zxid = std::nullopt) const { for (const auto & delta : deltas) { + if (last_zxid && delta.zxid >= last_zxid) + break; + if (path.empty() || delta.path == path) std::visit(visitor, delta.operation); } @@ -201,7 +211,7 @@ public: return false; } - std::shared_ptr getNode(StringRef path); + std::shared_ptr getNode(StringRef path, std::optional last_zxid = std::nullopt) const; bool hasNode(StringRef path) const; Coordination::ACLs getACLs(StringRef path) const; @@ -239,8 +249,17 @@ public: /// Global id of all requests applied to storage int64_t zxid{0}; - std::deque uncommitted_zxids; - int64_t uncommitted_zxid{0}; + + struct TransactionInfo + { + int64_t zxid; + uint64_t nodes_hash; + }; + + std::deque uncommitted_transactions; + + uint64_t nodes_hash{0}; + bool finalized{false}; /// Currently active watches (node_path -> subscribed sessions) @@ -254,12 +273,13 @@ public: int64_t getNextZXID() const { - if (uncommitted_zxids.empty()) + if (uncommitted_transactions.empty()) return zxid + 1; - return uncommitted_zxids.back() + 1; + return uncommitted_transactions.back().zxid + 1; } + uint64_t getNodesHash(bool committed) const; const String superdigest; @@ -281,6 +301,8 @@ public: session_expiry_queue.addNewSessionOrUpdate(session_id, session_timeout_ms); } + UInt64 calculateNodesHash(UInt64 current_hash, int64_t current_zxid) const; + /// Process user request and return response. /// check_acl = false only when converting data from ZooKeeper. ResponsesForSessions processRequest( @@ -291,7 +313,12 @@ public: bool check_acl = true, bool is_local = false); void preprocessRequest( - const Coordination::ZooKeeperRequestPtr & request, int64_t session_id, int64_t time, int64_t new_last_zxid, bool check_acl = true); + const Coordination::ZooKeeperRequestPtr & request, + int64_t session_id, + int64_t time, + int64_t new_last_zxid, + std::optional expected_hash = std::nullopt, + bool check_acl = true); void rollbackRequest(int64_t rollback_zxid); void finalize(); @@ -322,7 +349,6 @@ public: uint64_t getArenaDataSize() const { return container.keyArenaSize(); } - uint64_t getTotalWatchesCount() const; uint64_t getWatchedPathsCount() const { return watches.size() + list_watches.size(); } From 99b7e238129f9855221d2242fb392e173ebbd8c0 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Mon, 16 May 2022 12:12:29 +0000 Subject: [PATCH 019/204] Add version for digest --- src/Coordination/KeeperServer.cpp | 8 ++- src/Coordination/KeeperStateMachine.cpp | 20 +++--- src/Coordination/KeeperStateMachine.h | 2 +- src/Coordination/KeeperStorage.cpp | 79 ++++++++++++++++-------- src/Coordination/KeeperStorage.h | 37 +++++++++-- src/Coordination/ZooKeeperDataReader.cpp | 2 +- 6 files changed, 103 insertions(+), 45 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index b99e31b0283..ee5ed56e4cf 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -404,8 +404,10 @@ nuraft::ptr getZooKeeperLogEntry(const KeeperStorage::RequestFor request_for_session.request->write(write_buf); DB::writeIntBinary(request_for_session.time, write_buf); DB::writeIntBinary(request_for_session.zxid, write_buf); - assert(request_for_session.nodes_hash); - DB::writeIntBinary(*request_for_session.nodes_hash, write_buf); + DB::writeIntBinary(request_for_session.digest.version, write_buf); + if (request_for_session.digest.version != KeeperStorage::DigestVersion::NO_DIGEST) + DB::writeIntBinary(request_for_session.digest.value, write_buf); + return write_buf.getBuffer(); } @@ -537,7 +539,7 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ auto request_for_session = state_machine->parseRequest(entry_buf); request_for_session.zxid = next_zxid; state_machine->preprocess(request_for_session); - request_for_session.nodes_hash = state_machine->getNodesHash(); + request_for_session.digest = state_machine->getNodesDigest(); entry = nuraft::cs_new(entry->get_term(), getZooKeeperLogEntry(request_for_session), entry->get_val_type()); } diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index 907ff255fe4..d2852549398 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -126,9 +126,9 @@ KeeperStorage::RequestForSession KeeperStateMachine::parseRequest(nuraft::buffer if (!buffer.eof()) { - UInt64 nodes_hash; - readIntBinary(nodes_hash, buffer); - request_for_session.nodes_hash.emplace(nodes_hash); + readIntBinary(request_for_session.digest.version, buffer); + if (request_for_session.digest.version != KeeperStorage::DigestVersion::NO_DIGEST) + readIntBinary(request_for_session.digest.value, buffer); } return request_for_session; @@ -139,7 +139,7 @@ void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & req if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; std::lock_guard lock(storage_and_responses_lock); - storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, request_for_session.nodes_hash); + storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, request_for_session.digest); } nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data) @@ -177,8 +177,12 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n throw Exception(ErrorCodes::SYSTEM_ERROR, "Could not push response with session id {} into responses queue", response_for_session.session_id); } - if (request_for_session.nodes_hash) - assert(*request_for_session.nodes_hash == storage->getNodesHash(true)); + + if (!KeeperStorage::checkDigest(request_for_session.digest, storage->getNodesDigest(true))) + { + LOG_ERROR(log, "Digest for nodes is not matching after applying request of type {}", request_for_session.request->getOpNum()); + std::terminate(); + } last_committed_idx = log_idx; return nullptr; @@ -407,10 +411,10 @@ int64_t KeeperStateMachine::getNextZxid() const return storage->getNextZXID(); } -UInt64 KeeperStateMachine::getNodesHash() const +KeeperStorage::Digest KeeperStateMachine::getNodesDigest() const { std::lock_guard lock(storage_and_responses_lock); - return storage->getNodesHash(false); + return storage->getNodesDigest(false); } uint64_t KeeperStateMachine::getLastProcessedZxid() const diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index 452e03cb8c3..d9f5c947fd6 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -85,7 +85,7 @@ public: int64_t getNextZxid() const; - UInt64 getNodesHash() const; + KeeperStorage::Digest getNodesDigest() const; /// Introspection functions for 4lw commands uint64_t getLastProcessedZxid() const; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 8b9ad837cc0..c3d73e0c7ed 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -156,6 +156,7 @@ KeeperStorage::ResponsesForSessions processWatchesImpl( return result; } +// When this function is updated, update CURRENT_DIGEST_VERSION!! UInt64 calculateDigest(std::string_view path, std::string_view data, const Coordination::Stat & stat) { SipHash hash; @@ -264,6 +265,7 @@ std::shared_ptr KeeperStorage::UncommittedState::getNode(St [&](const UpdateNodeDelta & update_delta) { assert(node); + node->invalidateDigestCache(); update_delta.update_fn(*node); }, [&](auto && /*delta*/) {}, @@ -377,10 +379,10 @@ Coordination::Error KeeperStorage::commit(int64_t commit_zxid, int64_t session_i if (operation.version != -1 && operation.version != node_it->value.stat.version) onStorageInconsistency(); - nodes_hash -= node_it->value.getDigest(path); + nodes_digest -= node_it->value.getDigest(path); auto updated_node = container.updateValue(path, operation.update_fn); - node_it->value.invalidateDigestCache(); - nodes_hash += updated_node->value.getDigest(path); + updated_node->value.invalidateDigestCache(); + nodes_digest += updated_node->value.getDigest(path); return Coordination::Error::ZOK; } @@ -478,7 +480,7 @@ bool KeeperStorage::createNode( ephemerals[session_id].emplace(path); auto digest = map_key->getMapped()->value.getDigest(map_key->getKey().toView()); - nodes_hash += digest; + nodes_digest += digest; return true; }; @@ -512,11 +514,10 @@ bool KeeperStorage::removeNode(const std::string & path, int32_t version) container.erase(path); - nodes_hash -= prev_node.getDigest(path); + nodes_digest -= prev_node.getDigest(path); return true; } - struct KeeperStorageRequestProcessor { Coordination::ZooKeeperRequestPtr zk_request; @@ -1594,7 +1595,7 @@ KeeperStorageRequestProcessorsFactory::KeeperStorageRequestProcessorsFactory() } -UInt64 KeeperStorage::calculateNodesHash(UInt64 current_hash, int64_t current_zxid) const +UInt64 KeeperStorage::calculateNodesDigest(UInt64 current_digest, int64_t current_zxid) const { std::unordered_map> updated_nodes; @@ -1617,7 +1618,7 @@ UInt64 KeeperStorage::calculateNodesHash(UInt64 current_hash, int64_t current_zx if (!updated_nodes.contains(delta.path)) { auto old_digest = uncommitted_state.getNode(delta.path, current_zxid)->getDigest(delta.path); - current_hash -= old_digest; + current_digest -= old_digest; } updated_nodes.insert_or_assign(delta.path, nullptr); @@ -1630,7 +1631,7 @@ UInt64 KeeperStorage::calculateNodesHash(UInt64 current_hash, int64_t current_zx if (updated_node_it == updated_nodes.end()) { node = uncommitted_state.getNode(delta.path, current_zxid); - current_hash -= node->getDigest(delta.path); + current_digest -= node->getDigest(delta.path); updated_nodes.emplace(delta.path, node); } else @@ -1647,11 +1648,12 @@ UInt64 KeeperStorage::calculateNodesHash(UInt64 current_hash, int64_t current_zx if (updated_node) { updated_node->invalidateDigestCache(); - current_hash += updated_node->getDigest(path); + current_digest += updated_node->getDigest(path); } } - return current_hash; + + return current_digest; } void KeeperStorage::preprocessRequest( @@ -1659,28 +1661,53 @@ void KeeperStorage::preprocessRequest( int64_t session_id, int64_t time, int64_t new_last_zxid, - std::optional expected_hash, + Digest expected_digest, bool check_acl) { int64_t last_zxid = getNextZXID() - 1; - if (new_last_zxid < last_zxid || (uncommitted_transactions.empty() && new_last_zxid == last_zxid)) - throw Exception( - ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, zxid); - - if (new_last_zxid == last_zxid && expected_hash && *expected_hash == uncommitted_transactions.back().nodes_hash) + if (uncommitted_transactions.empty()) { - if (expected_hash && *expected_hash != uncommitted_transactions.back().nodes_hash) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} equal to current ZXID ({}). It's a bug", new_last_zxid, zxid); + if (new_last_zxid <= last_zxid) + throw Exception( + ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, last_zxid); + } + else + { + // if we are leader node, the request potentially already got processed + auto txn_it = std::lower_bound( + uncommitted_transactions.begin(), + uncommitted_transactions.end(), + new_last_zxid, + [&](const auto & request, const auto value) { return request.zxid < value; }); + // this zxid is not found in the uncommitted_transactions so do the regular check + if (txn_it == uncommitted_transactions.end()) + { + if (new_last_zxid <= last_zxid) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", + new_last_zxid, + last_zxid); + } + else + { + if (txn_it->zxid == new_last_zxid && checkDigest(txn_it->nodes_digest, expected_digest)) + // we found the preprocessed request with the same ZXID, we can skip it + return; - // last uncommitted zxids is same as the current one so we are probably pre_committing on the leader - // but the leader already preprocessed the request while he appended the ZXID - return; + throw Exception( + ErrorCodes::LOGICAL_ERROR, "Found invalid state of uncommitted transactions, missing request with ZXID {}", new_last_zxid); + } } TransactionInfo transaction{.zxid = new_last_zxid}; SCOPE_EXIT({ - transaction.nodes_hash = expected_hash.value_or(calculateNodesHash(getNodesHash(false), transaction.zxid)); + if (expected_digest.version == DigestVersion::NO_DIGEST) + transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; + else + transaction.nodes_digest = expected_digest; + uncommitted_transactions.emplace_back(transaction); }); @@ -1866,12 +1893,12 @@ void KeeperStorage::rollbackRequest(int64_t rollback_zxid) uncommitted_transactions.pop_back(); } -uint64_t KeeperStorage::getNodesHash(bool committed) const +KeeperStorage::Digest KeeperStorage::getNodesDigest(bool committed) const { if (committed || uncommitted_transactions.empty()) - return nodes_hash; + return {CURRENT_DIGEST_VERSION, nodes_digest}; - return uncommitted_transactions.back().nodes_hash; + return uncommitted_transactions.back().nodes_digest; } void KeeperStorage::clearDeadWatches(int64_t session_id) diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index c62d0718134..6b68faa8327 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -63,6 +63,14 @@ public: mutable std::optional cached_digest; }; + enum DigestVersion : uint8_t + { + NO_DIGEST = 0, + V0 = 1 + }; + + static constexpr auto CURRENT_DIGEST_VERSION = DigestVersion::V0; + struct ResponseForSession { int64_t session_id; @@ -70,13 +78,30 @@ public: }; using ResponsesForSessions = std::vector; + struct Digest + { + DigestVersion version{DigestVersion::NO_DIGEST}; + uint64_t value{0}; + }; + + static bool checkDigest(const Digest & first, const Digest & second) + { + if (first.version != second.version) + return true; + + if (first.version == DigestVersion::NO_DIGEST) + return true; + + return first.value == second.value; + } + struct RequestForSession { int64_t session_id; int64_t time; Coordination::ZooKeeperRequestPtr request; int64_t zxid{0}; - std::optional nodes_hash = std::nullopt; + Digest digest; }; struct AuthID @@ -253,12 +278,12 @@ public: struct TransactionInfo { int64_t zxid; - uint64_t nodes_hash; + Digest nodes_digest; }; std::deque uncommitted_transactions; - uint64_t nodes_hash{0}; + uint64_t nodes_digest{0}; bool finalized{false}; @@ -279,7 +304,7 @@ public: return uncommitted_transactions.back().zxid + 1; } - uint64_t getNodesHash(bool committed) const; + Digest getNodesDigest(bool committed) const; const String superdigest; @@ -301,7 +326,7 @@ public: session_expiry_queue.addNewSessionOrUpdate(session_id, session_timeout_ms); } - UInt64 calculateNodesHash(UInt64 current_hash, int64_t current_zxid) const; + UInt64 calculateNodesDigest(UInt64 current_digest, int64_t current_zxid) const; /// Process user request and return response. /// check_acl = false only when converting data from ZooKeeper. @@ -317,7 +342,7 @@ public: int64_t session_id, int64_t time, int64_t new_last_zxid, - std::optional expected_hash = std::nullopt, + Digest digest, bool check_acl = true); void rollbackRequest(int64_t rollback_zxid); diff --git a/src/Coordination/ZooKeeperDataReader.cpp b/src/Coordination/ZooKeeperDataReader.cpp index 4d1745edc6a..b11a296fefe 100644 --- a/src/Coordination/ZooKeeperDataReader.cpp +++ b/src/Coordination/ZooKeeperDataReader.cpp @@ -520,7 +520,7 @@ bool deserializeTxn(KeeperStorage & storage, ReadBuffer & in, Poco::Logger * /*l if (request->getOpNum() == Coordination::OpNum::Multi && hasErrorsInMultiRequest(request)) return true; - storage.preprocessRequest(request, session_id, time, zxid, /* check_acl = */ false); + storage.preprocessRequest(request, session_id, time, zxid, KeeperStorage::Digest{}, /* check_acl = */ false); storage.processRequest(request, session_id, time, zxid, /* check_acl = */ false); } } From 2205c01697d3f5fe59d7d7f49eff343e2e5268f1 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Mon, 16 May 2022 13:08:10 +0000 Subject: [PATCH 020/204] Add to snapshot and config for digest --- src/Coordination/KeeperServer.cpp | 3 +- src/Coordination/KeeperSnapshotManager.cpp | 7 ++++ src/Coordination/KeeperSnapshotManager.h | 4 +- src/Coordination/KeeperStateMachine.cpp | 10 +++-- src/Coordination/KeeperStateMachine.h | 5 ++- src/Coordination/KeeperStorage.cpp | 39 +++++++++++++------ src/Coordination/KeeperStorage.h | 12 ++++-- src/Coordination/ZooKeeperDataReader.cpp | 2 +- src/Coordination/tests/gtest_coordination.cpp | 12 +++--- 9 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index ee5ed56e4cf..24375269f7f 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -113,7 +113,8 @@ KeeperServer::KeeperServer( snapshots_queue_, configuration_and_settings_->snapshot_storage_path, coordination_settings, - checkAndGetSuperdigest(configuration_and_settings_->super_digest))) + checkAndGetSuperdigest(configuration_and_settings_->super_digest), + config.getBool("keeper_server.digest_enabled", true))) , state_manager(nuraft::cs_new( server_id, "keeper_server", configuration_and_settings_->log_storage_path, config, coordination_settings)) , log(&Poco::Logger::get("KeeperServer")) diff --git a/src/Coordination/KeeperSnapshotManager.cpp b/src/Coordination/KeeperSnapshotManager.cpp index ddb44d2ca0b..d45f58a0bf7 100644 --- a/src/Coordination/KeeperSnapshotManager.cpp +++ b/src/Coordination/KeeperSnapshotManager.cpp @@ -149,7 +149,10 @@ void KeeperStorageSnapshot::serialize(const KeeperStorageSnapshot & snapshot, Wr serializeSnapshotMetadata(snapshot.snapshot_meta, out); if (snapshot.version >= SnapshotVersion::V5) + { writeBinary(snapshot.zxid, out); + writeBinary(snapshot.nodes_digest, out); + } writeBinary(snapshot.session_id, out); @@ -241,7 +244,10 @@ void KeeperStorageSnapshot::deserialize(SnapshotDeserializationResult & deserial KeeperStorage & storage = *deserialization_result.storage; if (version >= SnapshotVersion::V5) + { readBinary(storage.zxid, in); + readBinary(storage.nodes_digest, in); + } else storage.zxid = deserialization_result.snapshot_meta->get_last_log_idx(); @@ -372,6 +378,7 @@ KeeperStorageSnapshot::KeeperStorageSnapshot(KeeperStorage * storage_, const Sna , session_id(storage->session_id_counter) , cluster_config(cluster_config_) , zxid(storage->zxid) + , nodes_digest(storage->nodes_digest) { auto [size, ver] = storage->container.snapshotSizeWithVersion(); snapshot_container_size = size; diff --git a/src/Coordination/KeeperSnapshotManager.h b/src/Coordination/KeeperSnapshotManager.h index c4ce9ee2937..d85be5334ef 100644 --- a/src/Coordination/KeeperSnapshotManager.h +++ b/src/Coordination/KeeperSnapshotManager.h @@ -21,7 +21,7 @@ enum SnapshotVersion : uint8_t V2 = 2, /// with 64 bit buffer header V3 = 3, /// compress snapshots with ZSTD codec V4 = 4, /// add Node size to snapshots - V5 = 5, /// add ZXID to snapshots + V5 = 5, /// add ZXID and digest to snapshots }; static constexpr auto CURRENT_SNAPSHOT_VERSION = SnapshotVersion::V4; @@ -80,6 +80,8 @@ public: ClusterConfigPtr cluster_config; /// Last committed ZXID int64_t zxid; + /// Current digest of committed nodes + uint64_t nodes_digest; }; using KeeperStorageSnapshotPtr = std::shared_ptr; diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index d2852549398..df417b72b2e 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -26,7 +26,8 @@ KeeperStateMachine::KeeperStateMachine( SnapshotsQueue & snapshots_queue_, const std::string & snapshots_path_, const CoordinationSettingsPtr & coordination_settings_, - const std::string & superdigest_) + const std::string & superdigest_, + const bool digest_enabled_) : coordination_settings(coordination_settings_) , snapshot_manager( snapshots_path_, coordination_settings->snapshots_to_keep, @@ -37,6 +38,7 @@ KeeperStateMachine::KeeperStateMachine( , last_committed_idx(0) , log(&Poco::Logger::get("KeeperStateMachine")) , superdigest(superdigest_) + , digest_enabled(digest_enabled_) { } @@ -83,7 +85,7 @@ void KeeperStateMachine::init() } if (!storage) - storage = std::make_unique(coordination_settings->dead_session_check_period_ms.totalMilliseconds(), superdigest); + storage = std::make_unique(coordination_settings->dead_session_check_period_ms.totalMilliseconds(), superdigest, digest_enabled); } nuraft::ptr KeeperStateMachine::pre_commit(uint64_t log_idx, nuraft::buffer & data) @@ -139,7 +141,7 @@ void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & req if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; std::lock_guard lock(storage_and_responses_lock); - storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, request_for_session.digest); + storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, true /* check_acl */, request_for_session.digest); } nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data) @@ -178,7 +180,7 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n } - if (!KeeperStorage::checkDigest(request_for_session.digest, storage->getNodesDigest(true))) + if (digest_enabled && !KeeperStorage::checkDigest(request_for_session.digest, storage->getNodesDigest(true))) { LOG_ERROR(log, "Digest for nodes is not matching after applying request of type {}", request_for_session.request->getOpNum()); std::terminate(); diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index d9f5c947fd6..3475e7ec0b7 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -22,7 +22,8 @@ public: KeeperStateMachine( ResponsesQueue & responses_queue_, SnapshotsQueue & snapshots_queue_, const std::string & snapshots_path_, const CoordinationSettingsPtr & coordination_settings_, - const std::string & superdigest_ = ""); + const std::string & superdigest_ = "", + bool digest_enabled_ = true); /// Read state from the latest snapshot void init(); @@ -150,6 +151,8 @@ private: /// Special part of ACL system -- superdigest specified in server config. const std::string superdigest; + + const bool digest_enabled; }; } diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index c3d73e0c7ed..18e65bc1f28 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -219,8 +219,8 @@ void KeeperStorage::Node::setDigest(UInt64 digest) cached_digest.emplace(digest); } -KeeperStorage::KeeperStorage(int64_t tick_time_ms, const String & superdigest_) - : session_expiry_queue(tick_time_ms), superdigest(superdigest_) +KeeperStorage::KeeperStorage(int64_t tick_time_ms, const String & superdigest_, const bool digest_enabled_) + : session_expiry_queue(tick_time_ms), digest_enabled(digest_enabled_), superdigest(superdigest_) { container.insert("/", Node()); } @@ -379,10 +379,9 @@ Coordination::Error KeeperStorage::commit(int64_t commit_zxid, int64_t session_i if (operation.version != -1 && operation.version != node_it->value.stat.version) onStorageInconsistency(); - nodes_digest -= node_it->value.getDigest(path); + removeDigest(node_it->value, path); auto updated_node = container.updateValue(path, operation.update_fn); - updated_node->value.invalidateDigestCache(); - nodes_digest += updated_node->value.getDigest(path); + addDigest(updated_node->value, path); return Coordination::Error::ZOK; } @@ -479,9 +478,7 @@ bool KeeperStorage::createNode( if (is_ephemeral) ephemerals[session_id].emplace(path); - auto digest = map_key->getMapped()->value.getDigest(map_key->getKey().toView()); - nodes_digest += digest; - + addDigest(map_key->getMapped()->value, map_key->getKey().toView()); return true; }; @@ -514,7 +511,7 @@ bool KeeperStorage::removeNode(const std::string & path, int32_t version) container.erase(path); - nodes_digest -= prev_node.getDigest(path); + removeDigest(prev_node, path); return true; } @@ -1661,8 +1658,8 @@ void KeeperStorage::preprocessRequest( int64_t session_id, int64_t time, int64_t new_last_zxid, - Digest expected_digest, - bool check_acl) + bool check_acl, + Digest expected_digest) { int64_t last_zxid = getNextZXID() - 1; @@ -1703,7 +1700,7 @@ void KeeperStorage::preprocessRequest( TransactionInfo transaction{.zxid = new_last_zxid}; SCOPE_EXIT({ - if (expected_digest.version == DigestVersion::NO_DIGEST) + if (expected_digest.version == DigestVersion::NO_DIGEST && digest_enabled) transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; else transaction.nodes_digest = expected_digest; @@ -1895,12 +1892,30 @@ void KeeperStorage::rollbackRequest(int64_t rollback_zxid) KeeperStorage::Digest KeeperStorage::getNodesDigest(bool committed) const { + if (!digest_enabled) + return {.version = DigestVersion::NO_DIGEST}; + if (committed || uncommitted_transactions.empty()) return {CURRENT_DIGEST_VERSION, nodes_digest}; return uncommitted_transactions.back().nodes_digest; } +void KeeperStorage::removeDigest(const Node & node, const std::string_view path) +{ + if (digest_enabled) + nodes_digest -= node.getDigest(path); +} + +void KeeperStorage::addDigest(const Node & node, const std::string_view path) +{ + if (digest_enabled) + { + node.invalidateDigestCache(); + nodes_digest += node.getDigest(path); + } +} + void KeeperStorage::clearDeadWatches(int64_t session_id) { /// Clear all watches for this session diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 6b68faa8327..d244ff31e1d 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -306,9 +306,11 @@ public: Digest getNodesDigest(bool committed) const; + const bool digest_enabled; + const String superdigest; - KeeperStorage(int64_t tick_time_ms, const String & superdigest_); + KeeperStorage(int64_t tick_time_ms, const String & superdigest_, bool digest_enabled_ = true); /// Allocate new session id with the specified timeouts int64_t getSessionID(int64_t session_timeout_ms) @@ -342,8 +344,8 @@ public: int64_t session_id, int64_t time, int64_t new_last_zxid, - Digest digest, - bool check_acl = true); + bool check_acl = true, + Digest digest = {DigestVersion::NO_DIGEST, 0}); void rollbackRequest(int64_t rollback_zxid); void finalize(); @@ -386,6 +388,10 @@ public: void dumpWatches(WriteBufferFromOwnString & buf) const; void dumpWatchesByPath(WriteBufferFromOwnString & buf) const; void dumpSessionsAndEphemerals(WriteBufferFromOwnString & buf) const; + +private: + void removeDigest(const Node & node, std::string_view path); + void addDigest(const Node & node, std::string_view path); }; using KeeperStoragePtr = std::unique_ptr; diff --git a/src/Coordination/ZooKeeperDataReader.cpp b/src/Coordination/ZooKeeperDataReader.cpp index b11a296fefe..7090234b593 100644 --- a/src/Coordination/ZooKeeperDataReader.cpp +++ b/src/Coordination/ZooKeeperDataReader.cpp @@ -520,7 +520,7 @@ bool deserializeTxn(KeeperStorage & storage, ReadBuffer & in, Poco::Logger * /*l if (request->getOpNum() == Coordination::OpNum::Multi && hasErrorsInMultiRequest(request)) return true; - storage.preprocessRequest(request, session_id, time, zxid, KeeperStorage::Digest{}, /* check_acl = */ false); + storage.preprocessRequest(request, session_id, time, zxid, /* check_acl = */ false, {KeeperStorage::DigestVersion::NO_DIGEST}); storage.processRequest(request, session_id, time, zxid, /* check_acl = */ false); } } diff --git a/src/Coordination/tests/gtest_coordination.cpp b/src/Coordination/tests/gtest_coordination.cpp index 2742f48f49e..e518c07a8ff 100644 --- a/src/Coordination/tests/gtest_coordination.cpp +++ b/src/Coordination/tests/gtest_coordination.cpp @@ -986,24 +986,24 @@ TEST_P(CoordinationTest, SnapshotableHashMapDataSize) world.disableSnapshotMode(); world.insert("world", n1); - EXPECT_EQ(world.getApproximateDataSize(), 177); + EXPECT_EQ(world.getApproximateDataSize(), 193); world.updateValue("world", [&](Node & value) { value = n2; }); - EXPECT_EQ(world.getApproximateDataSize(), 195); + EXPECT_EQ(world.getApproximateDataSize(), 211); world.erase("world"); EXPECT_EQ(world.getApproximateDataSize(), 0); world.enableSnapshotMode(100000); world.insert("world", n1); - EXPECT_EQ(world.getApproximateDataSize(), 177); + EXPECT_EQ(world.getApproximateDataSize(), 193); world.updateValue("world", [&](Node & value) { value = n2; }); - EXPECT_EQ(world.getApproximateDataSize(), 372); + EXPECT_EQ(world.getApproximateDataSize(), 404); world.clearOutdatedNodes(); - EXPECT_EQ(world.getApproximateDataSize(), 195); + EXPECT_EQ(world.getApproximateDataSize(), 211); world.erase("world"); - EXPECT_EQ(world.getApproximateDataSize(), 195); + EXPECT_EQ(world.getApproximateDataSize(), 211); world.clear(); EXPECT_EQ(world.getApproximateDataSize(), 0); From 4a91ab6560de1fef766721ea4dc6fa4a4499559a Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Mon, 16 May 2022 13:38:49 +0000 Subject: [PATCH 021/204] Use NuRaft with PreAppendLogs --- contrib/NuRaft | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index 24a13f15cf0..6e4b5e36de1 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit 24a13f15cf0838b93f3b1beb62ed010dffdb2117 +Subproject commit 6e4b5e36de157acb9d8d85d67942b99560d50bd9 From fc5a79f186b54803afecb9633f6ff921ab4c77c0 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 17 May 2022 08:11:08 +0000 Subject: [PATCH 022/204] Polishing changes --- src/Common/ZooKeeper/ZooKeeperCommon.cpp | 102 +++++++++++++++++++++ src/Common/ZooKeeper/ZooKeeperCommon.h | 15 +++ src/Coordination/KeeperServer.cpp | 7 +- src/Coordination/KeeperSnapshotManager.cpp | 18 +++- src/Coordination/KeeperStateMachine.cpp | 21 ++++- src/Coordination/KeeperStateMachine.h | 3 +- src/Coordination/KeeperStorage.cpp | 27 ++++-- src/Coordination/KeeperStorage.h | 13 ++- src/Coordination/ZooKeeperDataReader.cpp | 2 +- 9 files changed, 181 insertions(+), 27 deletions(-) diff --git a/src/Common/ZooKeeper/ZooKeeperCommon.cpp b/src/Common/ZooKeeper/ZooKeeperCommon.cpp index e958774c4a0..2c7bf8cc1f0 100644 --- a/src/Common/ZooKeeper/ZooKeeperCommon.cpp +++ b/src/Common/ZooKeeper/ZooKeeperCommon.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,17 @@ void ZooKeeperResponse::write(WriteBuffer & out) const out.next(); } +std::string ZooKeeperRequest::toString() const +{ + return fmt::format( + "XID = {}\n" + "OpNum = {}\n" + "Additional info:\n{}", + xid, + getOpNum(), + toStringImpl()); +} + void ZooKeeperRequest::write(WriteBuffer & out) const { /// Excessive copy to calculate length. @@ -48,6 +60,11 @@ void ZooKeeperSyncRequest::readImpl(ReadBuffer & in) Coordination::read(path, in); } +std::string ZooKeeperSyncRequest::toStringImpl() const +{ + return fmt::format("path = {}", path); +} + void ZooKeeperSyncResponse::readImpl(ReadBuffer & in) { Coordination::read(path, in); @@ -93,6 +110,17 @@ void ZooKeeperAuthRequest::readImpl(ReadBuffer & in) Coordination::read(data, in); } +std::string ZooKeeperAuthRequest::toStringImpl() const +{ + return fmt::format( + "type = {}\n" + "scheme = {}\n" + "data = {}", + type, + scheme, + data); +} + void ZooKeeperCreateRequest::writeImpl(WriteBuffer & out) const { Coordination::write(path, out); @@ -124,6 +152,19 @@ void ZooKeeperCreateRequest::readImpl(ReadBuffer & in) is_sequential = true; } +std::string ZooKeeperCreateRequest::toStringImpl() const +{ + return fmt::format( + "path = {}\n" + "data = {}\n" + "is_ephemeral = {}\n" + "is_sequential = {}", + path, + data, + is_ephemeral, + is_sequential); +} + void ZooKeeperCreateResponse::readImpl(ReadBuffer & in) { Coordination::read(path_created, in); @@ -140,6 +181,15 @@ void ZooKeeperRemoveRequest::writeImpl(WriteBuffer & out) const Coordination::write(version, out); } +std::string ZooKeeperRemoveRequest::toStringImpl() const +{ + return fmt::format( + "path = {}\n" + "version = {}", + path, + version); +} + void ZooKeeperRemoveRequest::readImpl(ReadBuffer & in) { Coordination::read(path, in); @@ -158,6 +208,11 @@ void ZooKeeperExistsRequest::readImpl(ReadBuffer & in) Coordination::read(has_watch, in); } +std::string ZooKeeperExistsRequest::toStringImpl() const +{ + return fmt::format("path = {}", path); +} + void ZooKeeperExistsResponse::readImpl(ReadBuffer & in) { Coordination::read(stat, in); @@ -180,6 +235,11 @@ void ZooKeeperGetRequest::readImpl(ReadBuffer & in) Coordination::read(has_watch, in); } +std::string ZooKeeperGetRequest::toStringImpl() const +{ + return fmt::format("path = {}", path); +} + void ZooKeeperGetResponse::readImpl(ReadBuffer & in) { Coordination::read(data, in); @@ -206,6 +266,17 @@ void ZooKeeperSetRequest::readImpl(ReadBuffer & in) Coordination::read(version, in); } +std::string ZooKeeperSetRequest::toStringImpl() const +{ + return fmt::format( + "path = {}\n", + "data = {}\n" + "version = {}", + path, + data, + version); +} + void ZooKeeperSetResponse::readImpl(ReadBuffer & in) { Coordination::read(stat, in); @@ -228,6 +299,11 @@ void ZooKeeperListRequest::readImpl(ReadBuffer & in) Coordination::read(has_watch, in); } +std::string ZooKeeperListRequest::toStringImpl() const +{ + return fmt::format("path = {}", path); +} + void ZooKeeperListResponse::readImpl(ReadBuffer & in) { Coordination::read(names, in); @@ -255,6 +331,11 @@ void ZooKeeperSetACLRequest::readImpl(ReadBuffer & in) Coordination::read(version, in); } +std::string ZooKeeperSetACLRequest::toStringImpl() const +{ + return fmt::format("path = {}\n", "version = {}", path, version); +} + void ZooKeeperSetACLResponse::writeImpl(WriteBuffer & out) const { Coordination::write(stat, out); @@ -275,6 +356,11 @@ void ZooKeeperGetACLRequest::writeImpl(WriteBuffer & out) const Coordination::write(path, out); } +std::string ZooKeeperGetACLRequest::toStringImpl() const +{ + return fmt::format("path = {}", path); +} + void ZooKeeperGetACLResponse::writeImpl(WriteBuffer & out) const { Coordination::write(acl, out); @@ -299,6 +385,11 @@ void ZooKeeperCheckRequest::readImpl(ReadBuffer & in) Coordination::read(version, in); } +std::string ZooKeeperCheckRequest::toStringImpl() const +{ + return fmt::format("path = {}\n", "version = {}", path, version); +} + void ZooKeeperErrorResponse::readImpl(ReadBuffer & in) { Coordination::Error read_error; @@ -401,6 +492,17 @@ void ZooKeeperMultiRequest::readImpl(ReadBuffer & in) } } +std::string ZooKeeperMultiRequest::toStringImpl() const +{ + auto out = fmt::memory_buffer(); + for (const auto & request : requests) + { + const auto & zk_request = dynamic_cast(*request); + format_to(std::back_inserter(out), "SubRequest\n{}\n", zk_request.toString()); + } + return {out.data(), out.size()}; +} + bool ZooKeeperMultiRequest::isReadRequest() const { /// Possibly we can do better diff --git a/src/Common/ZooKeeper/ZooKeeperCommon.h b/src/Common/ZooKeeper/ZooKeeperCommon.h index 532488c08f8..9c54dfb9f0c 100644 --- a/src/Common/ZooKeeper/ZooKeeperCommon.h +++ b/src/Common/ZooKeeper/ZooKeeperCommon.h @@ -68,10 +68,13 @@ struct ZooKeeperRequest : virtual Request /// Writes length, xid, op_num, then the rest. void write(WriteBuffer & out) const; + std::string toString() const; virtual void writeImpl(WriteBuffer &) const = 0; virtual void readImpl(ReadBuffer &) = 0; + virtual std::string toStringImpl() const { return ""; } + static std::shared_ptr read(ReadBuffer & in); virtual ZooKeeperResponsePtr makeResponse() const = 0; @@ -100,6 +103,7 @@ struct ZooKeeperSyncRequest final : ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Sync; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return false; } @@ -150,6 +154,7 @@ struct ZooKeeperAuthRequest final : ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Auth; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return false; } @@ -202,6 +207,7 @@ struct ZooKeeperCreateRequest final : public CreateRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Create; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return false; } @@ -232,6 +238,7 @@ struct ZooKeeperRemoveRequest final : RemoveRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Remove; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return false; } @@ -255,6 +262,7 @@ struct ZooKeeperExistsRequest final : ExistsRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Exists; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return true; } @@ -278,6 +286,7 @@ struct ZooKeeperGetRequest final : GetRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Get; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return true; } @@ -304,6 +313,7 @@ struct ZooKeeperSetRequest final : SetRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Set; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return false; } @@ -328,6 +338,7 @@ struct ZooKeeperListRequest : ListRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::List; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return true; } @@ -363,6 +374,7 @@ struct ZooKeeperCheckRequest final : CheckRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::Check; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return true; } @@ -397,6 +409,7 @@ struct ZooKeeperSetACLRequest final : SetACLRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::SetACL; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return false; } @@ -417,6 +430,7 @@ struct ZooKeeperGetACLRequest final : GetACLRequest, ZooKeeperRequest OpNum getOpNum() const override { return OpNum::GetACL; } void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override { return true; } @@ -441,6 +455,7 @@ struct ZooKeeperMultiRequest final : MultiRequest, ZooKeeperRequest void writeImpl(WriteBuffer & out) const override; void readImpl(ReadBuffer & in) override; + std::string toStringImpl() const override; ZooKeeperResponsePtr makeResponse() const override; bool isReadRequest() const override; diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 24375269f7f..378cec4f2b0 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -405,9 +405,10 @@ nuraft::ptr getZooKeeperLogEntry(const KeeperStorage::RequestFor request_for_session.request->write(write_buf); DB::writeIntBinary(request_for_session.time, write_buf); DB::writeIntBinary(request_for_session.zxid, write_buf); - DB::writeIntBinary(request_for_session.digest.version, write_buf); - if (request_for_session.digest.version != KeeperStorage::DigestVersion::NO_DIGEST) - DB::writeIntBinary(request_for_session.digest.value, write_buf); + assert(request_for_session.digest); + DB::writeIntBinary(request_for_session.digest->version, write_buf); + if (request_for_session.digest->version != KeeperStorage::DigestVersion::NO_DIGEST) + DB::writeIntBinary(request_for_session.digest->value, write_buf); return write_buf.getBuffer(); } diff --git a/src/Coordination/KeeperSnapshotManager.cpp b/src/Coordination/KeeperSnapshotManager.cpp index d45f58a0bf7..47551a9cc3e 100644 --- a/src/Coordination/KeeperSnapshotManager.cpp +++ b/src/Coordination/KeeperSnapshotManager.cpp @@ -151,6 +151,7 @@ void KeeperStorageSnapshot::serialize(const KeeperStorageSnapshot & snapshot, Wr if (snapshot.version >= SnapshotVersion::V5) { writeBinary(snapshot.zxid, out); + writeBinary(static_cast(KeeperStorage::CURRENT_DIGEST_VERSION), out); writeBinary(snapshot.nodes_digest, out); } @@ -243,10 +244,22 @@ void KeeperStorageSnapshot::deserialize(SnapshotDeserializationResult & deserial deserialization_result.snapshot_meta = deserializeSnapshotMetadata(in); KeeperStorage & storage = *deserialization_result.storage; + bool recalculate_digest = storage.digest_enabled; if (version >= SnapshotVersion::V5) { readBinary(storage.zxid, in); - readBinary(storage.nodes_digest, in); + uint8_t digest_version; + readBinary(digest_version, in); + if (digest_version != KeeperStorage::DigestVersion::NO_DIGEST) + { + uint64_t nodes_digest; + readBinary(nodes_digest, in); + if (digest_version == KeeperStorage::CURRENT_DIGEST_VERSION) + { + storage.nodes_digest = nodes_digest; + recalculate_digest = false; + } + } } else storage.zxid = deserialization_result.snapshot_meta->get_last_log_idx(); @@ -299,6 +312,9 @@ void KeeperStorageSnapshot::deserialize(SnapshotDeserializationResult & deserial storage.ephemerals[node.stat.ephemeralOwner].insert(path); current_size++; + + if (recalculate_digest) + storage.nodes_digest += node.getDigest(path); } for (const auto & itr : storage.container) diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index df417b72b2e..c4a299b2532 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -128,9 +128,10 @@ KeeperStorage::RequestForSession KeeperStateMachine::parseRequest(nuraft::buffer if (!buffer.eof()) { - readIntBinary(request_for_session.digest.version, buffer); - if (request_for_session.digest.version != KeeperStorage::DigestVersion::NO_DIGEST) - readIntBinary(request_for_session.digest.value, buffer); + request_for_session.digest.emplace(); + readIntBinary(request_for_session.digest->version, buffer); + if (request_for_session.digest->version != KeeperStorage::DigestVersion::NO_DIGEST) + readIntBinary(request_for_session.digest->value, buffer); } return request_for_session; @@ -180,9 +181,19 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n } - if (digest_enabled && !KeeperStorage::checkDigest(request_for_session.digest, storage->getNodesDigest(true))) + assert(request_for_session.digest); + auto local_nodes_digest = storage->getNodesDigest(true); + if (digest_enabled && !KeeperStorage::checkDigest(*request_for_session.digest, local_nodes_digest)) { - LOG_ERROR(log, "Digest for nodes is not matching after applying request of type {}", request_for_session.request->getOpNum()); + LOG_ERROR( + log, + "Digest for nodes is not matching after applying request of type '{}'.\nExpected digest - {}, actual digest {} (digest version {}). Keeper will " + "terminate to avoid inconsistencies.\nExtra information about the request:\n{}", + request_for_session.request->getOpNum(), + request_for_session.digest->value, + local_nodes_digest.value, + request_for_session.digest->version, + request_for_session.request->toString()); std::terminate(); } diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index 3475e7ec0b7..66eff3b62f9 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -28,7 +28,8 @@ public: /// Read state from the latest snapshot void init(); - KeeperStorage::RequestForSession parseRequest(nuraft::buffer & data); + static KeeperStorage::RequestForSession parseRequest(nuraft::buffer & data); + void preprocess(const KeeperStorage::RequestForSession & request_for_session); nuraft::ptr pre_commit(uint64_t log_idx, nuraft::buffer & data) override; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 71d48e53f09..e64962bd01a 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -236,7 +236,7 @@ struct Overloaded : Ts... template Overloaded(Ts...) -> Overloaded; -std::shared_ptr KeeperStorage::UncommittedState::getNode(StringRef path, std::optional last_zxid) const +std::shared_ptr KeeperStorage::UncommittedState::getNode(StringRef path, std::optional current_zxid) const { std::shared_ptr node{nullptr}; @@ -273,7 +273,7 @@ std::shared_ptr KeeperStorage::UncommittedState::getNode(St }, [&](auto && /*delta*/) {}, }, - last_zxid); + current_zxid); return node; } @@ -1657,7 +1657,7 @@ void KeeperStorage::preprocessRequest( int64_t time, int64_t new_last_zxid, bool check_acl, - Digest expected_digest) + std::optional digest) { int64_t last_zxid = getNextZXID() - 1; @@ -1669,7 +1669,9 @@ void KeeperStorage::preprocessRequest( } else { - // if we are leader node, the request potentially already got processed + // if we are Leader node, the request potentially already got preprocessed + // Leader can preprocess requests in a batch so the ZXID we are searching isn't + // guaranteed to be last auto txn_it = std::lower_bound( uncommitted_transactions.begin(), uncommitted_transactions.end(), @@ -1687,7 +1689,7 @@ void KeeperStorage::preprocessRequest( } else { - if (txn_it->zxid == new_last_zxid && checkDigest(txn_it->nodes_digest, expected_digest)) + if (txn_it->zxid == new_last_zxid) // we found the preprocessed request with the same ZXID, we can skip it return; @@ -1698,10 +1700,17 @@ void KeeperStorage::preprocessRequest( TransactionInfo transaction{.zxid = new_last_zxid}; SCOPE_EXIT({ - if (expected_digest.version == DigestVersion::NO_DIGEST && digest_enabled) - transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; + if (digest_enabled) + { + // if the leader has the same digest calculation version we + // can skip recalculating and use that value + if (digest && digest->version == CURRENT_DIGEST_VERSION) + transaction.nodes_digest = *digest; + else + transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; + } else - transaction.nodes_digest = expected_digest; + transaction.nodes_digest = Digest{DigestVersion::NO_DIGEST}; uncommitted_transactions.emplace_back(transaction); }); @@ -1825,7 +1834,7 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( if (is_local) { - assert(!zk_request->isReadRequest()); + assert(zk_request->isReadRequest()); if (check_acl && !request_processor->checkAuth(*this, session_id, true)) { response = zk_request->makeResponse(); diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 4227f7b404f..d4c60735bd7 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -101,7 +101,7 @@ public: int64_t time; Coordination::ZooKeeperRequestPtr request; int64_t zxid{0}; - Digest digest; + std::optional digest; }; struct AuthID @@ -211,11 +211,11 @@ public: explicit UncommittedState(KeeperStorage & storage_) : storage(storage_) { } template - void applyDeltas(StringRef path, const Visitor & visitor, std::optional last_zxid = std::nullopt) const + void applyDeltas(StringRef path, const Visitor & visitor, std::optional current_zxid = std::nullopt) const { for (const auto & delta : deltas) { - if (last_zxid && delta.zxid >= last_zxid) + if (current_zxid && delta.zxid >= current_zxid) break; if (path.empty() || delta.path == path) @@ -223,7 +223,7 @@ public: } } - bool hasACL(int64_t session_id, bool is_local, std::function predicate) + bool hasACL(int64_t session_id, bool is_local, std::function predicate) { for (const auto & session_auth : storage.session_and_auth[session_id]) { @@ -234,7 +234,6 @@ public: if (is_local) return false; - for (const auto & delta : deltas) { if (const auto * auth_delta = std::get_if(&delta.operation); @@ -245,7 +244,7 @@ public: return false; } - std::shared_ptr getNode(StringRef path, std::optional last_zxid = std::nullopt) const; + std::shared_ptr getNode(StringRef path, std::optional current_zxid = std::nullopt) const; bool hasNode(StringRef path) const; Coordination::ACLs getACLs(StringRef path) const; @@ -361,7 +360,7 @@ public: int64_t time, int64_t new_last_zxid, bool check_acl = true, - Digest digest = {DigestVersion::NO_DIGEST, 0}); + std::optional digest = std::nullopt); void rollbackRequest(int64_t rollback_zxid); void finalize(); diff --git a/src/Coordination/ZooKeeperDataReader.cpp b/src/Coordination/ZooKeeperDataReader.cpp index 7090234b593..4d1745edc6a 100644 --- a/src/Coordination/ZooKeeperDataReader.cpp +++ b/src/Coordination/ZooKeeperDataReader.cpp @@ -520,7 +520,7 @@ bool deserializeTxn(KeeperStorage & storage, ReadBuffer & in, Poco::Logger * /*l if (request->getOpNum() == Coordination::OpNum::Multi && hasErrorsInMultiRequest(request)) return true; - storage.preprocessRequest(request, session_id, time, zxid, /* check_acl = */ false, {KeeperStorage::DigestVersion::NO_DIGEST}); + storage.preprocessRequest(request, session_id, time, zxid, /* check_acl = */ false); storage.processRequest(request, session_id, time, zxid, /* check_acl = */ false); } } From c5e45984473dabeeae0a049ea4fd21563d5e09e0 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 17 May 2022 10:23:51 +0000 Subject: [PATCH 023/204] Calculate digest on preprocess --- src/Coordination/KeeperStorage.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index e64962bd01a..2d0b10f0d9a 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -1689,7 +1689,7 @@ void KeeperStorage::preprocessRequest( } else { - if (txn_it->zxid == new_last_zxid) + if (txn_it->zxid == new_last_zxid && (!digest || checkDigest(*digest, txn_it->nodes_digest))) // we found the preprocessed request with the same ZXID, we can skip it return; @@ -1701,14 +1701,7 @@ void KeeperStorage::preprocessRequest( TransactionInfo transaction{.zxid = new_last_zxid}; SCOPE_EXIT({ if (digest_enabled) - { - // if the leader has the same digest calculation version we - // can skip recalculating and use that value - if (digest && digest->version == CURRENT_DIGEST_VERSION) - transaction.nodes_digest = *digest; - else - transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; - } + transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; else transaction.nodes_digest = Digest{DigestVersion::NO_DIGEST}; From 6fba1c96edea332bba6bc3c1b790476ab017c2b8 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 17 May 2022 13:53:12 +0000 Subject: [PATCH 024/204] Define small test for digest check --- src/Common/ErrorCodes.cpp | 1 + src/Coordination/KeeperStateMachine.cpp | 53 ++++++++---- src/Coordination/KeeperStorage.cpp | 3 + src/Coordination/tests/gtest_coordination.cpp | 86 ++++++++++++++++--- 4 files changed, 110 insertions(+), 33 deletions(-) diff --git a/src/Common/ErrorCodes.cpp b/src/Common/ErrorCodes.cpp index eb84e24b713..5a40362489f 100644 --- a/src/Common/ErrorCodes.cpp +++ b/src/Common/ErrorCodes.cpp @@ -624,6 +624,7 @@ M(653, CANNOT_PARSE_BACKUP_SETTINGS) \ M(654, WRONG_BACKUP_SETTINGS) \ M(655, FAILED_TO_RESTORE_METADATA_ON_OTHER_NODE) \ + M(656, INVALID_STATE) \ \ M(999, KEEPER_EXCEPTION) \ M(1000, POCO_EXCEPTION) \ diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index c4a299b2532..25e497e32a3 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -4,7 +4,9 @@ #include #include #include +#include "Common/ZooKeeper/ZooKeeperCommon.h" #include +#include "Coordination/KeeperStorage.h" #include #include @@ -13,7 +15,7 @@ namespace DB namespace ErrorCodes { - extern const int LOGICAL_ERROR; + extern const int INVALID_STATE; extern const int SYSTEM_ERROR; } @@ -137,12 +139,39 @@ KeeperStorage::RequestForSession KeeperStateMachine::parseRequest(nuraft::buffer return request_for_session; } +namespace +{ + +void assertDigest(const KeeperStorage::Digest & first, const KeeperStorage::Digest & second, const Coordination::ZooKeeperRequest & request, bool committing) +{ + if (!KeeperStorage::checkDigest(first, second)) + { + throw DB::Exception( + DB::ErrorCodes::INVALID_STATE, + "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest {} (digest version {}). Keeper will " + "terminate to avoid inconsistencies.\nExtra information about the request:\n{}", + committing ? "committing" : "preprocessing", + request.getOpNum(), + first.value, + second.value, + first.version, + request.toString()); + } +} + +} + void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & request_for_session) { if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; - std::lock_guard lock(storage_and_responses_lock); - storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, true /* check_acl */, request_for_session.digest); + { + std::lock_guard lock(storage_and_responses_lock); + storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, true /* check_acl */, request_for_session.digest); + } + + if (digest_enabled && request_for_session.digest) + assertDigest(*request_for_session.digest, storage->getNodesDigest(false), *request_for_session.request, false); } nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data) @@ -182,21 +211,7 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n assert(request_for_session.digest); - auto local_nodes_digest = storage->getNodesDigest(true); - if (digest_enabled && !KeeperStorage::checkDigest(*request_for_session.digest, local_nodes_digest)) - { - LOG_ERROR( - log, - "Digest for nodes is not matching after applying request of type '{}'.\nExpected digest - {}, actual digest {} (digest version {}). Keeper will " - "terminate to avoid inconsistencies.\nExtra information about the request:\n{}", - request_for_session.request->getOpNum(), - request_for_session.digest->value, - local_nodes_digest.value, - request_for_session.digest->version, - request_for_session.request->toString()); - std::terminate(); - } - + assertDigest(*request_for_session.digest, storage->getNodesDigest(true), *request_for_session.request, true); last_committed_idx = log_idx; return nullptr; } @@ -208,7 +223,7 @@ bool KeeperStateMachine::apply_snapshot(nuraft::snapshot & s) { /// save snapshot into memory std::lock_guard lock(snapshots_lock); if (s.get_last_log_idx() != latest_snapshot_meta->get_last_log_idx()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Required to apply snapshot with last log index {}, but our last log index is {}", + throw Exception(ErrorCodes::INVALID_STATE, "Required to apply snapshot with last log index {}, but our last log index is {}", s.get_last_log_idx(), latest_snapshot_meta->get_last_log_idx()); latest_snapshot_ptr = latest_snapshot_buf; } diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 2d0b10f0d9a..3f07ef067d5 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -1701,6 +1701,9 @@ void KeeperStorage::preprocessRequest( TransactionInfo transaction{.zxid = new_last_zxid}; SCOPE_EXIT({ if (digest_enabled) + // if the version of digest we got from the leader is the same as the one this instances has, we can simply copy the value + // and just check the digest on the commit + // a mistake can happen while applying the changes to the uncommitted_state so for now let's just recalculate the digest here also transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; else transaction.nodes_digest = Digest{DigestVersion::NO_DIGEST}; diff --git a/src/Coordination/tests/gtest_coordination.cpp b/src/Coordination/tests/gtest_coordination.cpp index e518c07a8ff..24fb4a3d96e 100644 --- a/src/Coordination/tests/gtest_coordination.cpp +++ b/src/Coordination/tests/gtest_coordination.cpp @@ -1224,7 +1224,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotBroken) EXPECT_THROW(manager.restoreFromLatestSnapshot(), DB::Exception); } -nuraft::ptr getBufferFromZKRequest(int64_t session_id, const Coordination::ZooKeeperRequestPtr & request) +nuraft::ptr getBufferFromZKRequest(int64_t session_id, int64_t zxid, const Coordination::ZooKeeperRequestPtr & request, const std::optional digest = std::nullopt) { DB::WriteBufferFromNuraftBuffer buf; DB::writeIntBinary(session_id, buf); @@ -1232,12 +1232,18 @@ nuraft::ptr getBufferFromZKRequest(int64_t session_id, const Coo using namespace std::chrono; auto time = duration_cast(system_clock::now().time_since_epoch()).count(); DB::writeIntBinary(time, buf); + DB::writeIntBinary(zxid, buf); + if (digest) + { + DB::writeIntBinary(DB::KeeperStorage::CURRENT_DIGEST_VERSION, buf); + DB::writeIntBinary(*digest, buf); + } return buf.getBuffer(); } -nuraft::ptr getLogEntryFromZKRequest(size_t term, int64_t session_id, const Coordination::ZooKeeperRequestPtr & request) +nuraft::ptr getLogEntryFromZKRequest(size_t term, int64_t session_id, int64_t zxid, const Coordination::ZooKeeperRequestPtr & request, const std::optional digest = std::nullopt) { - auto buffer = getBufferFromZKRequest(session_id, request); + auto buffer = getBufferFromZKRequest(session_id, zxid, request, digest); return nuraft::cs_new(term, buffer); } @@ -1259,7 +1265,7 @@ void testLogAndStateMachine(Coordination::CoordinationSettingsPtr settings, uint { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, 1, request); changelog.append(entry); changelog.end_of_append_batch(0, 0); @@ -1410,7 +1416,7 @@ TEST_P(CoordinationTest, TestEphemeralNodeRemove) std::shared_ptr request_c = std::make_shared(); request_c->path = "/hello"; request_c->is_ephemeral = true; - auto entry_c = getLogEntryFromZKRequest(0, 1, request_c); + auto entry_c = getLogEntryFromZKRequest(0, 1, state_machine->getNextZxid(), request_c); state_machine->pre_commit(1, entry_c->get_buf()); state_machine->commit(1, entry_c->get_buf()); const auto & storage = state_machine->getStorage(); @@ -1419,7 +1425,7 @@ TEST_P(CoordinationTest, TestEphemeralNodeRemove) std::shared_ptr request_d = std::make_shared(); request_d->path = "/hello"; /// Delete from other session - auto entry_d = getLogEntryFromZKRequest(0, 2, request_d); + auto entry_d = getLogEntryFromZKRequest(0, 2, state_machine->getNextZxid(), request_d); state_machine->pre_commit(2, entry_d->get_buf()); state_machine->commit(2, entry_d->get_buf()); @@ -1440,7 +1446,7 @@ TEST_P(CoordinationTest, TestRotateIntervalChanges) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog.append(entry); changelog.end_of_append_batch(0, 0); } @@ -1455,7 +1461,7 @@ TEST_P(CoordinationTest, TestRotateIntervalChanges) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(100 + i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog_1.append(entry); changelog_1.end_of_append_batch(0, 0); } @@ -1470,7 +1476,7 @@ TEST_P(CoordinationTest, TestRotateIntervalChanges) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(200 + i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog_2.append(entry); changelog_2.end_of_append_batch(0, 0); } @@ -1490,7 +1496,7 @@ TEST_P(CoordinationTest, TestRotateIntervalChanges) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(300 + i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog_3.append(entry); changelog_3.end_of_append_batch(0, 0); } @@ -1537,7 +1543,7 @@ TEST_P(CoordinationTest, TestCompressedLogsMultipleRewrite) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog.append(entry); changelog.end_of_append_batch(0, 0); } @@ -1549,7 +1555,7 @@ TEST_P(CoordinationTest, TestCompressedLogsMultipleRewrite) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog1.append(entry); changelog1.end_of_append_batch(0, 0); } @@ -1560,7 +1566,7 @@ TEST_P(CoordinationTest, TestCompressedLogsMultipleRewrite) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog2.append(entry); changelog2.end_of_append_batch(0, 0); } @@ -1769,7 +1775,7 @@ TEST_P(CoordinationTest, TestLogGap) { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(i); - auto entry = getLogEntryFromZKRequest(0, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog.append(entry); changelog.end_of_append_batch(0, 0); } @@ -1907,6 +1913,58 @@ TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) ASSERT_FALSE(get_committed_data()); } +TEST_P(CoordinationTest, TestDigest) +{ + using namespace Coordination; + using namespace DB; + + ChangelogDirTest snapshots1("./snapshots1"); + ChangelogDirTest snapshots2("./snapshots2"); + CoordinationSettingsPtr settings = std::make_shared(); + + ResponsesQueue queue(std::numeric_limits::max()); + SnapshotsQueue snapshots_queue{1}; + const auto test_digest = [&](const auto modify_digest) + { + auto state_machine1 = std::make_shared(queue, snapshots_queue, "./snapshots1", settings); + auto state_machine2 = std::make_shared(queue, snapshots_queue, "./snapshots2", settings); + state_machine1->init(); + state_machine2->init(); + + std::shared_ptr request_c = std::make_shared(); + request_c->path = "/hello"; + auto zxid = state_machine1->getNextZxid(); + auto entry_c = getLogEntryFromZKRequest(0, 1, zxid, request_c); + state_machine1->pre_commit(1, entry_c->get_buf()); + auto correct_digest = state_machine1->getNodesDigest(); + ASSERT_EQ(correct_digest.version, DB::KeeperStorage::CURRENT_DIGEST_VERSION); + entry_c = getLogEntryFromZKRequest(0, 1, zxid, request_c, correct_digest.value); + + if (modify_digest) + { + std::shared_ptr modified_c = std::make_shared(); + modified_c->path = "modified"; + auto modified_entry = getLogEntryFromZKRequest(0, 1, zxid, modified_c, correct_digest.value); + ASSERT_THROW(state_machine2->pre_commit(1, modified_entry->get_buf()), DB::Exception); + } + else + ASSERT_NO_THROW(state_machine2->pre_commit(1, entry_c->get_buf())); + + if (modify_digest) + { + auto new_digest = modify_digest ? correct_digest.value + 1 : correct_digest.value; + auto modified_entry = getLogEntryFromZKRequest(0, 1, zxid, request_c, new_digest); + ASSERT_THROW(state_machine1->commit(1, modified_entry->get_buf()), DB::Exception); + } + else + ASSERT_NO_THROW(state_machine1->commit(1, entry_c->get_buf())); + }; + + test_digest(true); + test_digest(true); + test_digest(false); + test_digest(false); +} INSTANTIATE_TEST_SUITE_P(CoordinationTestSuite, CoordinationTest, From a29286ce0919808feb54afe2904ed3052592cfaa Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 17 May 2022 13:54:44 +0000 Subject: [PATCH 025/204] Remove data from request string --- src/Common/ZooKeeper/ZooKeeperCommon.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Common/ZooKeeper/ZooKeeperCommon.cpp b/src/Common/ZooKeeper/ZooKeeperCommon.cpp index 2c7bf8cc1f0..d25b05ad864 100644 --- a/src/Common/ZooKeeper/ZooKeeperCommon.cpp +++ b/src/Common/ZooKeeper/ZooKeeperCommon.cpp @@ -114,11 +114,9 @@ std::string ZooKeeperAuthRequest::toStringImpl() const { return fmt::format( "type = {}\n" - "scheme = {}\n" - "data = {}", + "scheme = {}", type, - scheme, - data); + scheme); } void ZooKeeperCreateRequest::writeImpl(WriteBuffer & out) const @@ -156,11 +154,9 @@ std::string ZooKeeperCreateRequest::toStringImpl() const { return fmt::format( "path = {}\n" - "data = {}\n" "is_ephemeral = {}\n" "is_sequential = {}", path, - data, is_ephemeral, is_sequential); } @@ -270,10 +266,8 @@ std::string ZooKeeperSetRequest::toStringImpl() const { return fmt::format( "path = {}\n", - "data = {}\n" "version = {}", path, - data, version); } From f5759e2d7e0f4ab227fb3317c256857858090b11 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 19 May 2022 09:45:38 +0000 Subject: [PATCH 026/204] Cache nodes latest state --- programs/keeper-converter/KeeperConverter.cpp | 2 +- src/Common/ZooKeeper/IKeeper.h | 22 +- src/Common/ZooKeeper/ZooKeeperCommon.cpp | 2 +- src/Coordination/KeeperServer.cpp | 29 +- src/Coordination/KeeperSnapshotManager.cpp | 59 ++- src/Coordination/KeeperSnapshotManager.h | 23 +- src/Coordination/KeeperStateMachine.cpp | 190 +++++---- src/Coordination/KeeperStateMachine.h | 37 +- src/Coordination/KeeperStorage.cpp | 400 +++++++++--------- src/Coordination/KeeperStorage.h | 37 +- src/Coordination/tests/gtest_coordination.cpp | 69 +-- 11 files changed, 439 insertions(+), 431 deletions(-) diff --git a/programs/keeper-converter/KeeperConverter.cpp b/programs/keeper-converter/KeeperConverter.cpp index 329bdad557d..dda84a4d2ae 100644 --- a/programs/keeper-converter/KeeperConverter.cpp +++ b/programs/keeper-converter/KeeperConverter.cpp @@ -39,7 +39,7 @@ int mainEntryClickHouseKeeperConverter(int argc, char ** argv) try { - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); DB::deserializeKeeperStorageFromSnapshotsDir(storage, options["zookeeper-snapshots-dir"].as(), logger); DB::deserializeLogsAndApplyToStorage(storage, options["zookeeper-logs-dir"].as(), logger); diff --git a/src/Common/ZooKeeper/IKeeper.h b/src/Common/ZooKeeper/IKeeper.h index 74b45d411b0..b80ac03900a 100644 --- a/src/Common/ZooKeeper/IKeeper.h +++ b/src/Common/ZooKeeper/IKeeper.h @@ -46,17 +46,17 @@ using ACLs = std::vector; struct Stat { - int64_t czxid; - int64_t mzxid; - int64_t ctime; - int64_t mtime; - int32_t version; - int32_t cversion; - int32_t aversion; - int64_t ephemeralOwner; /// NOLINT - int32_t dataLength; /// NOLINT - int32_t numChildren; /// NOLINT - int64_t pzxid; + int64_t czxid{0}; + int64_t mzxid{0}; + int64_t ctime{0}; + int64_t mtime{0}; + int32_t version{0}; + int32_t cversion{0}; + int32_t aversion{0}; + int64_t ephemeralOwner{0}; /// NOLINT + int32_t dataLength{0}; /// NOLINT + int32_t numChildren{0}; /// NOLINT + int64_t pzxid{0}; }; enum class Error : int32_t diff --git a/src/Common/ZooKeeper/ZooKeeperCommon.cpp b/src/Common/ZooKeeper/ZooKeeperCommon.cpp index d25b05ad864..de2fb630848 100644 --- a/src/Common/ZooKeeper/ZooKeeperCommon.cpp +++ b/src/Common/ZooKeeper/ZooKeeperCommon.cpp @@ -265,7 +265,7 @@ void ZooKeeperSetRequest::readImpl(ReadBuffer & in) std::string ZooKeeperSetRequest::toStringImpl() const { return fmt::format( - "path = {}\n", + "path = {}\n" "version = {}", path, version); diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 378cec4f2b0..f4a6e194d30 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -327,6 +327,7 @@ void KeeperServer::startup(const Poco::Util::AbstractConfiguration & config, boo { auto log_entries = log_store->log_entries(state_machine->last_commit_index() + 1, next_log_idx); + LOG_INFO(log, "Preprocessing {} log entries", log_entries->size()); auto idx = state_machine->last_commit_index() + 1; for (const auto & entry : *log_entries) { @@ -528,23 +529,21 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ { switch (type) { - case nuraft::cb_func::PreAppendLogs: + case nuraft::cb_func::PreAppendLog: { - nuraft::req_msg & req = *static_cast(param->ctx); + // we are relying on the fact that request are being processed under a mutex + // and not a RW lock + auto & entry = *static_cast(param->ctx); - for (auto & entry : req.log_entries()) - { - assert(entry->get_val_type() == nuraft::app_log); - auto next_zxid = state_machine->getNextZxid(); - - auto & entry_buf = entry->get_buf(); - auto request_for_session = state_machine->parseRequest(entry_buf); - request_for_session.zxid = next_zxid; - state_machine->preprocess(request_for_session); - request_for_session.digest = state_machine->getNodesDigest(); - - entry = nuraft::cs_new(entry->get_term(), getZooKeeperLogEntry(request_for_session), entry->get_val_type()); - } + assert(entry->get_val_type() == nuraft::app_log); + auto next_zxid = state_machine->getNextZxid(); + + auto & entry_buf = entry->get_buf(); + auto request_for_session = state_machine->parseRequest(entry_buf); + request_for_session.zxid = next_zxid; + state_machine->preprocess(request_for_session); + request_for_session.digest = state_machine->getNodesDigest(); + entry = nuraft::cs_new(entry->get_term(), getZooKeeperLogEntry(request_for_session), entry->get_val_type()); break; } default: diff --git a/src/Coordination/KeeperSnapshotManager.cpp b/src/Coordination/KeeperSnapshotManager.cpp index 47551a9cc3e..f18a0f1260e 100644 --- a/src/Coordination/KeeperSnapshotManager.cpp +++ b/src/Coordination/KeeperSnapshotManager.cpp @@ -1,17 +1,17 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace DB { @@ -151,8 +151,13 @@ void KeeperStorageSnapshot::serialize(const KeeperStorageSnapshot & snapshot, Wr if (snapshot.version >= SnapshotVersion::V5) { writeBinary(snapshot.zxid, out); - writeBinary(static_cast(KeeperStorage::CURRENT_DIGEST_VERSION), out); - writeBinary(snapshot.nodes_digest, out); + if (snapshot.storage->digest_enabled) + { + writeBinary(static_cast(KeeperStorage::CURRENT_DIGEST_VERSION), out); + writeBinary(snapshot.nodes_digest, out); + } + else + writeBinary(static_cast(KeeperStorage::NO_DIGEST), out); } writeBinary(snapshot.session_id, out); @@ -184,7 +189,7 @@ void KeeperStorageSnapshot::serialize(const KeeperStorageSnapshot & snapshot, Wr /// Benign race condition possible while taking snapshot: NuRaft decide to create snapshot at some log id /// and only after some time we lock storage and enable snapshot mode. So snapshot_container_size can be /// slightly bigger than required. - if (static_cast(node.stat.mzxid) > snapshot.snapshot_meta->get_last_log_idx()) + if (node.stat.mzxid > snapshot.zxid) break; writeBinary(path, out); @@ -200,7 +205,8 @@ void KeeperStorageSnapshot::serialize(const KeeperStorageSnapshot & snapshot, Wr /// Session must be saved in a sorted order, /// otherwise snapshots will be different - std::vector> sorted_session_and_timeout(snapshot.session_and_timeout.begin(), snapshot.session_and_timeout.end()); + std::vector> sorted_session_and_timeout( + snapshot.session_and_timeout.begin(), snapshot.session_and_timeout.end()); std::sort(sorted_session_and_timeout.begin(), sorted_session_and_timeout.end()); /// Serialize sessions @@ -300,6 +306,9 @@ void KeeperStorageSnapshot::deserialize(SnapshotDeserializationResult & deserial size_t snapshot_container_size; readBinary(snapshot_container_size, in); + if (recalculate_digest) + storage.nodes_digest = 0; + size_t current_size = 0; while (current_size < snapshot_container_size) { @@ -322,7 +331,8 @@ void KeeperStorageSnapshot::deserialize(SnapshotDeserializationResult & deserial if (itr.key != "/") { auto parent_path = parentPath(itr.key); - storage.container.updateValue(parent_path, [path = itr.key] (KeeperStorage::Node & value) { value.addChild(getBaseName(path)); }); + storage.container.updateValue( + parent_path, [path = itr.key](KeeperStorage::Node & value) { value.addChild(getBaseName(path)); }); } } @@ -388,7 +398,8 @@ KeeperStorageSnapshot::KeeperStorageSnapshot(KeeperStorage * storage_, uint64_t session_and_auth = storage->session_and_auth; } -KeeperStorageSnapshot::KeeperStorageSnapshot(KeeperStorage * storage_, const SnapshotMetadataPtr & snapshot_meta_, const ClusterConfigPtr & cluster_config_) +KeeperStorageSnapshot::KeeperStorageSnapshot( + KeeperStorage * storage_, const SnapshotMetadataPtr & snapshot_meta_, const ClusterConfigPtr & cluster_config_) : storage(storage_) , snapshot_meta(snapshot_meta_) , session_id(storage->session_id_counter) @@ -411,14 +422,18 @@ KeeperStorageSnapshot::~KeeperStorageSnapshot() } KeeperSnapshotManager::KeeperSnapshotManager( - const std::string & snapshots_path_, size_t snapshots_to_keep_, + const std::string & snapshots_path_, + size_t snapshots_to_keep_, bool compress_snapshots_zstd_, - const std::string & superdigest_, size_t storage_tick_time_) + const std::string & superdigest_, + size_t storage_tick_time_, + const bool digest_enabled_) : snapshots_path(snapshots_path_) , snapshots_to_keep(snapshots_to_keep_) , compress_snapshots_zstd(compress_snapshots_zstd_) , superdigest(superdigest_) , storage_tick_time(storage_tick_time_) + , digest_enabled(digest_enabled_) { namespace fs = std::filesystem; @@ -541,7 +556,7 @@ SnapshotDeserializationResult KeeperSnapshotManager::deserializeSnapshotFromBuff compressed_reader = std::make_unique(*reader); SnapshotDeserializationResult result; - result.storage = std::make_unique(storage_tick_time, superdigest); + result.storage = std::make_unique(storage_tick_time, superdigest, digest_enabled); KeeperStorageSnapshot::deserialize(result, *compressed_reader); return result; } @@ -580,7 +595,7 @@ std::pair KeeperSnapshotManager::serializeSnapshot std::string tmp_snapshot_path = std::filesystem::path{snapshots_path} / tmp_snapshot_file_name; std::string new_snapshot_path = std::filesystem::path{snapshots_path} / snapshot_file_name; - auto writer = std::make_unique(tmp_snapshot_path, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC| O_APPEND); + auto writer = std::make_unique(tmp_snapshot_path, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_APPEND); std::unique_ptr compressed_writer; if (compress_snapshots_zstd) compressed_writer = wrapWriteBufferWithCompressionMethod(std::move(writer), CompressionMethod::Zstd, 3); diff --git a/src/Coordination/KeeperSnapshotManager.h b/src/Coordination/KeeperSnapshotManager.h index d85be5334ef..57174cbc6f5 100644 --- a/src/Coordination/KeeperSnapshotManager.h +++ b/src/Coordination/KeeperSnapshotManager.h @@ -1,10 +1,10 @@ #pragma once #include #include -#include #include -#include #include +#include +#include namespace DB { @@ -24,7 +24,7 @@ enum SnapshotVersion : uint8_t V5 = 5, /// add ZXID and digest to snapshots }; -static constexpr auto CURRENT_SNAPSHOT_VERSION = SnapshotVersion::V4; +static constexpr auto CURRENT_SNAPSHOT_VERSION = SnapshotVersion::V5; /// What is stored in binary shapsnot struct SnapshotDeserializationResult @@ -50,7 +50,8 @@ struct KeeperStorageSnapshot public: KeeperStorageSnapshot(KeeperStorage * storage_, uint64_t up_to_log_idx_, const ClusterConfigPtr & cluster_config_ = nullptr); - KeeperStorageSnapshot(KeeperStorage * storage_, const SnapshotMetadataPtr & snapshot_meta_, const ClusterConfigPtr & cluster_config_ = nullptr); + KeeperStorageSnapshot( + KeeperStorage * storage_, const SnapshotMetadataPtr & snapshot_meta_, const ClusterConfigPtr & cluster_config_ = nullptr); ~KeeperStorageSnapshot(); @@ -96,8 +97,12 @@ class KeeperSnapshotManager { public: KeeperSnapshotManager( - const std::string & snapshots_path_, size_t snapshots_to_keep_, - bool compress_snapshots_zstd_ = true, const std::string & superdigest_ = "", size_t storage_tick_time_ = 500); + const std::string & snapshots_path_, + size_t snapshots_to_keep_, + bool compress_snapshots_zstd_ = true, + const std::string & superdigest_ = "", + size_t storage_tick_time_ = 500, + bool digest_enabled_ = true); /// Restore storage from latest available snapshot SnapshotDeserializationResult restoreFromLatestSnapshot(); @@ -123,10 +128,7 @@ public: void removeSnapshot(uint64_t log_idx); /// Total amount of snapshots - size_t totalSnapshots() const - { - return existing_snapshots.size(); - } + size_t totalSnapshots() const { return existing_snapshots.size(); } /// The most fresh snapshot log index we have size_t getLatestSnapshotIndex() const @@ -166,6 +168,7 @@ private: const std::string superdigest; /// Storage sessions timeout check interval (also for deserializatopn) size_t storage_tick_time; + const bool digest_enabled; }; /// Keeper create snapshots in background thread. KeeperStateMachine just create diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index 25e497e32a3..80b93e0d402 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -1,14 +1,14 @@ -#include #include +#include +#include #include #include #include #include +#include #include "Common/ZooKeeper/ZooKeeperCommon.h" #include #include "Coordination/KeeperStorage.h" -#include -#include namespace DB { @@ -24,17 +24,20 @@ namespace } KeeperStateMachine::KeeperStateMachine( - ResponsesQueue & responses_queue_, - SnapshotsQueue & snapshots_queue_, - const std::string & snapshots_path_, - const CoordinationSettingsPtr & coordination_settings_, - const std::string & superdigest_, - const bool digest_enabled_) + ResponsesQueue & responses_queue_, + SnapshotsQueue & snapshots_queue_, + const std::string & snapshots_path_, + const CoordinationSettingsPtr & coordination_settings_, + const std::string & superdigest_, + const bool digest_enabled_) : coordination_settings(coordination_settings_) , snapshot_manager( - snapshots_path_, coordination_settings->snapshots_to_keep, - coordination_settings->compress_snapshots_with_zstd_format, superdigest_, - coordination_settings->dead_session_check_period_ms.totalMicroseconds()) + snapshots_path_, + coordination_settings->snapshots_to_keep, + coordination_settings->compress_snapshots_with_zstd_format, + superdigest_, + coordination_settings->dead_session_check_period_ms.totalMicroseconds(), + digest_enabled_) , responses_queue(responses_queue_) , snapshots_queue(snapshots_queue_) , last_committed_idx(0) @@ -58,7 +61,8 @@ void KeeperStateMachine::init() try { - auto snapshot_deserialization_result = snapshot_manager.deserializeSnapshotFromBuffer(snapshot_manager.deserializeSnapshotBufferFromDisk(latest_log_index)); + auto snapshot_deserialization_result + = snapshot_manager.deserializeSnapshotFromBuffer(snapshot_manager.deserializeSnapshotBufferFromDisk(latest_log_index)); latest_snapshot_path = snapshot_manager.getLatestSnapshotPath(); storage = std::move(snapshot_deserialization_result.storage); latest_snapshot_meta = snapshot_deserialization_result.snapshot_meta; @@ -69,7 +73,11 @@ void KeeperStateMachine::init() } catch (const DB::Exception & ex) { - LOG_WARNING(log, "Failed to load from snapshot with index {}, with error {}, will remove it from disk", latest_log_index, ex.displayText()); + LOG_WARNING( + log, + "Failed to load from snapshot with index {}, with error {}, will remove it from disk", + latest_log_index, + ex.displayText()); snapshot_manager.removeSnapshot(latest_log_index); } } @@ -87,7 +95,36 @@ void KeeperStateMachine::init() } if (!storage) - storage = std::make_unique(coordination_settings->dead_session_check_period_ms.totalMilliseconds(), superdigest, digest_enabled); + storage = std::make_unique( + coordination_settings->dead_session_check_period_ms.totalMilliseconds(), superdigest, digest_enabled); +} + +namespace +{ + +void assertDigest( + const KeeperStorage::Digest & first, + const KeeperStorage::Digest & second, + const Coordination::ZooKeeperRequest & request, + bool committing) +{ + if (!KeeperStorage::checkDigest(first, second)) + { + LOG_FATAL( + &Poco::Logger::get("KeeperStateMachine"), + "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest {} (digest version " + "{}). Keeper will " + "terminate to avoid inconsistencies.\nExtra information about the request:\n{}", + committing ? "committing" : "preprocessing", + request.getOpNum(), + first.value, + second.value, + first.version, + request.toString()); + std::terminate(); + } +} + } nuraft::ptr KeeperStateMachine::pre_commit(uint64_t log_idx, nuraft::buffer & data) @@ -123,7 +160,8 @@ KeeperStorage::RequestForSession KeeperStateMachine::parseRequest(nuraft::buffer if (!buffer.eof()) readIntBinary(request_for_session.time, buffer); else /// backward compatibility - request_for_session.time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + request_for_session.time + = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if (!buffer.eof()) readIntBinary(request_for_session.zxid, buffer); @@ -139,37 +177,19 @@ KeeperStorage::RequestForSession KeeperStateMachine::parseRequest(nuraft::buffer return request_for_session; } -namespace -{ - -void assertDigest(const KeeperStorage::Digest & first, const KeeperStorage::Digest & second, const Coordination::ZooKeeperRequest & request, bool committing) -{ - if (!KeeperStorage::checkDigest(first, second)) - { - throw DB::Exception( - DB::ErrorCodes::INVALID_STATE, - "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest {} (digest version {}). Keeper will " - "terminate to avoid inconsistencies.\nExtra information about the request:\n{}", - committing ? "committing" : "preprocessing", - request.getOpNum(), - first.value, - second.value, - first.version, - request.toString()); - } -} - -} - void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & request_for_session) { if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) return; - { - std::lock_guard lock(storage_and_responses_lock); - storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid, true /* check_acl */, request_for_session.digest); - } + std::lock_guard lock(storage_and_responses_lock); + storage->preprocessRequest( + request_for_session.request, + request_for_session.session_id, + request_for_session.time, + request_for_session.zxid, + true /* check_acl */, + request_for_session.digest); if (digest_enabled && request_for_session.digest) assertDigest(*request_for_session.digest, storage->getNodesDigest(false), *request_for_session.request, false); } @@ -183,7 +203,8 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n /// Special processing of session_id request if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) { - const Coordination::ZooKeeperSessionIDRequest & session_id_request = dynamic_cast(*request_for_session.request); + const Coordination::ZooKeeperSessionIDRequest & session_id_request + = dynamic_cast(*request_for_session.request); int64_t session_id; std::shared_ptr response = std::make_shared(); response->internal_id = session_id_request.internal_id; @@ -203,15 +224,22 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n else { std::lock_guard lock(storage_and_responses_lock); - KeeperStorage::ResponsesForSessions responses_for_sessions = storage->processRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid); + KeeperStorage::ResponsesForSessions responses_for_sessions = storage->processRequest( + request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid); for (auto & response_for_session : responses_for_sessions) if (!responses_queue.push(response_for_session)) - throw Exception(ErrorCodes::SYSTEM_ERROR, "Could not push response with session id {} into responses queue", response_for_session.session_id); + throw Exception( + ErrorCodes::SYSTEM_ERROR, + "Could not push response with session id {} into responses queue", + response_for_session.session_id); + + if (digest_enabled) + { + assert(request_for_session.digest); + assertDigest(*request_for_session.digest, storage->getNodesDigest(true), *request_for_session.request, true); + } } - - assert(request_for_session.digest); - assertDigest(*request_for_session.digest, storage->getNodesDigest(true), *request_for_session.request, true); last_committed_idx = log_idx; return nullptr; } @@ -223,14 +251,18 @@ bool KeeperStateMachine::apply_snapshot(nuraft::snapshot & s) { /// save snapshot into memory std::lock_guard lock(snapshots_lock); if (s.get_last_log_idx() != latest_snapshot_meta->get_last_log_idx()) - throw Exception(ErrorCodes::INVALID_STATE, "Required to apply snapshot with last log index {}, but our last log index is {}", - s.get_last_log_idx(), latest_snapshot_meta->get_last_log_idx()); + throw Exception( + ErrorCodes::INVALID_STATE, + "Required to apply snapshot with last log index {}, but our last log index is {}", + s.get_last_log_idx(), + latest_snapshot_meta->get_last_log_idx()); latest_snapshot_ptr = latest_snapshot_buf; } { /// deserialize and apply snapshot to storage std::lock_guard lock(storage_and_responses_lock); - auto snapshot_deserialization_result = snapshot_manager.deserializeSnapshotFromBuffer(snapshot_manager.deserializeSnapshotBufferFromDisk(s.get_last_log_idx())); + auto snapshot_deserialization_result + = snapshot_manager.deserializeSnapshotFromBuffer(snapshot_manager.deserializeSnapshotBufferFromDisk(s.get_last_log_idx())); storage = std::move(snapshot_deserialization_result.storage); latest_snapshot_meta = snapshot_deserialization_result.snapshot_meta; cluster_config = snapshot_deserialization_result.cluster_config; @@ -248,10 +280,14 @@ void KeeperStateMachine::commit_config(const uint64_t /* log_idx */, nuraft::ptr cluster_config = ClusterConfig::deserialize(*tmp); } -void KeeperStateMachine::rollback(uint64_t log_idx, nuraft::buffer & /*data*/) +void KeeperStateMachine::rollback(uint64_t log_idx, nuraft::buffer & data) { + auto request_for_session = parseRequest(data); + if (!request_for_session.zxid) + request_for_session.zxid = log_idx; + std::lock_guard lock(storage_and_responses_lock); - storage->rollbackRequest(log_idx); + storage->rollbackRequest(request_for_session.zxid); } nuraft::ptr KeeperStateMachine::last_snapshot() @@ -261,9 +297,7 @@ nuraft::ptr KeeperStateMachine::last_snapshot() return latest_snapshot_meta; } -void KeeperStateMachine::create_snapshot( - nuraft::snapshot & s, - nuraft::async_result::handler_type & when_done) +void KeeperStateMachine::create_snapshot(nuraft::snapshot & s, nuraft::async_result::handler_type & when_done) { LOG_DEBUG(log, "Creating snapshot {}", s.get_last_log_idx()); @@ -276,19 +310,22 @@ void KeeperStateMachine::create_snapshot( } /// create snapshot task for background execution (in snapshot thread) - snapshot_task.create_snapshot = [this, when_done] (KeeperStorageSnapshotPtr && snapshot) + snapshot_task.create_snapshot = [this, when_done](KeeperStorageSnapshotPtr && snapshot) { nuraft::ptr exception(nullptr); bool ret = true; try { - { /// Read storage data without locks and create snapshot + { /// Read storage data without locks and create snapshot std::lock_guard lock(snapshots_lock); - auto [path, error_code]= snapshot_manager.serializeSnapshotToDisk(*snapshot); + auto [path, error_code] = snapshot_manager.serializeSnapshotToDisk(*snapshot); if (error_code) { - throw Exception(ErrorCodes::SYSTEM_ERROR, "Snapshot {} was created failed, error: {}", - snapshot->snapshot_meta->get_last_log_idx(), error_code.message()); + throw Exception( + ErrorCodes::SYSTEM_ERROR, + "Snapshot {} was created failed, error: {}", + snapshot->snapshot_meta->get_last_log_idx(), + error_code.message()); } latest_snapshot_path = path; latest_snapshot_meta = snapshot->snapshot_meta; @@ -323,11 +360,7 @@ void KeeperStateMachine::create_snapshot( } void KeeperStateMachine::save_logical_snp_obj( - nuraft::snapshot & s, - uint64_t & obj_id, - nuraft::buffer & data, - bool /*is_first_obj*/, - bool /*is_last_obj*/) + nuraft::snapshot & s, uint64_t & obj_id, nuraft::buffer & data, bool /*is_first_obj*/, bool /*is_last_obj*/) { LOG_DEBUG(log, "Saving snapshot {} obj_id {}", s.get_last_log_idx(), obj_id); @@ -383,13 +416,8 @@ static int bufferFromFile(Poco::Logger * log, const std::string & path, nuraft:: } int KeeperStateMachine::read_logical_snp_obj( - nuraft::snapshot & s, - void* & /*user_snp_ctx*/, - uint64_t obj_id, - nuraft::ptr & data_out, - bool & is_last_obj) + nuraft::snapshot & s, void *& /*user_snp_ctx*/, uint64_t obj_id, nuraft::ptr & data_out, bool & is_last_obj) { - LOG_DEBUG(log, "Reading snapshot {} obj_id {}", s.get_last_log_idx(), obj_id); std::lock_guard lock(snapshots_lock); @@ -397,8 +425,11 @@ int KeeperStateMachine::read_logical_snp_obj( /// Let's wait and NuRaft will retry this call. if (s.get_last_log_idx() != latest_snapshot_meta->get_last_log_idx()) { - LOG_WARNING(log, "Required to apply snapshot with last log index {}, but our last log index is {}. Will ignore this one and retry", - s.get_last_log_idx(), latest_snapshot_meta->get_last_log_idx()); + LOG_WARNING( + log, + "Required to apply snapshot with last log index {}, but our last log index is {}. Will ignore this one and retry", + s.get_last_log_idx(), + latest_snapshot_meta->get_last_log_idx()); return -1; } if (bufferFromFile(log, latest_snapshot_path, data_out)) @@ -415,10 +446,17 @@ void KeeperStateMachine::processReadRequest(const KeeperStorage::RequestForSessi { /// Pure local request, just process it with storage std::lock_guard lock(storage_and_responses_lock); - auto responses = storage->processRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, std::nullopt, true /*check_acl*/, true /*is_local*/); + auto responses = storage->processRequest( + request_for_session.request, + request_for_session.session_id, + request_for_session.time, + std::nullopt, + true /*check_acl*/, + true /*is_local*/); for (const auto & response : responses) if (!responses_queue.push(response)) - throw Exception(ErrorCodes::SYSTEM_ERROR, "Could not push response with session id {} into responses queue", response.session_id); + throw Exception( + ErrorCodes::SYSTEM_ERROR, "Could not push response with session id {} into responses queue", response.session_id); } void KeeperStateMachine::shutdownStorage() diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index 66eff3b62f9..c80b35bb704 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -1,11 +1,11 @@ #pragma once -#include -#include -#include -#include #include #include +#include +#include +#include +#include namespace DB @@ -20,8 +20,10 @@ class KeeperStateMachine : public nuraft::state_machine { public: KeeperStateMachine( - ResponsesQueue & responses_queue_, SnapshotsQueue & snapshots_queue_, - const std::string & snapshots_path_, const CoordinationSettingsPtr & coordination_settings_, + ResponsesQueue & responses_queue_, + SnapshotsQueue & snapshots_queue_, + const std::string & snapshots_path_, + const CoordinationSettingsPtr & coordination_settings_, const std::string & superdigest_ = "", bool digest_enabled_ = true); @@ -49,32 +51,18 @@ public: nuraft::ptr last_snapshot() override; /// Create new snapshot from current state. - void create_snapshot( - nuraft::snapshot & s, - nuraft::async_result::handler_type & when_done) override; + void create_snapshot(nuraft::snapshot & s, nuraft::async_result::handler_type & when_done) override; /// Save snapshot which was send by leader to us. After that we will apply it in apply_snapshot. - void save_logical_snp_obj( - nuraft::snapshot & s, - uint64_t & obj_id, - nuraft::buffer & data, - bool is_first_obj, - bool is_last_obj) override; + void save_logical_snp_obj(nuraft::snapshot & s, uint64_t & obj_id, nuraft::buffer & data, bool is_first_obj, bool is_last_obj) override; /// Better name is `serialize snapshot` -- save existing snapshot (created by create_snapshot) into /// in-memory buffer data_out. int read_logical_snp_obj( - nuraft::snapshot & s, - void* & user_snp_ctx, - uint64_t obj_id, - nuraft::ptr & data_out, - bool & is_last_obj) override; + nuraft::snapshot & s, void *& user_snp_ctx, uint64_t obj_id, nuraft::ptr & data_out, bool & is_last_obj) override; /// just for test - KeeperStorage & getStorage() - { - return *storage; - } + KeeperStorage & getStorage() { return *storage; } void shutdownStorage(); @@ -108,7 +96,6 @@ public: uint64_t getLatestSnapshotBufSize() const; private: - /// In our state machine we always have a single snapshot which is stored /// in memory in compressed (serialized) format. SnapshotMetadataPtr latest_snapshot_meta = nullptr; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 3f07ef067d5..db0961ec896 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -214,15 +215,20 @@ UInt64 KeeperStorage::Node::getDigest(const std::string_view path) const return *cached_digest; }; -void KeeperStorage::Node::setDigest(UInt64 digest) +void KeeperStorage::Node::shallowCopy(const KeeperStorage::Node & other) { - cached_digest.emplace(digest); + stat = other.stat; + seq_num = other.seq_num; + setData(other.getData()); + cached_digest = other.cached_digest; } KeeperStorage::KeeperStorage(int64_t tick_time_ms, const String & superdigest_, const bool digest_enabled_) : session_expiry_queue(tick_time_ms), digest_enabled(digest_enabled_), superdigest(superdigest_) { - container.insert("/", Node()); + Node root_node; + container.insert("/", root_node); + nodes_digest += root_node.getDigest("/"); } template @@ -236,115 +242,161 @@ struct Overloaded : Ts... template Overloaded(Ts...) -> Overloaded; -std::shared_ptr KeeperStorage::UncommittedState::getNode(StringRef path, std::optional current_zxid) const +std::shared_ptr KeeperStorage::UncommittedState::getNodeFromStorage(StringRef path) const { - std::shared_ptr node{nullptr}; - - if (auto maybe_node_it = storage.container.find(path); maybe_node_it != storage.container.end()) + if (auto node_it = storage.container.find(path); node_it != storage.container.end()) { - const auto & committed_node = maybe_node_it->value; - node = std::make_shared(); - node->stat = committed_node.stat; - node->seq_num = committed_node.seq_num; - node->setData(committed_node.getData()); - node->setDigest(committed_node.getDigest(path.toView())); + const auto & committed_node = node_it->value; + auto node = std::make_shared(); + node->shallowCopy(committed_node); + return node; } - applyDeltas( - path, - Overloaded{ - [&](const CreateNodeDelta & create_delta) + return nullptr; +} + +void KeeperStorage::UncommittedState::applyDelta(const Delta & delta) +{ + assert(!delta.path.empty()); + if (!nodes.contains(delta.path)) + { + if (auto storage_node = getNodeFromStorage(delta.path)) + nodes.emplace(delta.path, UncommittedNode{.node = std::move(storage_node)}); + else + nodes.emplace(delta.path, UncommittedNode{.node = nullptr}); + } + + auto & [node, acls, last_applied_zxid] = nodes.at(delta.path); + + std::visit( + [&, &node = node, &acls = acls, &last_applied_zxid = last_applied_zxid](const DeltaType & operation) + { + if constexpr (std::same_as) { assert(!node); node = std::make_shared(); - node->stat = create_delta.stat; - node->setData(create_delta.data); - }, - [&](const RemoveNodeDelta & /*remove_delta*/) + node->stat = operation.stat; + node->setData(operation.data); + acls = operation.acls; + last_applied_zxid = delta.zxid; + } + else if constexpr (std::same_as) { assert(node); node = nullptr; - }, - [&](const UpdateNodeDelta & update_delta) + last_applied_zxid = delta.zxid; + } + else if constexpr (std::same_as) { assert(node); node->invalidateDigestCache(); - update_delta.update_fn(*node); - }, - [&](auto && /*delta*/) {}, + operation.update_fn(*node); + last_applied_zxid = delta.zxid; + } + else if constexpr (std::same_as) + { + acls = operation.acls; + last_applied_zxid = delta.zxid; + } }, - current_zxid); - - return node; + delta.operation); } -bool KeeperStorage::UncommittedState::hasNode(StringRef path) const +void KeeperStorage::UncommittedState::addDeltas(std::vector new_deltas) { - bool exists = storage.container.contains(std::string{path}); - applyDeltas( - path, - Overloaded{ - [&](const CreateNodeDelta & /*create_delta*/) + for (auto & delta : new_deltas) + { + if (!delta.path.empty()) + applyDelta(delta); + + deltas.push_back(std::move(delta)); + } +} + +void KeeperStorage::UncommittedState::commit(int64_t commit_zxid) +{ + assert(deltas.empty() || deltas.front().zxid >= commit_zxid); + + while (!deltas.empty() && deltas.front().zxid == commit_zxid) + { + if (std::holds_alternative(deltas.front().operation)) + { + deltas.pop_front(); + break; + } + + deltas.pop_front(); + } + + // delete all cached nodes that were not modifed after the commit_zxid + std::erase_if(nodes, [commit_zxid](const auto & node) { return node.second.zxid == commit_zxid; }); +} + +void KeeperStorage::UncommittedState::rollback(int64_t rollback_zxid) +{ + // we can only rollback the last zxid (if there is any) + // if there is a delta with a larger zxid, we have invalid state + const auto last_zxid = deltas.back().zxid; + if (!deltas.empty() && last_zxid > rollback_zxid) + throw DB::Exception{ + DB::ErrorCodes::LOGICAL_ERROR, + "Invalid state of deltas found while trying to rollback request. Last ZXID ({}) is larger than the requested ZXID ({})", + last_zxid, + rollback_zxid}; + + std::erase_if(deltas, [rollback_zxid](const auto & delta) { return delta.zxid == rollback_zxid; }); + + std::unordered_set deleted_nodes; + std::erase_if( + nodes, + [&, rollback_zxid](const auto & node) + { + if (node.second.zxid == rollback_zxid) { - assert(!exists); - exists = true; - }, - [&](const RemoveNodeDelta & /*remove_delta*/) - { - assert(exists); - exists = false; - }, - [&](auto && /*delta*/) {}, + deleted_nodes.emplace(node.first); + return true; + } + return false; }); - return exists; + // recalculate all the uncommitted deleted nodes + for (const auto & delta : deltas) + { + if (!delta.path.empty() && deleted_nodes.contains(delta.path)) + applyDelta(delta); + } +} + +std::shared_ptr KeeperStorage::UncommittedState::getNode(StringRef path) const +{ + if (auto node_it = nodes.find(std::string{path}); node_it != nodes.end()) + return node_it->second.node; + + return getNodeFromStorage(path); } Coordination::ACLs KeeperStorage::UncommittedState::getACLs(StringRef path) const { - std::optional acl_id; - if (auto maybe_node_it = storage.container.find(path); maybe_node_it != storage.container.end()) - acl_id.emplace(maybe_node_it->value.acl_id); + if (auto node_it = nodes.find(std::string{path}); node_it != nodes.end()) + return node_it->second.acls; - const Coordination::ACLs * acls{nullptr}; - applyDeltas( - path, - Overloaded{ - [&](const CreateNodeDelta & create_delta) - { - assert(!acl_id); - acls = &create_delta.acls; - }, - [&](const RemoveNodeDelta & /*remove_delta*/) - { - assert(acl_id || acls); - acl_id.reset(); - acls = nullptr; - }, - [&](const SetACLDelta & set_acl_delta) - { - assert(acl_id || acls); - acls = &set_acl_delta.acls; - }, - [&](auto && /*delta*/) {}, - }); + auto node_it = storage.container.find(path); + if (node_it == storage.container.end()) + return {}; - if (acls) - return *acls; - - return acl_id ? storage.acl_map.convertNumber(*acl_id) : Coordination::ACLs{}; + return storage.acl_map.convertNumber(node_it->value.acl_id); } namespace { -[[noreturn]] void onStorageInconsistency() -{ - LOG_ERROR( - &Poco::Logger::get("KeeperStorage"), - "Inconsistency found between uncommitted and committed data. Keeper will terminate to avoid undefined behaviour."); - std::terminate(); -} + [[noreturn]] void onStorageInconsistency() + { + LOG_ERROR( + &Poco::Logger::get("KeeperStorage"), + "Inconsistency found between uncommitted and committed data. Keeper will terminate to avoid undefined behaviour."); + std::terminate(); + } } @@ -525,7 +577,7 @@ struct KeeperStorageRequestProcessor explicit KeeperStorageRequestProcessor(const Coordination::ZooKeeperRequestPtr & zk_request_) : zk_request(zk_request_) { } virtual Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const = 0; virtual std::vector - preprocess(KeeperStorage & /*storage*/, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /*time*/) const + preprocess(KeeperStorage & /*storage*/, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /*time*/, uint64_t & /*digest*/) const { return {}; } @@ -597,7 +649,6 @@ bool KeeperStorage::checkACL(StringRef path, int32_t permission, int64_t session if (uncommitted_state.hasACL(session_id, is_local, [](const auto & auth_id) { return auth_id.scheme == "super"; })) return true; - for (const auto & node_acl : node_acls) { if (node_acl.permissions & permission) @@ -633,7 +684,8 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr return storage.checkACL(parentPath(path), Coordination::ACL::Create, session_id, is_local); } - std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time, uint64_t & digest) const override { Coordination::ZooKeeperCreateRequest & request = dynamic_cast(*zk_request); @@ -659,7 +711,7 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr path_created += seq_num_str.str(); } - if (storage.uncommitted_state.hasNode(path_created)) + if (storage.uncommitted_state.getNode(path_created)) return {{zxid, Coordination::Error::ZNODEEXISTS}}; if (getBaseName(path_created).size == 0) @@ -704,6 +756,8 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr node.stat.pzxid = zxid; ++node.stat.numChildren; }}); + + digest = storage.calculateNodesDigest(digest, new_deltas); return new_deltas; } @@ -743,11 +797,11 @@ struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProce using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; std::vector - preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/, uint64_t & /*digest*/) const override { Coordination::ZooKeeperGetRequest & request = dynamic_cast(*zk_request); - if (!storage.uncommitted_state.hasNode(request.path)) + if (!storage.uncommitted_state.getNode(request.path)) return {{zxid, Coordination::Error::ZNONODE}}; return {}; @@ -809,7 +863,7 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; std::vector - preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/, uint64_t & digest) const override { Coordination::ZooKeeperRemoveRequest & request = dynamic_cast(*zk_request); @@ -818,7 +872,7 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr const auto update_parent_pzxid = [&]() { auto parent_path = parentPath(request.path); - if (!storage.uncommitted_state.hasNode(parent_path)) + if (!storage.uncommitted_state.getNode(parent_path)) return; new_deltas.emplace_back( @@ -858,6 +912,8 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr new_deltas.emplace_back(request.path, zxid, KeeperStorage::RemoveNodeDelta{request.version}); + digest = storage.calculateNodesDigest(digest, new_deltas); + return new_deltas; } @@ -882,11 +938,11 @@ struct KeeperStorageExistsRequestProcessor final : public KeeperStorageRequestPr using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; std::vector - preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/, uint64_t & /*digest*/) const override { Coordination::ZooKeeperExistsRequest & request = dynamic_cast(*zk_request); - if (!storage.uncommitted_state.hasNode(request.path)) + if (!storage.uncommitted_state.getNode(request.path)) return {{zxid, Coordination::Error::ZNONODE}}; return {}; @@ -945,13 +1001,14 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce } using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t time) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t time, uint64_t & digest) const override { Coordination::ZooKeeperSetRequest & request = dynamic_cast(*zk_request); std::vector new_deltas; - if (!storage.uncommitted_state.hasNode(request.path)) + if (!storage.uncommitted_state.getNode(request.path)) return {{zxid, Coordination::Error::ZNONODE}}; auto node = storage.uncommitted_state.getNode(request.path); @@ -977,6 +1034,7 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce parent.stat.cversion++; }}); + digest = storage.calculateNodesDigest(digest, new_deltas); return new_deltas; } @@ -1020,11 +1078,11 @@ struct KeeperStorageListRequestProcessor final : public KeeperStorageRequestProc using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; std::vector - preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/, uint64_t & /*digest*/) const override { Coordination::ZooKeeperListRequest & request = dynamic_cast(*zk_request); - if (!storage.uncommitted_state.hasNode(request.path)) + if (!storage.uncommitted_state.getNode(request.path)) return {{zxid, Coordination::Error::ZNONODE}}; return {}; @@ -1095,11 +1153,11 @@ struct KeeperStorageCheckRequestProcessor final : public KeeperStorageRequestPro using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; std::vector - preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/, uint64_t & /*digest*/) const override { Coordination::ZooKeeperCheckRequest & request = dynamic_cast(*zk_request); - if (!storage.uncommitted_state.hasNode(request.path)) + if (!storage.uncommitted_state.getNode(request.path)) return {{zxid, Coordination::Error::ZNONODE}}; auto node = storage.uncommitted_state.getNode(request.path); @@ -1172,12 +1230,13 @@ struct KeeperStorageSetACLRequestProcessor final : public KeeperStorageRequestPr using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/, uint64_t & digest) const override { Coordination::ZooKeeperSetACLRequest & request = dynamic_cast(*zk_request); auto & uncommitted_state = storage.uncommitted_state; - if (!uncommitted_state.hasNode(request.path)) + if (!uncommitted_state.getNode(request.path)) return {{zxid, Coordination::Error::ZNONODE}}; auto node = uncommitted_state.getNode(request.path); @@ -1192,22 +1251,13 @@ struct KeeperStorageSetACLRequestProcessor final : public KeeperStorageRequestPr if (!fixupACL(request.acls, session_auth_ids, node_acls)) return {{zxid, Coordination::Error::ZINVALIDACL}}; - return - { - { - request.path, - zxid, - KeeperStorage::SetACLDelta{std::move(node_acls), request.version} - }, - { - request.path, - zxid, - KeeperStorage::UpdateNodeDelta - { - [](KeeperStorage::Node & n) { ++n.stat.aversion; } - } - } - }; + std::vector new_deltas + = {{request.path, zxid, KeeperStorage::SetACLDelta{std::move(node_acls), request.version}}, + {request.path, zxid, KeeperStorage::UpdateNodeDelta{[](KeeperStorage::Node & n) { ++n.stat.aversion; }}}}; + + digest = storage.calculateNodesDigest(digest, new_deltas); + + return new_deltas; } Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const override @@ -1242,11 +1292,11 @@ struct KeeperStorageGetACLRequestProcessor final : public KeeperStorageRequestPr using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; std::vector - preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/, uint64_t & /*digest*/) const override { Coordination::ZooKeeperGetACLRequest & request = dynamic_cast(*zk_request); - if (!storage.uncommitted_state.hasNode(request.path)) + if (!storage.uncommitted_state.getNode(request.path)) return {{zxid, Coordination::Error::ZNONODE}}; return {}; @@ -1338,23 +1388,21 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro } } - std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time, uint64_t & digest) const override { - // manually add deltas so that the result of previous request in the transaction is used in the next request - auto & saved_deltas = storage.uncommitted_state.deltas; - std::vector response_errors; response_errors.reserve(concrete_requests.size()); + uint64_t current_digest = digest; for (size_t i = 0; i < concrete_requests.size(); ++i) { - auto new_deltas = concrete_requests[i]->preprocess(storage, zxid, session_id, time); + auto new_deltas = concrete_requests[i]->preprocess(storage, zxid, session_id, time, current_digest); if (!new_deltas.empty()) { if (auto * error = std::get_if(&new_deltas.back().operation)) { - std::erase_if(saved_deltas, [zxid](const auto & delta) { return delta.zxid == zxid; }); - + storage.uncommitted_state.rollback(zxid); response_errors.push_back(error->error); for (size_t j = i + 1; j < concrete_requests.size(); ++j) @@ -1368,9 +1416,12 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro new_deltas.emplace_back(zxid, KeeperStorage::SubDeltaEnd{}); response_errors.push_back(Coordination::Error::ZOK); - saved_deltas.insert(saved_deltas.end(), std::make_move_iterator(new_deltas.begin()), std::make_move_iterator(new_deltas.end())); + // manually add deltas so that the result of previous request in the transaction is used in the next request + storage.uncommitted_state.addDeltas(std::move(new_deltas)); } + digest = current_digest; + return {}; } @@ -1390,25 +1441,15 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro response.responses[i]->error = failed_multi->error_codes[i]; } + storage.uncommitted_state.commit(zxid); return response_ptr; } for (size_t i = 0; i < concrete_requests.size(); ++i) { auto cur_response = concrete_requests[i]->process(storage, zxid, session_id, time); - - while (!deltas.empty()) - { - if (std::holds_alternative(deltas.front().operation)) - { - deltas.pop_front(); - break; - } - - deltas.pop_front(); - } - response.responses[i] = cur_response; + storage.uncommitted_state.commit(zxid); } response.error = Coordination::Error::ZOK; @@ -1473,7 +1514,8 @@ struct KeeperStorageCloseRequestProcessor final : public KeeperStorageRequestPro struct KeeperStorageAuthRequestProcessor final : public KeeperStorageRequestProcessor { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/, uint64_t & /*digest*/) const override { Coordination::ZooKeeperAuthRequest & auth_request = dynamic_cast(*zk_request); Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); @@ -1482,15 +1524,15 @@ struct KeeperStorageAuthRequestProcessor final : public KeeperStorageRequestProc return {{zxid, Coordination::Error::ZAUTHFAILED}}; std::vector new_deltas; - auto digest = generateDigest(auth_request.data); - if (digest == storage.superdigest) + auto auth_digest = generateDigest(auth_request.data); + if (auth_digest == storage.superdigest) { KeeperStorage::AuthID auth{"super", ""}; new_deltas.emplace_back(zxid, KeeperStorage::AddAuthDelta{session_id, std::move(auth)}); } else { - KeeperStorage::AuthID new_auth{auth_request.scheme, digest}; + KeeperStorage::AuthID new_auth{auth_request.scheme, auth_digest}; if (!storage.uncommitted_state.hasACL(session_id, false, [&](const auto & auth_id) { return new_auth == auth_id; })) new_deltas.emplace_back(zxid, KeeperStorage::AddAuthDelta{session_id, std::move(new_auth)}); } @@ -1590,15 +1632,15 @@ KeeperStorageRequestProcessorsFactory::KeeperStorageRequestProcessorsFactory() } -UInt64 KeeperStorage::calculateNodesDigest(UInt64 current_digest, int64_t current_zxid) const +UInt64 KeeperStorage::calculateNodesDigest(UInt64 current_digest, const std::vector & new_deltas) const { + if (!digest_enabled) + return current_digest; + std::unordered_map> updated_nodes; - for (const auto & delta : uncommitted_state.deltas) + for (const auto & delta : new_deltas) { - if (delta.zxid != current_zxid) - continue; - std::visit( Overloaded{ [&](const CreateNodeDelta & create_delta) @@ -1612,7 +1654,7 @@ UInt64 KeeperStorage::calculateNodesDigest(UInt64 current_digest, int64_t curren { if (!updated_nodes.contains(delta.path)) { - auto old_digest = uncommitted_state.getNode(delta.path, current_zxid)->getDigest(delta.path); + auto old_digest = uncommitted_state.getNode(delta.path)->getDigest(delta.path); current_digest -= old_digest; } @@ -1625,7 +1667,8 @@ UInt64 KeeperStorage::calculateNodesDigest(UInt64 current_digest, int64_t curren auto updated_node_it = updated_nodes.find(delta.path); if (updated_node_it == updated_nodes.end()) { - node = uncommitted_state.getNode(delta.path, current_zxid); + node = std::make_shared(); + node->shallowCopy(*uncommitted_state.getNode(delta.path)); current_digest -= node->getDigest(delta.path); updated_nodes.emplace(delta.path, node); } @@ -1647,7 +1690,6 @@ UInt64 KeeperStorage::calculateNodesDigest(UInt64 current_digest, int64_t curren } } - return current_digest; } @@ -1669,53 +1711,35 @@ void KeeperStorage::preprocessRequest( } else { - // if we are Leader node, the request potentially already got preprocessed - // Leader can preprocess requests in a batch so the ZXID we are searching isn't - // guaranteed to be last - auto txn_it = std::lower_bound( - uncommitted_transactions.begin(), - uncommitted_transactions.end(), - new_last_zxid, - [&](const auto & request, const auto value) { return request.zxid < value; }); - // this zxid is not found in the uncommitted_transactions so do the regular check - if (txn_it == uncommitted_transactions.end()) - { - if (new_last_zxid <= last_zxid) - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", - new_last_zxid, - last_zxid); - } - else - { - if (txn_it->zxid == new_last_zxid && (!digest || checkDigest(*digest, txn_it->nodes_digest))) - // we found the preprocessed request with the same ZXID, we can skip it - return; + if (last_zxid == new_last_zxid && digest && checkDigest(*digest, getNodesDigest(false))) + // we found the preprocessed request with the same ZXID, we can skip it + return; + if (new_last_zxid <= last_zxid) throw Exception( - ErrorCodes::LOGICAL_ERROR, "Found invalid state of uncommitted transactions, missing request with ZXID {}", new_last_zxid); - } + ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, last_zxid); } + std::vector new_deltas; TransactionInfo transaction{.zxid = new_last_zxid}; + uint64_t new_digest = getNodesDigest(false).value; SCOPE_EXIT({ if (digest_enabled) // if the version of digest we got from the leader is the same as the one this instances has, we can simply copy the value // and just check the digest on the commit // a mistake can happen while applying the changes to the uncommitted_state so for now let's just recalculate the digest here also - transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, calculateNodesDigest(getNodesDigest(false).value, transaction.zxid)}; + transaction.nodes_digest = Digest{CURRENT_DIGEST_VERSION, new_digest}; else transaction.nodes_digest = Digest{DigestVersion::NO_DIGEST}; uncommitted_transactions.emplace_back(transaction); + uncommitted_state.addDeltas(std::move(new_deltas)); }); KeeperStorageRequestProcessorPtr request_processor = KeeperStorageRequestProcessorsFactory::instance().get(zk_request); if (zk_request->getOpNum() == Coordination::OpNum::Close) /// Close request is special { - auto & deltas = uncommitted_state.deltas; auto session_ephemerals = ephemerals.find(session_id); if (session_ephemerals != ephemerals.end()) { @@ -1724,9 +1748,9 @@ void KeeperStorage::preprocessRequest( // For now just add deltas for removing the node // On commit, ephemerals nodes will be deleted from storage // and removed from the session - if (uncommitted_state.hasNode(ephemeral_path)) + if (uncommitted_state.getNode(ephemeral_path)) { - deltas.emplace_back( + new_deltas.emplace_back( parentPath(ephemeral_path).toString(), new_last_zxid, UpdateNodeDelta{[ephemeral_path](Node & parent) @@ -1735,11 +1759,13 @@ void KeeperStorage::preprocessRequest( ++parent.stat.cversion; }}); - deltas.emplace_back(ephemeral_path, new_last_zxid, RemoveNodeDelta()); + new_deltas.emplace_back(ephemeral_path, new_last_zxid, RemoveNodeDelta()); } } } + new_digest = calculateNodesDigest(new_digest, new_deltas); + return; } @@ -1749,9 +1775,7 @@ void KeeperStorage::preprocessRequest( return; } - auto new_deltas = request_processor->preprocess(*this, transaction.zxid, session_id, time); - uncommitted_state.deltas.insert( - uncommitted_state.deltas.end(), std::make_move_iterator(new_deltas.begin()), std::make_move_iterator(new_deltas.end())); + new_deltas = request_processor->preprocess(*this, transaction.zxid, session_id, time, new_digest); } KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( @@ -1799,7 +1823,7 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( } } - std::erase_if(uncommitted_state.deltas, [this](const auto & delta) { return delta.zxid == zxid; }); + uncommitted_state.commit(zxid); clearDeadWatches(session_id); auto auth_it = session_and_auth.find(session_id); @@ -1845,7 +1869,7 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( else { response = request_processor->process(*this, zxid, session_id, time); - std::erase_if(uncommitted_state.deltas, [this](const auto & delta) { return delta.zxid == zxid; }); + uncommitted_state.commit(zxid); } /// Watches for this requests are added to the watches lists @@ -1890,14 +1914,8 @@ void KeeperStorage::rollbackRequest(int64_t rollback_zxid) throw Exception( ErrorCodes::LOGICAL_ERROR, "Trying to rollback invalid ZXID ({}). It should be the last preprocessed.", rollback_zxid); - // we can only rollback the last zxid (if there is any) - // if there is a delta with a larger zxid, we have invalid state - const auto last_zxid = uncommitted_state.deltas.back().zxid; - if (!uncommitted_state.deltas.empty() && last_zxid > rollback_zxid) - throw DB::Exception{DB::ErrorCodes::LOGICAL_ERROR, "Invalid state of deltas found while trying to rollback request. Last ZXID ({}) is larger than the requested ZXID ({})", last_zxid, rollback_zxid}; - - std::erase_if(uncommitted_state.deltas, [rollback_zxid](const auto & delta) { return delta.zxid == rollback_zxid; }); uncommitted_transactions.pop_back(); + uncommitted_state.rollback(rollback_zxid); } KeeperStorage::Digest KeeperStorage::getNodesDigest(bool committed) const diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index d4c60735bd7..de1cb775613 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -54,12 +54,12 @@ public: void invalidateDigestCache() const; UInt64 getDigest(std::string_view path) const; - void setDigest(UInt64 digest); + + void shallowCopy(const Node & other); private: String data; ChildrenSet children{}; - mutable std::optional cached_digest; }; @@ -210,19 +210,14 @@ public: { explicit UncommittedState(KeeperStorage & storage_) : storage(storage_) { } - template - void applyDeltas(StringRef path, const Visitor & visitor, std::optional current_zxid = std::nullopt) const - { - for (const auto & delta : deltas) - { - if (current_zxid && delta.zxid >= current_zxid) - break; + void addDeltas(std::vector new_deltas); + void commit(int64_t commit_zxid); + void rollback(int64_t rollback_zxid); - if (path.empty() || delta.path == path) - std::visit(visitor, delta.operation); - } - } + std::shared_ptr getNode(StringRef path) const; + Coordination::ACLs getACLs(StringRef path) const; + void applyDelta(const Delta & delta); bool hasACL(int64_t session_id, bool is_local, std::function predicate) { for (const auto & session_auth : storage.session_and_auth[session_id]) @@ -244,10 +239,16 @@ public: return false; } - std::shared_ptr getNode(StringRef path, std::optional current_zxid = std::nullopt) const; - bool hasNode(StringRef path) const; - Coordination::ACLs getACLs(StringRef path) const; + std::shared_ptr getNodeFromStorage(StringRef path) const; + struct UncommittedNode + { + std::shared_ptr node{nullptr}; + Coordination::ACLs acls{}; + int64_t zxid{0}; + }; + + mutable std::unordered_map nodes; std::deque deltas; KeeperStorage & storage; }; @@ -325,7 +326,7 @@ public: const String superdigest; - KeeperStorage(int64_t tick_time_ms, const String & superdigest_, bool digest_enabled_ = true); + KeeperStorage(int64_t tick_time_ms, const String & superdigest_, bool digest_enabled_); /// Allocate new session id with the specified timeouts int64_t getSessionID(int64_t session_timeout_ms) @@ -343,7 +344,7 @@ public: session_expiry_queue.addNewSessionOrUpdate(session_id, session_timeout_ms); } - UInt64 calculateNodesDigest(UInt64 current_digest, int64_t current_zxid) const; + UInt64 calculateNodesDigest(UInt64 current_digest, const std::vector & new_deltas) const; /// Process user request and return response. /// check_acl = false only when converting data from ZooKeeper. diff --git a/src/Coordination/tests/gtest_coordination.cpp b/src/Coordination/tests/gtest_coordination.cpp index 24fb4a3d96e..5176c01fd93 100644 --- a/src/Coordination/tests/gtest_coordination.cpp +++ b/src/Coordination/tests/gtest_coordination.cpp @@ -1024,7 +1024,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotSimple) ChangelogDirTest test("./snapshots"); DB::KeeperSnapshotManager manager("./snapshots", 3, params.enable_compression); - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); addNode(storage, "/hello", "world", 1); addNode(storage, "/hello/somepath", "somedata", 3); storage.session_id_counter = 5; @@ -1072,7 +1072,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotMoreWrites) ChangelogDirTest test("./snapshots"); DB::KeeperSnapshotManager manager("./snapshots", 3, params.enable_compression); - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); storage.getSessionID(130); for (size_t i = 0; i < 50; ++i) @@ -1113,7 +1113,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotManySnapshots) ChangelogDirTest test("./snapshots"); DB::KeeperSnapshotManager manager("./snapshots", 3, params.enable_compression); - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); storage.getSessionID(130); for (size_t j = 1; j <= 5; ++j) @@ -1151,7 +1151,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotMode) auto params = GetParam(); ChangelogDirTest test("./snapshots"); DB::KeeperSnapshotManager manager("./snapshots", 3, params.enable_compression); - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); for (size_t i = 0; i < 50; ++i) { addNode(storage, "/hello_" + std::to_string(i), "world_" + std::to_string(i)); @@ -1204,7 +1204,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotBroken) auto params = GetParam(); ChangelogDirTest test("./snapshots"); DB::KeeperSnapshotManager manager("./snapshots", 3, params.enable_compression); - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); for (size_t i = 0; i < 50; ++i) { addNode(storage, "/hello_" + std::to_string(i), "world_" + std::to_string(i)); @@ -1579,7 +1579,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotDifferentCompressions) ChangelogDirTest test("./snapshots"); DB::KeeperSnapshotManager manager("./snapshots", 3, params.enable_compression); - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); addNode(storage, "/hello", "world", 1); addNode(storage, "/hello/somepath", "somedata", 3); storage.session_id_counter = 5; @@ -1731,7 +1731,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotEqual) { DB::KeeperSnapshotManager manager("./snapshots", 3, params.enable_compression); - DB::KeeperStorage storage(500, ""); + DB::KeeperStorage storage(500, "", true); for (size_t j = 0; j < 5000; ++j) { addNode(storage, "/hello_" + std::to_string(j), "world", 1); @@ -1801,7 +1801,7 @@ TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) using namespace DB; using namespace Coordination; - DB::KeeperStorage storage{500, ""}; + DB::KeeperStorage storage{500, "", true}; constexpr std::string_view path = "/test"; @@ -1913,59 +1913,6 @@ TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) ASSERT_FALSE(get_committed_data()); } -TEST_P(CoordinationTest, TestDigest) -{ - using namespace Coordination; - using namespace DB; - - ChangelogDirTest snapshots1("./snapshots1"); - ChangelogDirTest snapshots2("./snapshots2"); - CoordinationSettingsPtr settings = std::make_shared(); - - ResponsesQueue queue(std::numeric_limits::max()); - SnapshotsQueue snapshots_queue{1}; - const auto test_digest = [&](const auto modify_digest) - { - auto state_machine1 = std::make_shared(queue, snapshots_queue, "./snapshots1", settings); - auto state_machine2 = std::make_shared(queue, snapshots_queue, "./snapshots2", settings); - state_machine1->init(); - state_machine2->init(); - - std::shared_ptr request_c = std::make_shared(); - request_c->path = "/hello"; - auto zxid = state_machine1->getNextZxid(); - auto entry_c = getLogEntryFromZKRequest(0, 1, zxid, request_c); - state_machine1->pre_commit(1, entry_c->get_buf()); - auto correct_digest = state_machine1->getNodesDigest(); - ASSERT_EQ(correct_digest.version, DB::KeeperStorage::CURRENT_DIGEST_VERSION); - entry_c = getLogEntryFromZKRequest(0, 1, zxid, request_c, correct_digest.value); - - if (modify_digest) - { - std::shared_ptr modified_c = std::make_shared(); - modified_c->path = "modified"; - auto modified_entry = getLogEntryFromZKRequest(0, 1, zxid, modified_c, correct_digest.value); - ASSERT_THROW(state_machine2->pre_commit(1, modified_entry->get_buf()), DB::Exception); - } - else - ASSERT_NO_THROW(state_machine2->pre_commit(1, entry_c->get_buf())); - - if (modify_digest) - { - auto new_digest = modify_digest ? correct_digest.value + 1 : correct_digest.value; - auto modified_entry = getLogEntryFromZKRequest(0, 1, zxid, request_c, new_digest); - ASSERT_THROW(state_machine1->commit(1, modified_entry->get_buf()), DB::Exception); - } - else - ASSERT_NO_THROW(state_machine1->commit(1, entry_c->get_buf())); - }; - - test_digest(true); - test_digest(true); - test_digest(false); - test_digest(false); -} - INSTANTIATE_TEST_SUITE_P(CoordinationTestSuite, CoordinationTest, ::testing::ValuesIn(std::initializer_list{ From e5f7f5f689d8f9df44d007b463206eb1e8c3d160 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 20 May 2022 09:59:13 +0000 Subject: [PATCH 027/204] Update NuRaft commit --- contrib/NuRaft | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index 6e4b5e36de1..1334b9ae725 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit 6e4b5e36de157acb9d8d85d67942b99560d50bd9 +Subproject commit 1334b9ae72576821a698d657d08838861cf33007 From 25ab817c2bee4fdf255fd70332a8bb73b5fdcba6 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 20 May 2022 10:12:09 +0000 Subject: [PATCH 028/204] Revert formatting --- src/Coordination/KeeperStorage.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 150e81b7eae..92ea208d57b 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -390,13 +390,13 @@ Coordination::ACLs KeeperStorage::UncommittedState::getACLs(StringRef path) cons namespace { - [[noreturn]] void onStorageInconsistency() - { - LOG_ERROR( - &Poco::Logger::get("KeeperStorage"), - "Inconsistency found between uncommitted and committed data. Keeper will terminate to avoid undefined behaviour."); - std::terminate(); - } +[[noreturn]] void onStorageInconsistency() +{ + LOG_ERROR( + &Poco::Logger::get("KeeperStorage"), + "Inconsistency found between uncommitted and committed data. Keeper will terminate to avoid undefined behaviour."); + std::terminate(); +} } @@ -1033,9 +1033,17 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce }, request.version}); - new_deltas.emplace_back(parentPath(request.path).toString(), zxid, KeeperStorage::UpdateNodeDelta{[](KeeperStorage::Node & parent) { - parent.stat.cversion++; - }}); + new_deltas.emplace_back( + parentPath(request.path).toString(), + zxid, + KeeperStorage::UpdateNodeDelta + { + [](KeeperStorage::Node & parent) + { + parent.stat.cversion++; + } + } + ); digest = storage.calculateNodesDigest(digest, new_deltas); return new_deltas; From d8c247cfdc7ba19d358ce1492f0343a27affcab0 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 20 May 2022 10:14:51 +0000 Subject: [PATCH 029/204] Add error --- src/Coordination/KeeperStateMachine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index 1d8da1f615b..ca7ad3a0ead 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -15,6 +15,7 @@ namespace DB namespace ErrorCodes { + extern const int LOGICAL_ERROR; extern const int SYSTEM_ERROR; } From f379f225fa427851f36bff2306996138e1d1a23b Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Mon, 23 May 2022 06:48:39 +0000 Subject: [PATCH 030/204] Add ephemerals on preprocess --- src/Coordination/KeeperStorage.cpp | 13 +++++-------- src/Coordination/KeeperStorage.h | 2 -- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 92ea208d57b..c63aefb99af 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -421,7 +421,6 @@ Coordination::Error KeeperStorage::commit(int64_t commit_zxid, int64_t session_i std::move(operation.data), operation.stat, operation.is_sequental, - operation.is_ephemeral, std::move(operation.acls), session_id)) onStorageInconsistency(); @@ -503,9 +502,8 @@ bool KeeperStorage::createNode( String data, const Coordination::Stat & stat, bool is_sequental, - bool is_ephemeral, Coordination::ACLs node_acls, - int64_t session_id) + int64_t /*session_id*/) { auto parent_path = parentPath(path); auto node_it = container.find(parent_path); @@ -533,9 +531,6 @@ bool KeeperStorage::createNode( auto child_path = getBaseName(map_key->getKey()); container.updateValue(parent_path, [child_path](KeeperStorage::Node & parent) { parent.addChild(child_path); }); - if (is_ephemeral) - ephemerals[session_id].emplace(path); - addDigest(map_key->getMapped()->value, map_key->getKey().toView()); return true; }; @@ -740,7 +735,7 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr new_deltas.emplace_back( std::move(path_created), zxid, - KeeperStorage::CreateNodeDelta{stat, request.is_ephemeral, request.is_sequential, std::move(node_acls), request.data}); + KeeperStorage::CreateNodeDelta{stat, request.is_sequential, std::move(node_acls), request.data}); int32_t parent_cversion = request.parent_cversion; @@ -760,6 +755,9 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr ++node.stat.numChildren; }}); + if (request.is_ephemeral) + storage.ephemerals[session_id].emplace(path_created); + digest = storage.calculateNodesDigest(digest, new_deltas); return new_deltas; } @@ -1776,7 +1774,6 @@ void KeeperStorage::preprocessRequest( } new_digest = calculateNodesDigest(new_digest, new_deltas); - return; } diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index c0111cd390c..c7edd95da4b 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -146,7 +146,6 @@ public: struct CreateNodeDelta { Coordination::Stat stat; - bool is_ephemeral; bool is_sequental; Coordination::ACLs acls; String data; @@ -266,7 +265,6 @@ public: String data, const Coordination::Stat & stat, bool is_sequental, - bool is_ephemeral, Coordination::ACLs node_acls, int64_t session_id); From ea1cbff0d8d3bb29c47a25478752249d72f587d5 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Mon, 23 May 2022 07:51:26 +0000 Subject: [PATCH 031/204] Fix unit tests --- src/Coordination/KeeperStateMachine.cpp | 3 +- src/Coordination/KeeperStorage.cpp | 122 +++++++++--------- src/Coordination/KeeperStorage.h | 6 +- src/Coordination/tests/gtest_coordination.cpp | 32 ++--- 4 files changed, 77 insertions(+), 86 deletions(-) diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index ca7ad3a0ead..d8040e48ad4 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -225,7 +225,7 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n { std::lock_guard lock(storage_and_responses_lock); KeeperStorage::ResponsesForSessions responses_for_sessions = storage->processRequest( - request_for_session.request, request_for_session.session_id, request_for_session.time, request_for_session.zxid); + request_for_session.request, request_for_session.session_id, request_for_session.zxid); for (auto & response_for_session : responses_for_sessions) if (!responses_queue.push(response_for_session)) throw Exception( @@ -449,7 +449,6 @@ void KeeperStateMachine::processReadRequest(const KeeperStorage::RequestForSessi auto responses = storage->processRequest( request_for_session.request, request_for_session.session_id, - request_for_session.time, std::nullopt, true /*check_acl*/, true /*is_local*/); diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index c63aefb99af..81eaf699ae1 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -400,7 +400,7 @@ namespace } -Coordination::Error KeeperStorage::commit(int64_t commit_zxid, int64_t session_id) +Coordination::Error KeeperStorage::commit(int64_t commit_zxid) { // Deltas are added with increasing ZXIDs // If there are no deltas for the commit_zxid (e.g. read requests), we instantly return @@ -421,8 +421,7 @@ Coordination::Error KeeperStorage::commit(int64_t commit_zxid, int64_t session_i std::move(operation.data), operation.stat, operation.is_sequental, - std::move(operation.acls), - session_id)) + std::move(operation.acls))) onStorageInconsistency(); return Coordination::Error::ZOK; @@ -502,8 +501,7 @@ bool KeeperStorage::createNode( String data, const Coordination::Stat & stat, bool is_sequental, - Coordination::ACLs node_acls, - int64_t /*session_id*/) + Coordination::ACLs node_acls) { auto parent_path = parentPath(path); auto node_it = container.find(parent_path); @@ -551,6 +549,7 @@ bool KeeperStorage::removeNode(const std::string & path, int32_t version) if (prev_node.stat.ephemeralOwner != 0) { auto ephemerals_it = ephemerals.find(prev_node.stat.ephemeralOwner); + assert(ephemerals_it != ephemerals.end()); ephemerals_it->second.erase(path); if (ephemerals_it->second.empty()) ephemerals.erase(ephemerals_it); @@ -573,7 +572,7 @@ struct KeeperStorageRequestProcessor Coordination::ZooKeeperRequestPtr zk_request; explicit KeeperStorageRequestProcessor(const Coordination::ZooKeeperRequestPtr & zk_request_) : zk_request(zk_request_) { } - virtual Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const = 0; + virtual Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const = 0; virtual std::vector preprocess(KeeperStorage & /*storage*/, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /*time*/, uint64_t & /*digest*/) const { @@ -582,7 +581,7 @@ struct KeeperStorageRequestProcessor // process the request using locally committed data virtual Coordination::ZooKeeperResponsePtr - processLocal(KeeperStorage & /*storage*/, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /*time*/) const + processLocal(KeeperStorage & /*storage*/, int64_t /*zxid*/) const { throw Exception{DB::ErrorCodes::LOGICAL_ERROR, "Cannot process the request locally"}; } @@ -601,7 +600,7 @@ struct KeeperStorageHeartbeatRequestProcessor final : public KeeperStorageReques { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; Coordination::ZooKeeperResponsePtr - process(KeeperStorage & /* storage */, int64_t /* zxid */, int64_t /* session_id */, int64_t /* time */) const override + process(KeeperStorage & /* storage */, int64_t /* zxid */) const override { return zk_request->makeResponse(); } @@ -611,7 +610,7 @@ struct KeeperStorageSyncRequestProcessor final : public KeeperStorageRequestProc { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; Coordination::ZooKeeperResponsePtr - process(KeeperStorage & /* storage */, int64_t /* zxid */, int64_t /* session_id */, int64_t /* time */) const override + process(KeeperStorage & /* storage */, int64_t /* zxid */) const override { auto response = zk_request->makeResponse(); dynamic_cast(*response).path @@ -719,6 +718,9 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr if (!fixupACL(request.acls, storage.session_and_auth[session_id], node_acls)) return {{zxid, Coordination::Error::ZINVALIDACL}}; + if (request.is_ephemeral) + storage.ephemerals[session_id].emplace(path_created); + Coordination::Stat stat; stat.czxid = zxid; stat.mzxid = zxid; @@ -755,19 +757,16 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr ++node.stat.numChildren; }}); - if (request.is_ephemeral) - storage.ephemerals[session_id].emplace(path_created); - digest = storage.calculateNodesDigest(digest, new_deltas); return new_deltas; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperCreateResponse & response = dynamic_cast(*response_ptr); - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -809,7 +808,7 @@ struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProce } template - Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid) const { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperGetResponse & response = dynamic_cast(*response_ptr); @@ -817,7 +816,7 @@ struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProce if constexpr (!local) { - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -844,14 +843,14 @@ struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProce } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } - Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } }; @@ -918,12 +917,12 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr return new_deltas; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperRemoveResponse & response = dynamic_cast(*response_ptr); - response.error = storage.commit(zxid, session_id); + response.error = storage.commit(zxid); return response_ptr; } @@ -950,7 +949,7 @@ struct KeeperStorageExistsRequestProcessor final : public KeeperStorageRequestPr } template - Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid) const { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperExistsResponse & response = dynamic_cast(*response_ptr); @@ -958,7 +957,7 @@ struct KeeperStorageExistsRequestProcessor final : public KeeperStorageRequestPr if constexpr (!local) { - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -983,14 +982,14 @@ struct KeeperStorageExistsRequestProcessor final : public KeeperStorageRequestPr return response_ptr; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } - Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } }; @@ -1047,7 +1046,7 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce return new_deltas; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { auto & container = storage.container; @@ -1055,7 +1054,7 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce Coordination::ZooKeeperSetResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperSetRequest & request = dynamic_cast(*zk_request); - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -1099,7 +1098,7 @@ struct KeeperStorageListRequestProcessor final : public KeeperStorageRequestProc template - Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid) const { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperListResponse & response = dynamic_cast(*response_ptr); @@ -1107,7 +1106,7 @@ struct KeeperStorageListRequestProcessor final : public KeeperStorageRequestProc if constexpr (!local) { - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -1142,14 +1141,14 @@ struct KeeperStorageListRequestProcessor final : public KeeperStorageRequestProc return response_ptr; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } - Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } }; @@ -1177,7 +1176,7 @@ struct KeeperStorageCheckRequestProcessor final : public KeeperStorageRequestPro } template - Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid) const { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperCheckResponse & response = dynamic_cast(*response_ptr); @@ -1185,7 +1184,7 @@ struct KeeperStorageCheckRequestProcessor final : public KeeperStorageRequestPro if constexpr (!local) { - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -1218,14 +1217,14 @@ struct KeeperStorageCheckRequestProcessor final : public KeeperStorageRequestPro return response_ptr; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } - Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } }; @@ -1269,13 +1268,13 @@ struct KeeperStorageSetACLRequestProcessor final : public KeeperStorageRequestPr return new_deltas; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperSetACLResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperSetACLRequest & request = dynamic_cast(*zk_request); - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -1312,7 +1311,7 @@ struct KeeperStorageGetACLRequestProcessor final : public KeeperStorageRequestPr } template - Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid) const { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperGetACLResponse & response = dynamic_cast(*response_ptr); @@ -1320,7 +1319,7 @@ struct KeeperStorageGetACLRequestProcessor final : public KeeperStorageRequestPr if constexpr (!local) { - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) { response.error = result; return response_ptr; @@ -1345,14 +1344,14 @@ struct KeeperStorageGetACLRequestProcessor final : public KeeperStorageRequestPr return response_ptr; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } - Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid) const override { - return processImpl(storage, zxid, session_id, time); + return processImpl(storage, zxid); } }; @@ -1434,7 +1433,7 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro return {}; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperMultiResponse & response = dynamic_cast(*response_ptr); @@ -1456,7 +1455,7 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro for (size_t i = 0; i < concrete_requests.size(); ++i) { - auto cur_response = concrete_requests[i]->process(storage, zxid, session_id, time); + auto cur_response = concrete_requests[i]->process(storage, zxid); response.responses[i] = cur_response; storage.uncommitted_state.commit(zxid); } @@ -1465,14 +1464,14 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro return response_ptr; } - Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid) const override { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperMultiResponse & response = dynamic_cast(*response_ptr); for (size_t i = 0; i < concrete_requests.size(); ++i) { - auto cur_response = concrete_requests[i]->process(storage, zxid, session_id, time); + auto cur_response = concrete_requests[i]->processLocal(storage, zxid); response.responses[i] = cur_response; if (cur_response->error != Coordination::Error::ZOK) @@ -1514,7 +1513,7 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro struct KeeperStorageCloseRequestProcessor final : public KeeperStorageRequestProcessor { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - Coordination::ZooKeeperResponsePtr process(KeeperStorage &, int64_t, int64_t, int64_t /* time */) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage &, int64_t) const override { throw DB::Exception("Called process on close request", ErrorCodes::LOGICAL_ERROR); } @@ -1549,12 +1548,12 @@ struct KeeperStorageAuthRequestProcessor final : public KeeperStorageRequestProc return new_deltas; } - Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid) const override { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperAuthResponse & auth_response = dynamic_cast(*response_ptr); - if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + if (const auto result = storage.commit(zxid); result != Coordination::Error::ZOK) auth_response.error = result; return response_ptr; @@ -1789,7 +1788,6 @@ void KeeperStorage::preprocessRequest( KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( const Coordination::ZooKeeperRequestPtr & zk_request, int64_t session_id, - int64_t time, std::optional new_last_zxid, bool check_acl, bool is_local) @@ -1817,7 +1815,7 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( if (zk_request->getOpNum() == Coordination::OpNum::Close) /// Close request is special { - commit(zxid, session_id); + commit(zxid); for (const auto & delta : uncommitted_state.deltas) { @@ -1849,7 +1847,7 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( else if (zk_request->getOpNum() == Coordination::OpNum::Heartbeat) /// Heartbeat request is also special { KeeperStorageRequestProcessorPtr storage_request = KeeperStorageRequestProcessorsFactory::instance().get(zk_request); - auto response = storage_request->process(*this, zxid, session_id, time); + auto response = storage_request->process(*this, zxid); response->xid = zk_request->xid; response->zxid = getZXID(); @@ -1871,12 +1869,12 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( } else { - response = request_processor->processLocal(*this, zxid, session_id, time); + response = request_processor->processLocal(*this, zxid); } } else { - response = request_processor->process(*this, zxid, session_id, time); + response = request_processor->process(*this, zxid); uncommitted_state.commit(zxid); } diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index c7edd95da4b..63be1b94593 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -255,7 +255,7 @@ public: UncommittedState uncommitted_state{*this}; - Coordination::Error commit(int64_t zxid, int64_t session_id); + Coordination::Error commit(int64_t zxid); // Create node in the storage // Returns false if it failed to create the node, true otherwise @@ -265,8 +265,7 @@ public: String data, const Coordination::Stat & stat, bool is_sequental, - Coordination::ACLs node_acls, - int64_t session_id); + Coordination::ACLs node_acls); // Remove node in the storage // Returns false if it failed to remove the node, true otherwise @@ -350,7 +349,6 @@ public: ResponsesForSessions processRequest( const Coordination::ZooKeeperRequestPtr & request, int64_t session_id, - int64_t time, std::optional new_last_zxid, bool check_acl = true, bool is_local = false); diff --git a/src/Coordination/tests/gtest_coordination.cpp b/src/Coordination/tests/gtest_coordination.cpp index 5176c01fd93..bf49b1c48d5 100644 --- a/src/Coordination/tests/gtest_coordination.cpp +++ b/src/Coordination/tests/gtest_coordination.cpp @@ -1224,7 +1224,7 @@ TEST_P(CoordinationTest, TestStorageSnapshotBroken) EXPECT_THROW(manager.restoreFromLatestSnapshot(), DB::Exception); } -nuraft::ptr getBufferFromZKRequest(int64_t session_id, int64_t zxid, const Coordination::ZooKeeperRequestPtr & request, const std::optional digest = std::nullopt) +nuraft::ptr getBufferFromZKRequest(int64_t session_id, int64_t zxid, const Coordination::ZooKeeperRequestPtr & request) { DB::WriteBufferFromNuraftBuffer buf; DB::writeIntBinary(session_id, buf); @@ -1233,17 +1233,13 @@ nuraft::ptr getBufferFromZKRequest(int64_t session_id, int64_t z auto time = duration_cast(system_clock::now().time_since_epoch()).count(); DB::writeIntBinary(time, buf); DB::writeIntBinary(zxid, buf); - if (digest) - { - DB::writeIntBinary(DB::KeeperStorage::CURRENT_DIGEST_VERSION, buf); - DB::writeIntBinary(*digest, buf); - } + DB::writeIntBinary(DB::KeeperStorage::DigestVersion::NO_DIGEST, buf); return buf.getBuffer(); } -nuraft::ptr getLogEntryFromZKRequest(size_t term, int64_t session_id, int64_t zxid, const Coordination::ZooKeeperRequestPtr & request, const std::optional digest = std::nullopt) +nuraft::ptr getLogEntryFromZKRequest(size_t term, int64_t session_id, int64_t zxid, const Coordination::ZooKeeperRequestPtr & request) { - auto buffer = getBufferFromZKRequest(session_id, zxid, request, digest); + auto buffer = getBufferFromZKRequest(session_id, zxid, request); return nuraft::cs_new(term, buffer); } @@ -1265,7 +1261,7 @@ void testLogAndStateMachine(Coordination::CoordinationSettingsPtr settings, uint { std::shared_ptr request = std::make_shared(); request->path = "/hello_" + std::to_string(i); - auto entry = getLogEntryFromZKRequest(0, 1, 1, request); + auto entry = getLogEntryFromZKRequest(0, 1, i, request); changelog.append(entry); changelog.end_of_append_batch(0, 0); @@ -1809,7 +1805,7 @@ TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) { auto request = std::make_shared(); request->path = path; - auto responses = storage.processRequest(request, 0, 0, std::nullopt, true, true); + auto responses = storage.processRequest(request, 0, std::nullopt, true, true); const auto & get_response = getSingleResponse(responses); if (get_response.error != Error::ZOK) @@ -1857,19 +1853,19 @@ TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) ASSERT_FALSE(get_committed_data()); { - const auto responses = storage.processRequest(create_request, 0, 0, 1); + const auto responses = storage.processRequest(create_request, 0, 1); const auto & create_response = getSingleResponse(responses); ASSERT_EQ(create_response.error, Error::ZOK); } { - const auto responses = storage.processRequest(create_request, 0, 0, 2); + const auto responses = storage.processRequest(create_request, 0, 2); const auto & create_response = getSingleResponse(responses); ASSERT_EQ(create_response.error, Error::ZNODEEXISTS); } { - const auto responses = storage.processRequest(after_create_get, 0, 0, 3); + const auto responses = storage.processRequest(after_create_get, 0, 3); const auto & get_response = getSingleResponse(responses); ASSERT_EQ(get_response.error, Error::ZOK); ASSERT_EQ(get_response.data, "initial_data"); @@ -1878,13 +1874,13 @@ TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) ASSERT_EQ(get_committed_data(), "initial_data"); { - const auto responses = storage.processRequest(set_request, 0, 0, 4); + const auto responses = storage.processRequest(set_request, 0, 4); const auto & create_response = getSingleResponse(responses); ASSERT_EQ(create_response.error, Error::ZOK); } { - const auto responses = storage.processRequest(after_set_get, 0, 0, 5); + const auto responses = storage.processRequest(after_set_get, 0, 5); const auto & get_response = getSingleResponse(responses); ASSERT_EQ(get_response.error, Error::ZOK); ASSERT_EQ(get_response.data, "new_data"); @@ -1893,19 +1889,19 @@ TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) ASSERT_EQ(get_committed_data(), "new_data"); { - const auto responses = storage.processRequest(remove_request, 0, 0, 6); + const auto responses = storage.processRequest(remove_request, 0, 6); const auto & create_response = getSingleResponse(responses); ASSERT_EQ(create_response.error, Error::ZOK); } { - const auto responses = storage.processRequest(remove_request, 0, 0, 7); + const auto responses = storage.processRequest(remove_request, 0, 7); const auto & create_response = getSingleResponse(responses); ASSERT_EQ(create_response.error, Error::ZNONODE); } { - const auto responses = storage.processRequest(after_remove_get, 0, 0, 8); + const auto responses = storage.processRequest(after_remove_get, 0, 8); const auto & get_response = getSingleResponse(responses); ASSERT_EQ(get_response.error, Error::ZNONODE); } From 2b8f71b4a8685aa401729027daf6fd3aa78fa5b8 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Mon, 23 May 2022 07:55:23 +0000 Subject: [PATCH 032/204] Fix style --- src/Coordination/KeeperServer.cpp | 2 +- src/Coordination/KeeperStorage.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index f4a6e194d30..9ba336efd24 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -537,7 +537,7 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ assert(entry->get_val_type() == nuraft::app_log); auto next_zxid = state_machine->getNextZxid(); - + auto & entry_buf = entry->get_buf(); auto request_for_session = state_machine->parseRequest(entry_buf); request_for_session.zxid = next_zxid; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 81eaf699ae1..f6ae93ae59b 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -328,7 +328,7 @@ void KeeperStorage::UncommittedState::commit(int64_t commit_zxid) deltas.pop_front(); } - // delete all cached nodes that were not modifed after the commit_zxid + // delete all cached nodes that were not modified after the commit_zxid std::erase_if(nodes, [commit_zxid](const auto & node) { return node.second.zxid == commit_zxid; }); } @@ -338,11 +338,11 @@ void KeeperStorage::UncommittedState::rollback(int64_t rollback_zxid) // if there is a delta with a larger zxid, we have invalid state const auto last_zxid = deltas.back().zxid; if (!deltas.empty() && last_zxid > rollback_zxid) - throw DB::Exception{ + throw DB::Exception( DB::ErrorCodes::LOGICAL_ERROR, "Invalid state of deltas found while trying to rollback request. Last ZXID ({}) is larger than the requested ZXID ({})", last_zxid, - rollback_zxid}; + rollback_zxid); std::erase_if(deltas, [rollback_zxid](const auto & delta) { return delta.zxid == rollback_zxid; }); From 85e7118300b327e28aaa2fcfabebb06db737bf03 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Mon, 23 May 2022 14:37:57 +0000 Subject: [PATCH 033/204] Fix integrations test --- src/Coordination/KeeperSnapshotManager.cpp | 1 + src/Coordination/KeeperStorage.cpp | 104 ++++++++++++++------- src/Coordination/KeeperStorage.h | 3 + src/Coordination/ZooKeeperDataReader.cpp | 2 +- 4 files changed, 74 insertions(+), 36 deletions(-) diff --git a/src/Coordination/KeeperSnapshotManager.cpp b/src/Coordination/KeeperSnapshotManager.cpp index f18a0f1260e..df763affeda 100644 --- a/src/Coordination/KeeperSnapshotManager.cpp +++ b/src/Coordination/KeeperSnapshotManager.cpp @@ -388,6 +388,7 @@ KeeperStorageSnapshot::KeeperStorageSnapshot(KeeperStorage * storage_, uint64_t , session_id(storage->session_id_counter) , cluster_config(cluster_config_) , zxid(storage->zxid) + , nodes_digest(storage->nodes_digest) { auto [size, ver] = storage->container.snapshotSizeWithVersion(); snapshot_container_size = size; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index f6ae93ae59b..3147fbc1f36 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -329,7 +329,9 @@ void KeeperStorage::UncommittedState::commit(int64_t commit_zxid) } // delete all cached nodes that were not modified after the commit_zxid - std::erase_if(nodes, [commit_zxid](const auto & node) { return node.second.zxid == commit_zxid; }); + // the commit can end on SubDeltaEnd so we don't want to clear cached nodes too soon + if (deltas.empty() || deltas.front().zxid > commit_zxid) + std::erase_if(nodes, [commit_zxid](const auto & node) { return node.second.zxid == commit_zxid; }); } void KeeperStorage::UncommittedState::rollback(int64_t rollback_zxid) @@ -344,6 +346,35 @@ void KeeperStorage::UncommittedState::rollback(int64_t rollback_zxid) last_zxid, rollback_zxid); + // we need to undo ephemeral mapping modifications + // CreateNodeDelta added ephemeral for session id -> we need to remove it + // RemoveNodeDelta removed ephemeral for session id -> we need to add it back + for (auto delta_it = deltas.rbegin(); delta_it != deltas.rend(); ++delta_it) + { + if (delta_it->zxid < rollback_zxid) + break; + + assert(delta_it->zxid == rollback_zxid); + if (!delta_it->path.empty()) + { + std::visit( + [&](const DeltaType & operation) + { + if constexpr (std::same_as) + { + if (operation.stat.ephemeralOwner != 0) + storage.unregisterEphemeralPath(operation.stat.ephemeralOwner, delta_it->path); + } + else if constexpr (std::same_as) + { + if (operation.ephemeral_owner != 0) + storage.ephemerals[operation.ephemeral_owner].emplace(delta_it->path); + } + }, + delta_it->operation); + } + } + std::erase_if(deltas, [rollback_zxid](const auto & delta) { return delta.zxid == rollback_zxid; }); std::unordered_set deleted_nodes; @@ -546,15 +577,6 @@ bool KeeperStorage::removeNode(const std::string & path, int32_t version) return false; auto prev_node = node_it->value; - if (prev_node.stat.ephemeralOwner != 0) - { - auto ephemerals_it = ephemerals.find(prev_node.stat.ephemeralOwner); - assert(ephemerals_it != ephemerals.end()); - ephemerals_it->second.erase(path); - if (ephemerals_it->second.empty()) - ephemerals.erase(ephemerals_it); - } - acl_map.removeUsage(prev_node.acl_id); container.updateValue( @@ -664,6 +686,14 @@ bool KeeperStorage::checkACL(StringRef path, int32_t permission, int64_t session return false; } +void KeeperStorage::unregisterEphemeralPath(int64_t session_id, const std::string & path) +{ + auto ephemerals_it = ephemerals.find(session_id); + assert(ephemerals_it != ephemerals.end()); + ephemerals_it->second.erase(path); + if (ephemerals_it->second.empty()) + ephemerals.erase(ephemerals_it); +} struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestProcessor { @@ -878,11 +908,15 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr new_deltas.emplace_back( std::string{parent_path}, zxid, - KeeperStorage::UpdateNodeDelta{[zxid](KeeperStorage::Node & parent) - { - if (parent.stat.pzxid < zxid) - parent.stat.pzxid = zxid; - }}); + KeeperStorage::UpdateNodeDelta + { + [zxid](KeeperStorage::Node & parent) + { + if (parent.stat.pzxid < zxid) + parent.stat.pzxid = zxid; + } + } + ); }; auto node = storage.uncommitted_state.getNode(request.path); @@ -910,7 +944,10 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr ++parent.stat.cversion; }}); - new_deltas.emplace_back(request.path, zxid, KeeperStorage::RemoveNodeDelta{request.version}); + if (node->stat.ephemeralOwner != 0) + storage.unregisterEphemeralPath(node->stat.ephemeralOwner, request.path); + + new_deltas.emplace_back(request.path, zxid, KeeperStorage::RemoveNodeDelta{request.version, node->stat.ephemeralOwner}); digest = storage.calculateNodesDigest(digest, new_deltas); @@ -1567,10 +1604,6 @@ void KeeperStorage::finalize() finalized = true; - for (const auto & [session_id, ephemerals_paths] : ephemerals) - for (const String & ephemeral_path : ephemerals_paths) - container.erase(ephemeral_path); - ephemerals.clear(); watches.clear(); @@ -1753,23 +1786,24 @@ void KeeperStorage::preprocessRequest( { for (const auto & ephemeral_path : session_ephemerals->second) { - // For now just add deltas for removing the node - // On commit, ephemerals nodes will be deleted from storage - // and removed from the session - if (uncommitted_state.getNode(ephemeral_path)) - { - new_deltas.emplace_back( - parentPath(ephemeral_path).toString(), - new_last_zxid, - UpdateNodeDelta{[ephemeral_path](Node & parent) - { - --parent.stat.numChildren; - ++parent.stat.cversion; - }}); + new_deltas.emplace_back + ( + parentPath(ephemeral_path).toString(), + new_last_zxid, + UpdateNodeDelta + { + [ephemeral_path](Node & parent) + { + --parent.stat.numChildren; + ++parent.stat.cversion; + } + } + ); - new_deltas.emplace_back(ephemeral_path, new_last_zxid, RemoveNodeDelta()); - } + new_deltas.emplace_back(ephemeral_path, transaction.zxid, RemoveNodeDelta{.ephemeral_owner = session_id}); } + + ephemerals.erase(session_ephemerals); } new_digest = calculateNodesDigest(new_digest, new_deltas); diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 63be1b94593..e2fb921fcdf 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -154,6 +154,7 @@ public: struct RemoveNodeDelta { int32_t version{-1}; + int64_t ephemeral_owner{0}; }; struct UpdateNodeDelta @@ -274,6 +275,8 @@ public: bool checkACL(StringRef path, int32_t permissions, int64_t session_id, bool is_local); + void unregisterEphemeralPath(int64_t session_id, const std::string & path); + /// Mapping session_id -> set of ephemeral nodes paths Ephemerals ephemerals; /// Mapping session_id -> set of watched nodes paths diff --git a/src/Coordination/ZooKeeperDataReader.cpp b/src/Coordination/ZooKeeperDataReader.cpp index 4d1745edc6a..6702c4cc718 100644 --- a/src/Coordination/ZooKeeperDataReader.cpp +++ b/src/Coordination/ZooKeeperDataReader.cpp @@ -521,7 +521,7 @@ bool deserializeTxn(KeeperStorage & storage, ReadBuffer & in, Poco::Logger * /*l return true; storage.preprocessRequest(request, session_id, time, zxid, /* check_acl = */ false); - storage.processRequest(request, session_id, time, zxid, /* check_acl = */ false); + storage.processRequest(request, session_id, zxid, /* check_acl = */ false); } } From c55f4e470a8397ba659d93e934b02cd71125d7e3 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 24 May 2022 13:11:11 +0000 Subject: [PATCH 034/204] Small fix --- src/Coordination/KeeperServer.cpp | 3 --- src/Coordination/KeeperStateMachine.cpp | 2 +- src/Coordination/KeeperStorage.cpp | 17 +++++++++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 9ba336efd24..45325454473 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -1,12 +1,9 @@ #include #include -#include "Coordination/Changelog.h" -#include "IO/WriteBuffer.h" #include "config_core.h" #include -#include #include #include #include diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index d8040e48ad4..40f1b3cbf19 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -112,7 +112,7 @@ void assertDigest( { LOG_FATAL( &Poco::Logger::get("KeeperStateMachine"), - "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest {} (digest version " + "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest - {} (digest version " "{}). Keeper will " "terminate to avoid inconsistencies.\nExtra information about the request:\n{}", committing ? "committing" : "preprocessing", diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 3147fbc1f36..9eeaa63e584 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -1297,8 +1297,21 @@ struct KeeperStorageSetACLRequestProcessor final : public KeeperStorageRequestPr return {{zxid, Coordination::Error::ZINVALIDACL}}; std::vector new_deltas - = {{request.path, zxid, KeeperStorage::SetACLDelta{std::move(node_acls), request.version}}, - {request.path, zxid, KeeperStorage::UpdateNodeDelta{[](KeeperStorage::Node & n) { ++n.stat.aversion; }}}}; + { + { + request.path, + zxid, + KeeperStorage::SetACLDelta{std::move(node_acls), request.version} + }, + { + request.path, + zxid, + KeeperStorage::UpdateNodeDelta + { + [](KeeperStorage::Node & n) { ++n.stat.aversion; } + } + } + }; digest = storage.calculateNodesDigest(digest, new_deltas); From 61e38b9e823d8e7bd187ca58c2ad00c43da0ad23 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 25 May 2022 07:41:00 +0000 Subject: [PATCH 035/204] Allow comitting of logs without digest --- src/Coordination/KeeperStateMachine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index 40f1b3cbf19..2d902453f22 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -233,9 +233,8 @@ nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, n "Could not push response with session id {} into responses queue", response_for_session.session_id); - if (digest_enabled) + if (digest_enabled && request_for_session.digest) { - assert(request_for_session.digest); assertDigest(*request_for_session.digest, storage->getNodesDigest(true), *request_for_session.request, true); } } From 9e5a635da22619bc69f6187d50519733d8b9c02d Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 25 May 2022 14:21:26 +0000 Subject: [PATCH 036/204] Fix rollback --- src/Coordination/KeeperStorage.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 9eeaa63e584..bfe296198d1 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -338,12 +338,11 @@ void KeeperStorage::UncommittedState::rollback(int64_t rollback_zxid) { // we can only rollback the last zxid (if there is any) // if there is a delta with a larger zxid, we have invalid state - const auto last_zxid = deltas.back().zxid; - if (!deltas.empty() && last_zxid > rollback_zxid) + if (!deltas.empty() && deltas.back().zxid > rollback_zxid) throw DB::Exception( DB::ErrorCodes::LOGICAL_ERROR, "Invalid state of deltas found while trying to rollback request. Last ZXID ({}) is larger than the requested ZXID ({})", - last_zxid, + deltas.back().zxid, rollback_zxid); // we need to undo ephemeral mapping modifications From 0697b9ffcffa4203531adbd9cb27aa4513a2b797 Mon Sep 17 00:00:00 2001 From: koloshmet Date: Wed, 25 May 2022 22:04:39 +0300 Subject: [PATCH 037/204] FPC codec --- src/Compression/CompressionCodecFPC.cpp | 498 ++++++++++++++++++++++++ src/Compression/CompressionFactory.cpp | 2 + src/Compression/CompressionInfo.h | 3 +- 3 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 src/Compression/CompressionCodecFPC.cpp diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp new file mode 100644 index 00000000000..3c7aac794c0 --- /dev/null +++ b/src/Compression/CompressionCodecFPC.cpp @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace DB +{ + +class CompressionCodecFPC : public ICompressionCodec +{ +public: + explicit CompressionCodecFPC(UInt8 float_size, UInt8 compression_level); + + uint8_t getMethodByte() const override; + + void updateHash(SipHash & hash) const override; + +protected: + UInt32 doCompressData(const char * source, UInt32 source_size, char * dest) const override; + + void doDecompressData(const char * source, UInt32 source_size, char * dest, UInt32 uncompressed_size) const override; + + UInt32 getMaxCompressedDataSize(UInt32 uncompressed_size) const override; + + bool isCompression() const override { return true; } + bool isGenericCompression() const override { return false; } + + static constexpr UInt32 HEADER_SIZE{3}; + +private: + UInt8 float_width; + UInt8 level; +}; + + +namespace ErrorCodes +{ + extern const int CANNOT_COMPRESS; + extern const int CANNOT_DECOMPRESS; + extern const int ILLEGAL_CODEC_PARAMETER; + extern const int ILLEGAL_SYNTAX_FOR_CODEC_TYPE; + extern const int BAD_ARGUMENTS; +} + +uint8_t CompressionCodecFPC::getMethodByte() const +{ + return static_cast(CompressionMethodByte::FPC); +} + +void CompressionCodecFPC::updateHash(SipHash & hash) const +{ + getCodecDesc()->updateTreeHash(hash); +} + +CompressionCodecFPC::CompressionCodecFPC(UInt8 float_size, UInt8 compression_level) + : float_width{float_size}, level{compression_level} +{ + setCodecDescription("FPC", {std::make_shared(static_cast(level))}); +} + +UInt32 CompressionCodecFPC::getMaxCompressedDataSize(UInt32 uncompressed_size) const +{ + auto float_count = (uncompressed_size + float_width - 1) / float_width; + if (float_count % 2 != 0) { + ++float_count; + } + return HEADER_SIZE + (float_count + float_count / 2) * float_width; +} + +namespace +{ + +UInt8 getFloatBytesSize(const IDataType & column_type) +{ + if (!WhichDataType(column_type).isFloat()) + { + throw Exception(ErrorCodes::BAD_ARGUMENTS, "FPC codec is not applicable for {} because the data type is not float", + column_type.getName()); + } + + if (auto float_size = column_type.getSizeOfValueInMemory(); float_size >= 4) + { + return static_cast(float_size); + } + throw Exception(ErrorCodes::BAD_ARGUMENTS, "FPC codec is not applicable for floats of size less than 4 bytes. Given type {}", + column_type.getName()); +} + +UInt8 encodeEndianness(std::endian endian) +{ + switch (endian) + { + case std::endian::little: + return 0; + case std::endian::big: + return 1; + } + throw Exception("Unsupported endianness", ErrorCodes::BAD_ARGUMENTS); +} + +std::endian decodeEndianness(UInt8 endian) { + switch (endian) + { + case 0: + return std::endian::little; + case 1: + return std::endian::big; + } + throw Exception("Unsupported endianness", ErrorCodes::BAD_ARGUMENTS); +} + +} + +void registerCodecFPC(CompressionCodecFactory & factory) +{ + auto method_code = static_cast(CompressionMethodByte::FPC); + auto codec_builder = [&](const ASTPtr & arguments, const IDataType * column_type) -> CompressionCodecPtr + { + if (!column_type) + { + throw Exception("FPC codec must have associated column", ErrorCodes::BAD_ARGUMENTS); + } + UInt8 level{0}; + if (arguments && !arguments->children.empty()) + { + if (arguments->children.size() > 1) + { + throw Exception(ErrorCodes::ILLEGAL_SYNTAX_FOR_CODEC_TYPE, + "FPC codec must have 1 parameter, given {}", arguments->children.size()); + } + + const auto * literal = arguments->children.front()->as(); + if (!literal) + { + throw Exception("FPC codec argument must be integer", ErrorCodes::ILLEGAL_CODEC_PARAMETER); + } + + level = literal->value.safeGet(); + } + return std::make_shared(getFloatBytesSize(*column_type), level); + }; + factory.registerCompressionCodecWithType("FPC", method_code, codec_builder); +} + +namespace +{ + +template requires (sizeof(TUint) >= 4) +class DfcmPredictor { +public: + explicit DfcmPredictor(std::size_t table_size): table(table_size, 0), prev_value{0}, hash{0} + { + } + + [[nodiscard]] + TUint predict() const noexcept + { + return table[hash] + prev_value; + } + + void add(TUint value) noexcept + { + table[hash] = value - prev_value; + recalculateHash(); + prev_value = value; + } + +private: + void recalculateHash() noexcept + { + auto value = table[hash]; + if constexpr (sizeof(TUint) >= 8) + { + hash = ((hash << 2) ^ static_cast(value >> 40)) & (table.size() - 1); + } + else + { + hash = ((hash << 4) ^ static_cast(value >> 23)) & (table.size() - 1); + } + } + + std::vector table; + TUint prev_value{0}; + std::size_t hash{0}; +}; + +template requires (sizeof(TUint) >= 4) +class FcmPredictor { +public: + explicit FcmPredictor(std::size_t table_size): table(table_size, 0), hash{0} + { + } + + [[nodiscard]] + TUint predict() const noexcept + { + return table[hash]; + } + + void add(TUint value) noexcept + { + table[hash] = value; + recalculateHash(); + } + +private: + void recalculateHash() noexcept + { + auto value = table[hash]; + if constexpr (sizeof(TUint) >= 8) + { + hash = ((hash << 6) ^ static_cast(value >> 48)) & (table.size() - 1); + } + else + { + hash = ((hash << 1) ^ static_cast(value >> 22)) & (table.size() - 1); + } + } + + std::vector table; + std::size_t hash{0}; +}; + +template + requires (Endian == std::endian::little || Endian == std::endian::big) +class FPCOperation +{ + static constexpr std::size_t CHUNK_SIZE{64}; + + static constexpr auto VALUE_SIZE = sizeof(TUint); + static constexpr std::byte DFCM_BIT_1{1u << 7}; + static constexpr std::byte DFCM_BIT_2{1u << 3}; + static constexpr unsigned MAX_COMPRESSED_SIZE{0b111u}; + +public: + explicit FPCOperation(std::span destination, UInt8 compression_level) + : dfcm_predictor(1 << compression_level), fcm_predictor(1 << compression_level), chunk{}, result{destination} + { + } + + std::size_t encode(std::span data) && + { + auto initial_size = result.size(); + + std::span chunk_view(chunk); + for (std::size_t i = 0; i < data.size(); i += chunk_view.size_bytes()) + { + auto written_values = importChunk(data.subspan(i), chunk_view); + encodeChunk(chunk_view.subspan(0, written_values)); + } + + return initial_size - result.size(); + } + + void decode(std::span values, std::size_t decoded_size) && + { + std::size_t read_bytes{0}; + + std::span chunk_view(chunk); + for (std::size_t i = 0; i < decoded_size; i += chunk_view.size_bytes()) + { + if (i + chunk_view.size_bytes() > decoded_size) + chunk_view = chunk_view.first(ceilBytesToEvenValues(decoded_size - i)); + read_bytes += decodeChunk(values.subspan(read_bytes), chunk_view); + exportChunk(chunk_view); + } + } + +private: + static std::size_t ceilBytesToEvenValues(std::size_t bytes_count) + { + auto values_count = (bytes_count + VALUE_SIZE - 1) / VALUE_SIZE; + return values_count % 2 == 0 ? values_count : values_count + 1; + } + + std::size_t importChunk(std::span values, std::span chnk) + { + if (auto chunk_view = std::as_writable_bytes(chnk); chunk_view.size() <= values.size()) + { + std::memcpy(chunk_view.data(), values.data(), chunk_view.size()); + return chunk_view.size() / VALUE_SIZE; + } + else + { + std::memset(chunk_view.data(), 0, chunk_view.size()); + std::memcpy(chunk_view.data(), values.data(), values.size()); + return ceilBytesToEvenValues(values.size()); + } + } + + void exportChunk(std::span chnk) + { + auto chunk_view = std::as_bytes(chnk).first(std::min(result.size(), chnk.size_bytes())); + std::memcpy(result.data(), chunk_view.data(), chunk_view.size()); + result = result.subspan(chunk_view.size()); + } + + void encodeChunk(std::span seq) + { + for (std::size_t i = 0; i < seq.size(); i += 2) + { + encodePair(seq[i], seq[i + 1]); + } + } + + struct CompressedValue + { + TUint value; + unsigned compressed_size; + bool is_dfcm_predictor; + }; + + unsigned encodeCompressedSize(int compressed) + { + if constexpr (VALUE_SIZE > MAX_COMPRESSED_SIZE) + { + if (compressed >= 4) + --compressed; + } + return std::min(static_cast(compressed), MAX_COMPRESSED_SIZE); + } + + unsigned decodeCompressedSize(unsigned encoded_size) + { + if constexpr (VALUE_SIZE > MAX_COMPRESSED_SIZE) + { + if (encoded_size > 3) + ++encoded_size; + } + return encoded_size; + } + + CompressedValue compressValue(TUint value) noexcept + { + TUint compressed_dfcm = dfcm_predictor.predict() ^ value; + TUint compressed_fcm = fcm_predictor.predict() ^ value; + dfcm_predictor.add(value); + fcm_predictor.add(value); + auto zeroes_dfcm = std::countl_zero(compressed_dfcm); + auto zeroes_fcm = std::countl_zero(compressed_fcm); + if (zeroes_dfcm > zeroes_fcm) + return {compressed_dfcm, encodeCompressedSize(zeroes_dfcm / CHAR_BIT), true}; + return {compressed_fcm, encodeCompressedSize(zeroes_fcm / CHAR_BIT), false}; + } + + void encodePair(TUint first, TUint second) + { + auto [value1, compressed_size1, is_dfcm_predictor1] = compressValue(first); + auto [value2, compressed_size2, is_dfcm_predictor2] = compressValue(second); + std::byte header{0x0}; + if (is_dfcm_predictor1) + header |= DFCM_BIT_1; + if (is_dfcm_predictor2) + header |= DFCM_BIT_2; + header |= static_cast((compressed_size1 << 4) | compressed_size2); + result.front() = header; + + compressed_size1 = decodeCompressedSize(compressed_size1); + compressed_size2 = decodeCompressedSize(compressed_size2); + auto tail_size1 = VALUE_SIZE - compressed_size1; + auto tail_size2 = VALUE_SIZE - compressed_size2; + + std::memcpy(result.data() + 1, valueTail(value1, compressed_size1), tail_size1); + std::memcpy(result.data() + 1 + tail_size1, valueTail(value2, compressed_size2), tail_size2); + result = result.subspan(1 + tail_size1 + tail_size2); + } + + std::size_t decodeChunk(std::span values, std::span seq) + { + std::size_t read_bytes{0}; + for (std::size_t i = 0; i < seq.size(); i += 2) + { + read_bytes += decodePair(values.subspan(read_bytes), seq[i], seq[i + 1]); + } + return read_bytes; + } + + TUint decompressValue(TUint value, bool isDfcmPredictor) + { + TUint decompressed; + if (isDfcmPredictor) + { + decompressed = dfcm_predictor.predict() ^ value; + } + else + { + decompressed = fcm_predictor.predict() ^ value; + } + dfcm_predictor.add(decompressed); + fcm_predictor.add(decompressed); + return decompressed; + } + + std::size_t decodePair(std::span bytes, TUint& first, TUint& second) + { + if (bytes.empty()) + throw Exception(ErrorCodes::CANNOT_DECOMPRESS, "Unexpected end of encoded sequence"); + + auto compressed_size1 = decodeCompressedSize(static_cast(bytes.front() >> 4) & MAX_COMPRESSED_SIZE); + auto compressed_size2 = decodeCompressedSize(static_cast(bytes.front()) & MAX_COMPRESSED_SIZE); + + auto tail_size1 = VALUE_SIZE - compressed_size1; + auto tail_size2 = VALUE_SIZE - compressed_size2; + + if (bytes.size() < 1 + tail_size1 + tail_size2) + throw Exception(ErrorCodes::CANNOT_DECOMPRESS, "Unexpected end of encoded sequence"); + + TUint value1{0}; + TUint value2{0}; + + std::memcpy(valueTail(value1, compressed_size1), bytes.data() + 1, tail_size1); + std::memcpy(valueTail(value2, compressed_size2), bytes.data() + 1 + tail_size1, tail_size2); + + auto is_dfcm_predictor1 = static_cast(bytes.front() & DFCM_BIT_1); + auto is_dfcm_predictor2 = static_cast(bytes.front() & DFCM_BIT_2); + first = decompressValue(value1, is_dfcm_predictor1 != 0); + second = decompressValue(value2, is_dfcm_predictor2 != 0); + + return 1 + tail_size1 + tail_size2; + } + + static void* valueTail(TUint& value, unsigned compressed_size) + { + if constexpr (Endian == std::endian::little) + { + return &value; + } + else + { + return reinterpret_cast(&value) + compressed_size; + } + } + + DfcmPredictor dfcm_predictor; + FcmPredictor fcm_predictor; + std::array chunk{}; + std::span result{}; +}; + +} + +UInt32 CompressionCodecFPC::doCompressData(const char * source, UInt32 source_size, char * dest) const +{ + dest[0] = static_cast(float_width); + dest[1] = static_cast(level); + dest[2] = static_cast(encodeEndianness(std::endian::native)); + + auto destination = std::as_writable_bytes(std::span(dest, source_size).subspan(HEADER_SIZE)); + auto src = std::as_bytes(std::span(source, source_size)); + switch (float_width) + { + case sizeof(Float64): + return HEADER_SIZE + FPCOperation(destination, level).encode(src); + case sizeof(Float32): + return HEADER_SIZE + FPCOperation(destination, level).encode(src); + default: + break; + } + throw Exception("Cannot compress. File has incorrect float width", ErrorCodes::CANNOT_COMPRESS); +} + +void CompressionCodecFPC::doDecompressData(const char * source, UInt32 source_size, char * dest, UInt32 uncompressed_size) const +{ + if (source_size < HEADER_SIZE) + throw Exception("Cannot decompress. File has wrong header", ErrorCodes::CANNOT_DECOMPRESS); + + auto compressed_data = std::span(source, source_size); + if (static_cast(compressed_data[0]) != float_width) + throw Exception("Cannot decompress. File has incorrect float width", ErrorCodes::CANNOT_DECOMPRESS); + if (static_cast(compressed_data[1]) != level) + throw Exception("Cannot decompress. File has incorrect compression level", ErrorCodes::CANNOT_DECOMPRESS); + if (decodeEndianness(static_cast(compressed_data[2])) != std::endian::native) + throw Exception("Cannot decompress. File has incorrect endianness", ErrorCodes::CANNOT_DECOMPRESS); + + auto destination = std::as_writable_bytes(std::span(dest, uncompressed_size)); + auto src = std::as_bytes(compressed_data.subspan(HEADER_SIZE)); + switch (float_width) + { + case sizeof(Float64): + FPCOperation(destination, level).decode(src, uncompressed_size); + break; + case sizeof(Float32): + FPCOperation(destination, level).decode(src, uncompressed_size); + break; + default: + break; + } +} + +} diff --git a/src/Compression/CompressionFactory.cpp b/src/Compression/CompressionFactory.cpp index abf5e38a8c3..b8a1c5877a4 100644 --- a/src/Compression/CompressionFactory.cpp +++ b/src/Compression/CompressionFactory.cpp @@ -177,6 +177,7 @@ void registerCodecT64(CompressionCodecFactory & factory); void registerCodecDoubleDelta(CompressionCodecFactory & factory); void registerCodecGorilla(CompressionCodecFactory & factory); void registerCodecEncrypted(CompressionCodecFactory & factory); +void registerCodecFPC(CompressionCodecFactory & factory); #endif @@ -194,6 +195,7 @@ CompressionCodecFactory::CompressionCodecFactory() registerCodecDoubleDelta(*this); registerCodecGorilla(*this); registerCodecEncrypted(*this); + registerCodecFPC(*this); #endif default_codec = get("LZ4", {}); diff --git a/src/Compression/CompressionInfo.h b/src/Compression/CompressionInfo.h index bbe8315f3ea..839fb68e8c3 100644 --- a/src/Compression/CompressionInfo.h +++ b/src/Compression/CompressionInfo.h @@ -44,7 +44,8 @@ enum class CompressionMethodByte : uint8_t DoubleDelta = 0x94, Gorilla = 0x95, AES_128_GCM_SIV = 0x96, - AES_256_GCM_SIV = 0x97 + AES_256_GCM_SIV = 0x97, + FPC = 0x98 }; } From adf888811cf036612ceb33ac245a47eb6a24e689 Mon Sep 17 00:00:00 2001 From: koloshmet Date: Thu, 26 May 2022 04:53:07 +0300 Subject: [PATCH 038/204] fixed max size computation --- src/Compression/CompressionCodecFPC.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index 3c7aac794c0..0e50d62893c 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -72,7 +72,7 @@ UInt32 CompressionCodecFPC::getMaxCompressedDataSize(UInt32 uncompressed_size) c if (float_count % 2 != 0) { ++float_count; } - return HEADER_SIZE + (float_count + float_count / 2) * float_width; + return HEADER_SIZE + float_count * float_width + float_count / 2; } namespace @@ -453,7 +453,8 @@ UInt32 CompressionCodecFPC::doCompressData(const char * source, UInt32 source_si dest[1] = static_cast(level); dest[2] = static_cast(encodeEndianness(std::endian::native)); - auto destination = std::as_writable_bytes(std::span(dest, source_size).subspan(HEADER_SIZE)); + auto dest_size = getMaxCompressedDataSize(source_size); + auto destination = std::as_writable_bytes(std::span(dest, dest_size).subspan(HEADER_SIZE)); auto src = std::as_bytes(std::span(source, source_size)); switch (float_width) { From cd3e28e3b1e4c8881c642c29d3fcf59bbf25e621 Mon Sep 17 00:00:00 2001 From: koloshmet Date: Thu, 26 May 2022 06:53:48 +0300 Subject: [PATCH 039/204] fixed decoding parameters --- src/Compression/CompressionCodecFPC.cpp | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index 0e50d62893c..d6b96f8a2f2 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -124,11 +124,11 @@ void registerCodecFPC(CompressionCodecFactory & factory) auto method_code = static_cast(CompressionMethodByte::FPC); auto codec_builder = [&](const ASTPtr & arguments, const IDataType * column_type) -> CompressionCodecPtr { - if (!column_type) - { - throw Exception("FPC codec must have associated column", ErrorCodes::BAD_ARGUMENTS); - } - UInt8 level{0}; + UInt8 float_width{0}; + if (column_type != nullptr) + float_width = getFloatBytesSize(*column_type); + + UInt8 level{12}; if (arguments && !arguments->children.empty()) { if (arguments->children.size() > 1) @@ -139,13 +139,11 @@ void registerCodecFPC(CompressionCodecFactory & factory) const auto * literal = arguments->children.front()->as(); if (!literal) - { throw Exception("FPC codec argument must be integer", ErrorCodes::ILLEGAL_CODEC_PARAMETER); - } level = literal->value.safeGet(); } - return std::make_shared(getFloatBytesSize(*column_type), level); + return std::make_shared(float_width, level); }; factory.registerCompressionCodecWithType("FPC", method_code, codec_builder); } @@ -474,26 +472,28 @@ void CompressionCodecFPC::doDecompressData(const char * source, UInt32 source_si throw Exception("Cannot decompress. File has wrong header", ErrorCodes::CANNOT_DECOMPRESS); auto compressed_data = std::span(source, source_size); - if (static_cast(compressed_data[0]) != float_width) - throw Exception("Cannot decompress. File has incorrect float width", ErrorCodes::CANNOT_DECOMPRESS); - if (static_cast(compressed_data[1]) != level) - throw Exception("Cannot decompress. File has incorrect compression level", ErrorCodes::CANNOT_DECOMPRESS); if (decodeEndianness(static_cast(compressed_data[2])) != std::endian::native) throw Exception("Cannot decompress. File has incorrect endianness", ErrorCodes::CANNOT_DECOMPRESS); + auto compressed_float_width = static_cast(compressed_data[0]); + auto compressed_level = static_cast(compressed_data[1]); + if (compressed_level == 0) + throw Exception("Cannot decompress. File has incorrect level", ErrorCodes::CANNOT_DECOMPRESS); + auto destination = std::as_writable_bytes(std::span(dest, uncompressed_size)); auto src = std::as_bytes(compressed_data.subspan(HEADER_SIZE)); - switch (float_width) + switch (compressed_float_width) { case sizeof(Float64): - FPCOperation(destination, level).decode(src, uncompressed_size); + FPCOperation(destination, compressed_level).decode(src, uncompressed_size); break; case sizeof(Float32): - FPCOperation(destination, level).decode(src, uncompressed_size); + FPCOperation(destination, compressed_level).decode(src, uncompressed_size); break; default: break; } + throw Exception("Cannot decompress. File has incorrect float width", ErrorCodes::CANNOT_DECOMPRESS); } } From 1bfeb982af9bffea3c42ff2f87daa99ea016f83e Mon Sep 17 00:00:00 2001 From: koloshmet Date: Thu, 26 May 2022 06:58:34 +0300 Subject: [PATCH 040/204] fixed decoding parameters --- src/Compression/CompressionCodecFPC.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index d6b96f8a2f2..269a935da4b 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -491,9 +491,8 @@ void CompressionCodecFPC::doDecompressData(const char * source, UInt32 source_si FPCOperation(destination, compressed_level).decode(src, uncompressed_size); break; default: - break; + throw Exception("Cannot decompress. File has incorrect float width", ErrorCodes::CANNOT_DECOMPRESS); } - throw Exception("Cannot decompress. File has incorrect float width", ErrorCodes::CANNOT_DECOMPRESS); } } From d9dd11bb66d30d644cf2f0239839c82932454c27 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 26 May 2022 08:14:55 +0200 Subject: [PATCH 041/204] Revert "Revert "Add support for preprocessing ZooKeeper operations in `clickhouse-keeper`"" --- src/Coordination/KeeperServer.cpp | 17 + src/Coordination/KeeperStateMachine.cpp | 24 +- src/Coordination/KeeperStateMachine.h | 8 +- src/Coordination/KeeperStorage.cpp | 1744 +++++++++++------ src/Coordination/KeeperStorage.h | 250 ++- .../WriteBufferFromNuraftBuffer.h | 1 - src/Coordination/ZooKeeperDataReader.cpp | 1 + src/Coordination/tests/gtest_coordination.cpp | 130 ++ 8 files changed, 1487 insertions(+), 688 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 5f77e996744..d74ad173811 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -315,6 +316,22 @@ void KeeperServer::startup(const Poco::Util::AbstractConfiguration & config, boo state_manager->loadLogStore(state_machine->last_commit_index() + 1, coordination_settings->reserved_log_items); + auto log_store = state_manager->load_log_store(); + auto next_log_idx = log_store->next_slot(); + if (next_log_idx > 0 && next_log_idx > state_machine->last_commit_index()) + { + auto log_entries = log_store->log_entries(state_machine->last_commit_index() + 1, next_log_idx); + + auto idx = state_machine->last_commit_index() + 1; + for (const auto & entry : *log_entries) + { + if (entry && entry->get_val_type() == nuraft::log_val_type::app_log) + state_machine->preprocess(idx, entry->get_buf()); + + ++idx; + } + } + loadLatestConfig(); last_local_config = state_manager->parseServersConfiguration(config, true).cluster_config; diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index be7110fa841..fa3a5195226 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -44,7 +44,6 @@ namespace else /// backward compatibility request_for_session.time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - return request_for_session; } } @@ -114,6 +113,21 @@ void KeeperStateMachine::init() storage = std::make_unique(coordination_settings->dead_session_check_period_ms.totalMilliseconds(), superdigest); } +nuraft::ptr KeeperStateMachine::pre_commit(uint64_t log_idx, nuraft::buffer & data) +{ + preprocess(log_idx, data); + return nullptr; +} + +void KeeperStateMachine::preprocess(const uint64_t log_idx, nuraft::buffer & data) +{ + auto request_for_session = parseRequest(data); + if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) + return; + std::lock_guard lock(storage_and_responses_lock); + storage->preprocessRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, log_idx); +} + nuraft::ptr KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data) { auto request_for_session = parseRequest(data); @@ -182,6 +196,12 @@ void KeeperStateMachine::commit_config(const uint64_t /* log_idx */, nuraft::ptr cluster_config = ClusterConfig::deserialize(*tmp); } +void KeeperStateMachine::rollback(uint64_t log_idx, nuraft::buffer & /*data*/) +{ + std::lock_guard lock(storage_and_responses_lock); + storage->rollbackRequest(log_idx); +} + nuraft::ptr KeeperStateMachine::last_snapshot() { /// Just return the latest snapshot. @@ -343,7 +363,7 @@ void KeeperStateMachine::processReadRequest(const KeeperStorage::RequestForSessi { /// Pure local request, just process it with storage std::lock_guard lock(storage_and_responses_lock); - auto responses = storage->processRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, std::nullopt); + auto responses = storage->processRequest(request_for_session.request, request_for_session.session_id, request_for_session.time, std::nullopt, true /*check_acl*/, true /*is_local*/); for (const auto & response : responses) if (!responses_queue.push(response)) throw Exception(ErrorCodes::SYSTEM_ERROR, "Could not push response with session id {} into responses queue", response.session_id); diff --git a/src/Coordination/KeeperStateMachine.h b/src/Coordination/KeeperStateMachine.h index 73578e6a2ba..aed96a59c13 100644 --- a/src/Coordination/KeeperStateMachine.h +++ b/src/Coordination/KeeperStateMachine.h @@ -27,16 +27,16 @@ public: /// Read state from the latest snapshot void init(); - /// Currently not supported - nuraft::ptr pre_commit(const uint64_t /*log_idx*/, nuraft::buffer & /*data*/) override { return nullptr; } + void preprocess(uint64_t log_idx, nuraft::buffer & data); + + nuraft::ptr pre_commit(uint64_t log_idx, nuraft::buffer & data) override; nuraft::ptr commit(const uint64_t log_idx, nuraft::buffer & data) override; /// NOLINT /// Save new cluster config to our snapshot (copy of the config stored in StateManager) void commit_config(const uint64_t log_idx, nuraft::ptr & new_conf) override; /// NOLINT - /// Currently not supported - void rollback(const uint64_t /*log_idx*/, nuraft::buffer & /*data*/) override {} + void rollback(uint64_t log_idx, nuraft::buffer & data) override; uint64_t last_commit_index() override { return last_committed_idx; } diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index f58776cf843..6c0699be95c 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -1,19 +1,21 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common/ZooKeeper/ZooKeeperConstants.h" +#include +#include +#include #include +#include namespace DB { @@ -49,37 +51,10 @@ String getSHA1(const String & userdata) String generateDigest(const String & userdata) { std::vector user_password; - boost::split(user_password, userdata, [](char c) { return c == ':'; }); + boost::split(user_password, userdata, [](char character) { return character == ':'; }); return user_password[0] + ":" + base64Encode(getSHA1(userdata)); } -bool checkACL(int32_t permission, const Coordination::ACLs & node_acls, const std::vector & session_auths) -{ - if (node_acls.empty()) - return true; - - for (const auto & session_auth : session_auths) - if (session_auth.scheme == "super") - return true; - - for (const auto & node_acl : node_acls) - { - if (node_acl.permissions & permission) - { - if (node_acl.scheme == "world" && node_acl.id == "anyone") - return true; - - for (const auto & session_auth : session_auths) - { - if (node_acl.scheme == session_auth.scheme && node_acl.id == session_auth.id) - return true; - } - } - } - - return false; -} - bool fixupACL( const std::vector & request_acls, const std::vector & current_ids, @@ -122,11 +97,12 @@ bool fixupACL( return valid_found; } -KeeperStorage::ResponsesForSessions processWatchesImpl(const String & path, KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches, Coordination::Event event_type) +KeeperStorage::ResponsesForSessions processWatchesImpl( + const String & path, KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches, Coordination::Event event_type) { KeeperStorage::ResponsesForSessions result; - auto it = watches.find(path); - if (it != watches.end()) + auto watch_it = watches.find(path); + if (watch_it != watches.end()) { std::shared_ptr watch_response = std::make_shared(); watch_response->path = path; @@ -134,10 +110,10 @@ KeeperStorage::ResponsesForSessions processWatchesImpl(const String & path, Keep watch_response->zxid = -1; watch_response->type = event_type; watch_response->state = Coordination::State::CONNECTED; - for (auto watcher_session : it->second) + for (auto watcher_session : watch_it->second) result.push_back(KeeperStorage::ResponseForSession{watcher_session, watch_response}); - watches.erase(it); + watches.erase(watch_it); } auto parent_path = parentPath(path); @@ -156,10 +132,11 @@ KeeperStorage::ResponsesForSessions processWatchesImpl(const String & path, Keep for (const auto & path_to_check : paths_to_check_for_list_watches) { - it = list_watches.find(path_to_check); - if (it != list_watches.end()) + watch_it = list_watches.find(path_to_check); + if (watch_it != list_watches.end()) { - std::shared_ptr watch_list_response = std::make_shared(); + std::shared_ptr watch_list_response + = std::make_shared(); watch_list_response->path = path_to_check; watch_list_response->xid = Coordination::WATCH_XID; watch_list_response->zxid = -1; @@ -169,14 +146,15 @@ KeeperStorage::ResponsesForSessions processWatchesImpl(const String & path, Keep watch_list_response->type = Coordination::Event::DELETED; watch_list_response->state = Coordination::State::CONNECTED; - for (auto watcher_session : it->second) + for (auto watcher_session : watch_it->second) result.push_back(KeeperStorage::ResponseForSession{watcher_session, watch_list_response}); - list_watches.erase(it); + list_watches.erase(watch_it); } } return result; } + } void KeeperStorage::Node::setData(String new_data) @@ -198,24 +176,322 @@ void KeeperStorage::Node::removeChild(StringRef child_path) } KeeperStorage::KeeperStorage(int64_t tick_time_ms, const String & superdigest_) - : session_expiry_queue(tick_time_ms) - , superdigest(superdigest_) + : session_expiry_queue(tick_time_ms), superdigest(superdigest_) { container.insert("/", Node()); } -using Undo = std::function; +template +struct Overloaded : Ts... +{ + using Ts::operator()...; +}; + +// explicit deduction guide +// https://en.cppreference.com/w/cpp/language/class_template_argument_deduction +template +Overloaded(Ts...) -> Overloaded; + +std::shared_ptr KeeperStorage::UncommittedState::getNode(StringRef path) +{ + std::shared_ptr node{nullptr}; + + if (auto maybe_node_it = storage.container.find(path); maybe_node_it != storage.container.end()) + { + const auto & committed_node = maybe_node_it->value; + node = std::make_shared(); + node->stat = committed_node.stat; + node->seq_num = committed_node.seq_num; + node->setData(committed_node.getData()); + } + + applyDeltas( + path, + Overloaded{ + [&](const CreateNodeDelta & create_delta) + { + assert(!node); + node = std::make_shared(); + node->stat = create_delta.stat; + node->setData(create_delta.data); + }, + [&](const RemoveNodeDelta & /*remove_delta*/) + { + assert(node); + node = nullptr; + }, + [&](const UpdateNodeDelta & update_delta) + { + assert(node); + update_delta.update_fn(*node); + }, + [&](auto && /*delta*/) {}, + }); + + return node; +} + +bool KeeperStorage::UncommittedState::hasNode(StringRef path) const +{ + bool exists = storage.container.contains(std::string{path}); + applyDeltas( + path, + Overloaded{ + [&](const CreateNodeDelta & /*create_delta*/) + { + assert(!exists); + exists = true; + }, + [&](const RemoveNodeDelta & /*remove_delta*/) + { + assert(exists); + exists = false; + }, + [&](auto && /*delta*/) {}, + }); + + return exists; +} + +Coordination::ACLs KeeperStorage::UncommittedState::getACLs(StringRef path) const +{ + std::optional acl_id; + if (auto maybe_node_it = storage.container.find(path); maybe_node_it != storage.container.end()) + acl_id.emplace(maybe_node_it->value.acl_id); + + const Coordination::ACLs * acls{nullptr}; + applyDeltas( + path, + Overloaded{ + [&](const CreateNodeDelta & create_delta) + { + assert(!acl_id); + acls = &create_delta.acls; + }, + [&](const RemoveNodeDelta & /*remove_delta*/) + { + assert(acl_id || acls); + acl_id.reset(); + acls = nullptr; + }, + [&](const SetACLDelta & set_acl_delta) + { + assert(acl_id || acls); + acls = &set_acl_delta.acls; + }, + [&](auto && /*delta*/) {}, + }); + + if (acls) + return *acls; + + return acl_id ? storage.acl_map.convertNumber(*acl_id) : Coordination::ACLs{}; +} + +namespace +{ + +[[noreturn]] void onStorageInconsistency() +{ + LOG_ERROR(&Poco::Logger::get("KeeperStorage"), "Inconsistency found between uncommitted and committed data. Keeper will terminate to avoid undefined behaviour."); + std::terminate(); +} + +} + +Coordination::Error KeeperStorage::commit(int64_t commit_zxid, int64_t session_id) +{ + // Deltas are added with increasing ZXIDs + // If there are no deltas for the commit_zxid (e.g. read requests), we instantly return + // on first delta + for (auto & delta : uncommitted_state.deltas) + { + if (delta.zxid > commit_zxid) + break; + + bool finish_subdelta = false; + auto result = std::visit( + [&, &path = delta.path](DeltaType & operation) -> Coordination::Error + { + if constexpr (std::same_as) + { + if (!createNode( + path, + std::move(operation.data), + operation.stat, + operation.is_sequental, + operation.is_ephemeral, + std::move(operation.acls), + session_id)) + onStorageInconsistency(); + + return Coordination::Error::ZOK; + } + else if constexpr (std::same_as) + { + auto node_it = container.find(path); + if (node_it == container.end()) + onStorageInconsistency(); + + if (operation.version != -1 && operation.version != node_it->value.stat.version) + onStorageInconsistency(); + + container.updateValue(path, operation.update_fn); + return Coordination::Error::ZOK; + } + else if constexpr (std::same_as) + { + if (!removeNode(path, operation.version)) + onStorageInconsistency(); + + return Coordination::Error::ZOK; + } + else if constexpr (std::same_as) + { + auto node_it = container.find(path); + if (node_it == container.end()) + onStorageInconsistency(); + + if (operation.version != -1 && operation.version != node_it->value.stat.aversion) + onStorageInconsistency(); + + acl_map.removeUsage(node_it->value.acl_id); + + uint64_t acl_id = acl_map.convertACLs(operation.acls); + acl_map.addUsage(acl_id); + + container.updateValue(path, [acl_id](KeeperStorage::Node & node) { node.acl_id = acl_id; }); + + return Coordination::Error::ZOK; + } + else if constexpr (std::same_as) + return operation.error; + else if constexpr (std::same_as) + { + finish_subdelta = true; + return Coordination::Error::ZOK; + } + else if constexpr (std::same_as) + { + session_and_auth[operation.session_id].emplace_back(std::move(operation.auth_id)); + return Coordination::Error::ZOK; + } + else + { + // shouldn't be called in any process functions + onStorageInconsistency(); + } + }, + delta.operation); + + if (result != Coordination::Error::ZOK) + return result; + + if (finish_subdelta) + return Coordination::Error::ZOK; + } + + return Coordination::Error::ZOK; +} + +bool KeeperStorage::createNode( + const std::string & path, + String data, + const Coordination::Stat & stat, + bool is_sequental, + bool is_ephemeral, + Coordination::ACLs node_acls, + int64_t session_id) +{ + auto parent_path = parentPath(path); + auto node_it = container.find(parent_path); + + if (node_it == container.end()) + return false; + + if (node_it->value.stat.ephemeralOwner != 0) + return false; + + if (container.contains(path)) + return false; + + KeeperStorage::Node created_node; + + uint64_t acl_id = acl_map.convertACLs(node_acls); + acl_map.addUsage(acl_id); + + created_node.acl_id = acl_id; + created_node.stat = stat; + created_node.setData(std::move(data)); + created_node.is_sequental = is_sequental; + auto [map_key, _] = container.insert(path, created_node); + /// Take child path from key owned by map. + auto child_path = getBaseName(map_key->getKey()); + container.updateValue(parent_path, [child_path](KeeperStorage::Node & parent) { parent.addChild(child_path); }); + + if (is_ephemeral) + ephemerals[session_id].emplace(path); + + return true; +}; + +bool KeeperStorage::removeNode(const std::string & path, int32_t version) +{ + auto node_it = container.find(path); + if (node_it == container.end()) + return false; + + if (version != -1 && version != node_it->value.stat.version) + return false; + + if (node_it->value.stat.numChildren) + return false; + + auto prev_node = node_it->value; + if (prev_node.stat.ephemeralOwner != 0) + { + auto ephemerals_it = ephemerals.find(prev_node.stat.ephemeralOwner); + ephemerals_it->second.erase(path); + if (ephemerals_it->second.empty()) + ephemerals.erase(ephemerals_it); + } + + acl_map.removeUsage(prev_node.acl_id); + + container.updateValue( + parentPath(path), + [child_basename = getBaseName(node_it->key)](KeeperStorage::Node & parent) { parent.removeChild(child_basename); }); + + container.erase(path); + return true; +} + struct KeeperStorageRequestProcessor { Coordination::ZooKeeperRequestPtr zk_request; - explicit KeeperStorageRequestProcessor(const Coordination::ZooKeeperRequestPtr & zk_request_) - : zk_request(zk_request_) - {} - virtual std::pair process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const = 0; - virtual KeeperStorage::ResponsesForSessions processWatches(KeeperStorage::Watches & /*watches*/, KeeperStorage::Watches & /*list_watches*/) const { return {}; } - virtual bool checkAuth(KeeperStorage & /*storage*/, int64_t /*session_id*/) const { return true; } + explicit KeeperStorageRequestProcessor(const Coordination::ZooKeeperRequestPtr & zk_request_) : zk_request(zk_request_) { } + virtual Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const = 0; + virtual std::vector + preprocess(KeeperStorage & /*storage*/, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /*time*/) const + { + return {}; + } + + // process the request using locally committed data + virtual Coordination::ZooKeeperResponsePtr + processLocal(KeeperStorage & /*storage*/, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /*time*/) const + { + throw Exception{DB::ErrorCodes::LOGICAL_ERROR, "Cannot process the request locally"}; + } + + virtual KeeperStorage::ResponsesForSessions + processWatches(KeeperStorage::Watches & /*watches*/, KeeperStorage::Watches & /*list_watches*/) const + { + return {}; + } + virtual bool checkAuth(KeeperStorage & /*storage*/, int64_t /*session_id*/, bool /*is_local*/) const { return true; } virtual ~KeeperStorageRequestProcessor() = default; }; @@ -223,331 +499,328 @@ struct KeeperStorageRequestProcessor struct KeeperStorageHeartbeatRequestProcessor final : public KeeperStorageRequestProcessor { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & /* storage */, int64_t /* zxid */, int64_t /* session_id */, int64_t /* time */) const override + Coordination::ZooKeeperResponsePtr + process(KeeperStorage & /* storage */, int64_t /* zxid */, int64_t /* session_id */, int64_t /* time */) const override { - return {zk_request->makeResponse(), {}}; + return zk_request->makeResponse(); } }; struct KeeperStorageSyncRequestProcessor final : public KeeperStorageRequestProcessor { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & /* storage */, int64_t /* zxid */, int64_t /* session_id */, int64_t /* time */) const override + Coordination::ZooKeeperResponsePtr + process(KeeperStorage & /* storage */, int64_t /* zxid */, int64_t /* session_id */, int64_t /* time */) const override { auto response = zk_request->makeResponse(); dynamic_cast(*response).path = dynamic_cast(*zk_request).path; - return {response, {}}; - } -}; - -struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestProcessor -{ - using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - - KeeperStorage::ResponsesForSessions processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override - { - return processWatchesImpl(zk_request->getPath(), watches, list_watches, Coordination::Event::CREATED); - } - - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override - { - auto & container = storage.container; - auto path = zk_request->getPath(); - auto parent_path = parentPath(path); - - auto it = container.find(parent_path); - if (it == container.end()) - return true; - - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; - - const auto & session_auths = storage.session_and_auth[session_id]; - return checkACL(Coordination::ACL::Create, node_acls, session_auths); - } - - std::pair process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override - { - auto & container = storage.container; - auto & ephemerals = storage.ephemerals; - - Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); - Undo undo; - Coordination::ZooKeeperCreateResponse & response = dynamic_cast(*response_ptr); - Coordination::ZooKeeperCreateRequest & request = dynamic_cast(*zk_request); - - auto parent_path = parentPath(request.path); - auto it = container.find(parent_path); - - if (it == container.end()) - { - response.error = Coordination::Error::ZNONODE; - return { response_ptr, undo }; - } - else if (it->value.stat.ephemeralOwner != 0) - { - response.error = Coordination::Error::ZNOCHILDRENFOREPHEMERALS; - return { response_ptr, undo }; - } - std::string path_created = request.path; - if (request.is_sequential) - { - auto seq_num = it->value.seq_num; - - std::stringstream seq_num_str; // STYLE_CHECK_ALLOW_STD_STRING_STREAM - seq_num_str.exceptions(std::ios::failbit); - seq_num_str << std::setw(10) << std::setfill('0') << seq_num; - - path_created += seq_num_str.str(); - } - if (container.contains(path_created)) - { - response.error = Coordination::Error::ZNODEEXISTS; - return { response_ptr, undo }; - } - if (getBaseName(path_created).size == 0) - { - response.error = Coordination::Error::ZBADARGUMENTS; - return { response_ptr, undo }; - } - - auto & session_auth_ids = storage.session_and_auth[session_id]; - - KeeperStorage::Node created_node; - - Coordination::ACLs node_acls; - if (!fixupACL(request.acls, session_auth_ids, node_acls)) - { - response.error = Coordination::Error::ZINVALIDACL; - return {response_ptr, {}}; - } - - uint64_t acl_id = storage.acl_map.convertACLs(node_acls); - storage.acl_map.addUsage(acl_id); - - created_node.acl_id = acl_id; - created_node.stat.czxid = zxid; - created_node.stat.mzxid = zxid; - created_node.stat.pzxid = zxid; - created_node.stat.ctime = time; - created_node.stat.mtime = time; - created_node.stat.numChildren = 0; - created_node.stat.dataLength = request.data.length(); - created_node.stat.ephemeralOwner = request.is_ephemeral ? session_id : 0; - created_node.is_sequental = request.is_sequential; - created_node.setData(std::move(request.data)); - - auto [map_key, _] = container.insert(path_created, created_node); - /// Take child path from key owned by map. - auto child_path = getBaseName(map_key->getKey()); - - int32_t parent_cversion = request.parent_cversion; - int64_t prev_parent_zxid; - int32_t prev_parent_cversion; - container.updateValue(parent_path, [child_path, zxid, &prev_parent_zxid, - parent_cversion, &prev_parent_cversion] (KeeperStorage::Node & parent) - { - parent.addChild(child_path); - prev_parent_cversion = parent.stat.cversion; - prev_parent_zxid = parent.stat.pzxid; - - /// Increment sequential number even if node is not sequential - ++parent.seq_num; - - if (parent_cversion == -1) - ++parent.stat.cversion; - else if (parent_cversion > parent.stat.cversion) - parent.stat.cversion = parent_cversion; - - if (zxid > parent.stat.pzxid) - parent.stat.pzxid = zxid; - ++parent.stat.numChildren; - }); - - response.path_created = path_created; - - if (request.is_ephemeral) - ephemerals[session_id].emplace(path_created); - - undo = [&storage, prev_parent_zxid, prev_parent_cversion, session_id, path_created, is_ephemeral = request.is_ephemeral, parent_path, child_path, acl_id] - { - storage.acl_map.removeUsage(acl_id); - - if (is_ephemeral) - storage.ephemerals[session_id].erase(path_created); - - storage.container.updateValue(parent_path, [child_path, prev_parent_zxid, prev_parent_cversion] (KeeperStorage::Node & undo_parent) - { - --undo_parent.stat.numChildren; - --undo_parent.seq_num; - undo_parent.stat.cversion = prev_parent_cversion; - undo_parent.stat.pzxid = prev_parent_zxid; - undo_parent.removeChild(child_path); - }); - - storage.container.erase(path_created); - }; - - response.error = Coordination::Error::ZOK; - return { response_ptr, undo }; - } -}; - -struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProcessor -{ - - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override - { - auto & container = storage.container; - auto it = container.find(zk_request->getPath()); - if (it == container.end()) - return true; - - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; - - const auto & session_auths = storage.session_and_auth[session_id]; - return checkACL(Coordination::ACL::Read, node_acls, session_auths); - } - - using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t /* zxid */, int64_t /* session_id */, int64_t /* time */) const override - { - auto & container = storage.container; - Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); - Coordination::ZooKeeperGetResponse & response = dynamic_cast(*response_ptr); - Coordination::ZooKeeperGetRequest & request = dynamic_cast(*zk_request); - - auto it = container.find(request.path); - if (it == container.end()) - { - response.error = Coordination::Error::ZNONODE; - } - else - { - response.stat = it->value.stat; - response.data = it->value.getData(); - response.error = Coordination::Error::ZOK; - } - - return { response_ptr, {} }; + return response; } }; namespace { - /// Garbage required to apply log to "fuzzy" zookeeper snapshot - void updateParentPzxid(const std::string & child_path, int64_t zxid, KeeperStorage::Container & container) + + Coordination::ACLs getNodeACLs(KeeperStorage & storage, StringRef path, bool is_local) { - auto parent_path = parentPath(child_path); - auto parent_it = container.find(parent_path); - if (parent_it != container.end()) + if (is_local) { - container.updateValue(parent_path, [zxid](KeeperStorage::Node & parent) - { - if (parent.stat.pzxid < zxid) - parent.stat.pzxid = zxid; - }); + auto node_it = storage.container.find(path); + if (node_it == storage.container.end()) + return {}; + + return storage.acl_map.convertNumber(node_it->value.acl_id); + } + + return storage.uncommitted_state.getACLs(path); + } + +} +bool KeeperStorage::checkACL(StringRef path, int32_t permission, int64_t session_id, bool is_local) +{ + const auto node_acls = getNodeACLs(*this, path, is_local); + if (node_acls.empty()) + return true; + + if (uncommitted_state.hasACL(session_id, is_local, [](const auto & auth_id) { return auth_id.scheme == "super"; })) + return true; + + + for (const auto & node_acl : node_acls) + { + if (node_acl.permissions & permission) + { + if (node_acl.scheme == "world" && node_acl.id == "anyone") + return true; + + if (uncommitted_state.hasACL( + session_id, + is_local, + [&](const auto & auth_id) { return auth_id.scheme == node_acl.scheme && auth_id.id == node_acl.id; })) + return true; } } + + return false; } -struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestProcessor + +struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestProcessor { - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override + using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; + + KeeperStorage::ResponsesForSessions + processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override { - auto & container = storage.container; - auto it = container.find(parentPath(zk_request->getPath())); - if (it == container.end()) - return true; + return processWatchesImpl(zk_request->getPath(), watches, list_watches, Coordination::Event::CREATED); + } - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override + { + auto path = zk_request->getPath(); + return storage.checkACL(parentPath(path), Coordination::ACL::Create, session_id, is_local); + } - const auto & session_auths = storage.session_and_auth[session_id]; - return checkACL(Coordination::ACL::Delete, node_acls, session_auths); + std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + Coordination::ZooKeeperCreateRequest & request = dynamic_cast(*zk_request); + + std::vector new_deltas; + + auto parent_path = parentPath(request.path); + auto parent_node = storage.uncommitted_state.getNode(parent_path); + if (parent_node == nullptr) + return {{zxid, Coordination::Error::ZNONODE}}; + + else if (parent_node->stat.ephemeralOwner != 0) + return {{zxid, Coordination::Error::ZNOCHILDRENFOREPHEMERALS}}; + + std::string path_created = request.path; + if (request.is_sequential) + { + auto seq_num = parent_node->seq_num; + + std::stringstream seq_num_str; // STYLE_CHECK_ALLOW_STD_STRING_STREAM + seq_num_str.exceptions(std::ios::failbit); + seq_num_str << std::setw(10) << std::setfill('0') << seq_num; + + path_created += seq_num_str.str(); + } + + if (storage.uncommitted_state.hasNode(path_created)) + return {{zxid, Coordination::Error::ZNODEEXISTS}}; + + if (getBaseName(path_created).size == 0) + return {{zxid, Coordination::Error::ZBADARGUMENTS}}; + + Coordination::ACLs node_acls; + if (!fixupACL(request.acls, storage.session_and_auth[session_id], node_acls)) + return {{zxid, Coordination::Error::ZINVALIDACL}}; + + Coordination::Stat stat; + stat.czxid = zxid; + stat.mzxid = zxid; + stat.pzxid = zxid; + stat.ctime = time; + stat.mtime = time; + stat.numChildren = 0; + stat.version = 0; + stat.aversion = 0; + stat.cversion = 0; + stat.dataLength = request.data.length(); + stat.ephemeralOwner = request.is_ephemeral ? session_id : 0; + + new_deltas.emplace_back( + std::move(path_created), + zxid, + KeeperStorage::CreateNodeDelta{stat, request.is_ephemeral, request.is_sequential, std::move(node_acls), request.data}); + + int32_t parent_cversion = request.parent_cversion; + + new_deltas.emplace_back( + std::string{parent_path}, + zxid, + KeeperStorage::UpdateNodeDelta{[parent_cversion, zxid](KeeperStorage::Node & node) + { + ++node.seq_num; + if (parent_cversion == -1) + ++node.stat.cversion; + else if (parent_cversion > node.stat.cversion) + node.stat.cversion = parent_cversion; + + if (zxid > node.stat.pzxid) + node.stat.pzxid = zxid; + ++node.stat.numChildren; + }}); + return new_deltas; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override + { + Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); + Coordination::ZooKeeperCreateResponse & response = dynamic_cast(*response_ptr); + + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + { + response.error = result; + return response_ptr; + } + + const auto & deltas = storage.uncommitted_state.deltas; + auto create_delta_it = std::find_if( + deltas.begin(), + deltas.end(), + [zxid](const auto & delta) + { return delta.zxid == zxid && std::holds_alternative(delta.operation); }); + + assert(create_delta_it != deltas.end()); + + response.path_created = create_delta_it->path; + response.error = Coordination::Error::ZOK; + return response_ptr; + } +}; + +struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProcessor +{ + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override + { + return storage.checkACL(zk_request->getPath(), Coordination::ACL::Read, session_id, is_local); } using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /* time */) const override + + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override { - auto & container = storage.container; - auto & ephemerals = storage.ephemerals; + Coordination::ZooKeeperGetRequest & request = dynamic_cast(*zk_request); + if (!storage.uncommitted_state.hasNode(request.path)) + return {{zxid, Coordination::Error::ZNONODE}}; + + return {}; + } + + template + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); - Coordination::ZooKeeperRemoveResponse & response = dynamic_cast(*response_ptr); - Coordination::ZooKeeperRemoveRequest & request = dynamic_cast(*zk_request); - Undo undo; + Coordination::ZooKeeperGetResponse & response = dynamic_cast(*response_ptr); + Coordination::ZooKeeperGetRequest & request = dynamic_cast(*zk_request); - auto it = container.find(request.path); - if (it == container.end()) + if constexpr (!local) { - if (request.restored_from_zookeeper_log) - updateParentPzxid(request.path, zxid, container); - response.error = Coordination::Error::ZNONODE; + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + { + response.error = result; + return response_ptr; + } } - else if (request.version != -1 && request.version != it->value.stat.version) + + auto & container = storage.container; + auto node_it = container.find(request.path); + if (node_it == container.end()) { - response.error = Coordination::Error::ZBADVERSION; - } - else if (it->value.stat.numChildren) - { - response.error = Coordination::Error::ZNOTEMPTY; + if constexpr (local) + response.error = Coordination::Error::ZNONODE; + else + onStorageInconsistency(); } else { - if (request.restored_from_zookeeper_log) - updateParentPzxid(request.path, zxid, container); - - auto prev_node = it->value; - if (prev_node.stat.ephemeralOwner != 0) - { - auto ephemerals_it = ephemerals.find(prev_node.stat.ephemeralOwner); - ephemerals_it->second.erase(request.path); - if (ephemerals_it->second.empty()) - ephemerals.erase(ephemerals_it); - } - - storage.acl_map.removeUsage(prev_node.acl_id); - - container.updateValue(parentPath(request.path), [child_basename = getBaseName(it->key)] (KeeperStorage::Node & parent) - { - --parent.stat.numChildren; - ++parent.stat.cversion; - parent.removeChild(child_basename); - }); - + response.stat = node_it->value.stat; + response.data = node_it->value.getData(); response.error = Coordination::Error::ZOK; - /// Erase full path from container after child removed from parent - container.erase(request.path); - - undo = [prev_node, &storage, path = request.path] - { - if (prev_node.stat.ephemeralOwner != 0) - storage.ephemerals[prev_node.stat.ephemeralOwner].emplace(path); - - storage.acl_map.addUsage(prev_node.acl_id); - - /// Dangerous place: we are adding StringRef to child into children unordered_hash set. - /// That's why we are taking getBaseName from inserted key, not from the path from request object. - auto [map_key, _] = storage.container.insert(path, prev_node); - storage.container.updateValue(parentPath(path), [child_name = getBaseName(map_key->getKey())] (KeeperStorage::Node & parent) - { - ++parent.stat.numChildren; - --parent.stat.cversion; - parent.addChild(child_name); - }); - }; } - return { response_ptr, undo }; + return response_ptr; } - KeeperStorage::ResponsesForSessions processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); + } + + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); + } +}; + +struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestProcessor +{ + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override + { + return storage.checkACL(parentPath(zk_request->getPath()), Coordination::ACL::Delete, session_id, is_local); + } + + using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + { + Coordination::ZooKeeperRemoveRequest & request = dynamic_cast(*zk_request); + + std::vector new_deltas; + + const auto update_parent_pzxid = [&]() + { + auto parent_path = parentPath(request.path); + if (!storage.uncommitted_state.hasNode(parent_path)) + return; + + new_deltas.emplace_back( + std::string{parent_path}, + zxid, + KeeperStorage::UpdateNodeDelta{[zxid](KeeperStorage::Node & parent) + { + if (parent.stat.pzxid < zxid) + parent.stat.pzxid = zxid; + }}); + }; + + auto node = storage.uncommitted_state.getNode(request.path); + + if (!node) + { + if (request.restored_from_zookeeper_log) + update_parent_pzxid(); + return {{zxid, Coordination::Error::ZNONODE}}; + } + else if (request.version != -1 && request.version != node->stat.version) + return {{zxid, Coordination::Error::ZBADVERSION}}; + else if (node->stat.numChildren) + return {{zxid, Coordination::Error::ZNOTEMPTY}}; + + if (request.restored_from_zookeeper_log) + update_parent_pzxid(); + + new_deltas.emplace_back( + std::string{parentPath(request.path)}, + zxid, + KeeperStorage::UpdateNodeDelta{[](KeeperStorage::Node & parent) + { + --parent.stat.numChildren; + ++parent.stat.cversion; + }}); + + new_deltas.emplace_back(request.path, zxid, KeeperStorage::RemoveNodeDelta{request.version}); + + return new_deltas; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const override + { + Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); + Coordination::ZooKeeperRemoveResponse & response = dynamic_cast(*response_ptr); + + response.error = storage.commit(zxid, session_id); + return response_ptr; + } + + KeeperStorage::ResponsesForSessions + processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override { return processWatchesImpl(zk_request->getPath(), watches, list_watches, Coordination::Event::DELETED); } @@ -556,101 +829,140 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr struct KeeperStorageExistsRequestProcessor final : public KeeperStorageRequestProcessor { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t /*zxid*/, int64_t /* session_id */, int64_t /* time */) const override - { - auto & container = storage.container; + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + { + Coordination::ZooKeeperExistsRequest & request = dynamic_cast(*zk_request); + + if (!storage.uncommitted_state.hasNode(request.path)) + return {{zxid, Coordination::Error::ZNONODE}}; + + return {}; + } + + template + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperExistsResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperExistsRequest & request = dynamic_cast(*zk_request); - auto it = container.find(request.path); - if (it != container.end()) + if constexpr (!local) { - response.stat = it->value.stat; - response.error = Coordination::Error::ZOK; + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + { + response.error = result; + return response_ptr; + } + } + + auto & container = storage.container; + auto node_it = container.find(request.path); + if (node_it == container.end()) + { + if constexpr (local) + response.error = Coordination::Error::ZNONODE; + else + onStorageInconsistency(); } else { - response.error = Coordination::Error::ZNONODE; + response.stat = node_it->value.stat; + response.error = Coordination::Error::ZOK; } - return { response_ptr, {} }; + return response_ptr; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); + } + + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); } }; struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProcessor { - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override { - auto & container = storage.container; - auto it = container.find(zk_request->getPath()); - if (it == container.end()) - return true; - - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; - - const auto & session_auths = storage.session_and_auth[session_id]; - return checkACL(Coordination::ACL::Write, node_acls, session_auths); + return storage.checkACL(zk_request->getPath(), Coordination::ACL::Write, session_id, is_local); } using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t zxid, int64_t /* session_id */, int64_t time) const override + std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t time) const override + { + Coordination::ZooKeeperSetRequest & request = dynamic_cast(*zk_request); + + std::vector new_deltas; + + if (!storage.uncommitted_state.hasNode(request.path)) + return {{zxid, Coordination::Error::ZNONODE}}; + + auto node = storage.uncommitted_state.getNode(request.path); + + if (request.version != -1 && request.version != node->stat.version) + return {{zxid, Coordination::Error::ZBADVERSION}}; + + new_deltas.emplace_back( + request.path, + zxid, + KeeperStorage::UpdateNodeDelta{ + [zxid, data = request.data, time](KeeperStorage::Node & value) + { + value.stat.version++; + value.stat.mzxid = zxid; + value.stat.mtime = time; + value.stat.dataLength = data.length(); + value.setData(data); + }, + request.version}); + + new_deltas.emplace_back( + parentPath(request.path).toString(), + zxid, + KeeperStorage::UpdateNodeDelta + { + [](KeeperStorage::Node & parent) + { + parent.stat.cversion++; + } + } + ); + + return new_deltas; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override { auto & container = storage.container; Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperSetResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperSetRequest & request = dynamic_cast(*zk_request); - Undo undo; - auto it = container.find(request.path); - if (it == container.end()) + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) { - response.error = Coordination::Error::ZNONODE; - } - else if (request.version == -1 || request.version == it->value.stat.version) - { - - auto prev_node = it->value; - - auto itr = container.updateValue(request.path, [zxid, request, time] (KeeperStorage::Node & value) mutable - { - value.stat.version++; - value.stat.mzxid = zxid; - value.stat.mtime = time; - value.stat.dataLength = request.data.length(); - value.setData(std::move(request.data)); - }); - - container.updateValue(parentPath(request.path), [] (KeeperStorage::Node & parent) - { - parent.stat.cversion++; - }); - - response.stat = itr->value.stat; - response.error = Coordination::Error::ZOK; - - undo = [prev_node, &container, path = request.path] - { - container.updateValue(path, [&prev_node] (KeeperStorage::Node & value) { value = prev_node; }); - container.updateValue(parentPath(path), [] (KeeperStorage::Node & parent) - { - parent.stat.cversion--; - }); - }; - } - else - { - response.error = Coordination::Error::ZBADVERSION; + response.error = result; + return response_ptr; } - return { response_ptr, undo }; + auto node_it = container.find(request.path); + if (node_it == container.end()) + onStorageInconsistency(); + + response.stat = node_it->value.stat; + response.error = Coordination::Error::ZOK; + + return response_ptr; } - KeeperStorage::ResponsesForSessions processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override + KeeperStorage::ResponsesForSessions + processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override { return processWatchesImpl(zk_request->getPath(), watches, list_watches, Coordination::Event::CHANGED); } @@ -658,33 +970,48 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce struct KeeperStorageListRequestProcessor final : public KeeperStorageRequestProcessor { - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override { - auto & container = storage.container; - auto it = container.find(zk_request->getPath()); - if (it == container.end()) - return true; - - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; - - const auto & session_auths = storage.session_and_auth[session_id]; - return checkACL(Coordination::ACL::Read, node_acls, session_auths); + return storage.checkACL(zk_request->getPath(), Coordination::ACL::Read, session_id, is_local); } using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /* time */) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + { + Coordination::ZooKeeperListRequest & request = dynamic_cast(*zk_request); + + if (!storage.uncommitted_state.hasNode(request.path)) + return {{zxid, Coordination::Error::ZNONODE}}; + + return {}; + } + + + template + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const { - auto & container = storage.container; Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperListResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperListRequest & request = dynamic_cast(*zk_request); - auto it = container.find(request.path); - if (it == container.end()) + if constexpr (!local) { - response.error = Coordination::Error::ZNONODE; + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + { + response.error = result; + return response_ptr; + } + } + + auto & container = storage.container; + auto node_it = container.find(request.path); + if (node_it == container.end()) + { + if constexpr (local) + response.error = Coordination::Error::ZNONODE; + else + onStorageInconsistency(); } else { @@ -692,174 +1019,247 @@ struct KeeperStorageListRequestProcessor final : public KeeperStorageRequestProc if (path_prefix.empty()) throw DB::Exception("Logical error: path cannot be empty", ErrorCodes::LOGICAL_ERROR); - const auto & children = it->value.getChildren(); + const auto & children = node_it->value.getChildren(); response.names.reserve(children.size()); for (const auto child : children) response.names.push_back(child.toString()); - response.stat = it->value.stat; + response.stat = node_it->value.stat; response.error = Coordination::Error::ZOK; } - return { response_ptr, {} }; + return response_ptr; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); + } + + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); } }; struct KeeperStorageCheckRequestProcessor final : public KeeperStorageRequestProcessor { - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override { - auto & container = storage.container; - auto it = container.find(zk_request->getPath()); - if (it == container.end()) - return true; - - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; - - const auto & session_auths = storage.session_and_auth[session_id]; - return checkACL(Coordination::ACL::Read, node_acls, session_auths); + return storage.checkACL(zk_request->getPath(), Coordination::ACL::Read, session_id, is_local); } using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /* time */) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override { - auto & container = storage.container; + Coordination::ZooKeeperCheckRequest & request = dynamic_cast(*zk_request); + if (!storage.uncommitted_state.hasNode(request.path)) + return {{zxid, Coordination::Error::ZNONODE}}; + + auto node = storage.uncommitted_state.getNode(request.path); + if (request.version != -1 && request.version != node->stat.version) + return {{zxid, Coordination::Error::ZBADVERSION}}; + + return {}; + } + + template + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const + { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperCheckResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperCheckRequest & request = dynamic_cast(*zk_request); - auto it = container.find(request.path); - if (it == container.end()) + + if constexpr (!local) { - response.error = Coordination::Error::ZNONODE; + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + { + response.error = result; + return response_ptr; + } } - else if (request.version != -1 && request.version != it->value.stat.version) + + const auto on_error = [&]([[maybe_unused]] const auto error_code) { - response.error = Coordination::Error::ZBADVERSION; + if constexpr (local) + response.error = error_code; + else + onStorageInconsistency(); + }; + + auto & container = storage.container; + auto node_it = container.find(request.path); + if (node_it == container.end()) + { + on_error(Coordination::Error::ZNONODE); + } + else if (request.version != -1 && request.version != node_it->value.stat.version) + { + on_error(Coordination::Error::ZBADVERSION); } else { response.error = Coordination::Error::ZOK; } - return { response_ptr, {} }; + return response_ptr; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); + } + + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); } }; struct KeeperStorageSetACLRequestProcessor final : public KeeperStorageRequestProcessor { - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override { - auto & container = storage.container; - auto it = container.find(zk_request->getPath()); - if (it == container.end()) - return true; - - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; - - const auto & session_auths = storage.session_and_auth[session_id]; - return checkACL(Coordination::ACL::Admin, node_acls, session_auths); + return storage.checkACL(zk_request->getPath(), Coordination::ACL::Admin, session_id, is_local); } using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t /*zxid*/, int64_t session_id, int64_t /* time */) const override + std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override { - auto & container = storage.container; + Coordination::ZooKeeperSetACLRequest & request = dynamic_cast(*zk_request); + auto & uncommitted_state = storage.uncommitted_state; + if (!uncommitted_state.hasNode(request.path)) + return {{zxid, Coordination::Error::ZNONODE}}; + + auto node = uncommitted_state.getNode(request.path); + + if (request.version != -1 && request.version != node->stat.aversion) + return {{zxid, Coordination::Error::ZBADVERSION}}; + + + auto & session_auth_ids = storage.session_and_auth[session_id]; + Coordination::ACLs node_acls; + + if (!fixupACL(request.acls, session_auth_ids, node_acls)) + return {{zxid, Coordination::Error::ZINVALIDACL}}; + + return + { + { + request.path, + zxid, + KeeperStorage::SetACLDelta{std::move(node_acls), request.version} + }, + { + request.path, + zxid, + KeeperStorage::UpdateNodeDelta + { + [](KeeperStorage::Node & n) { ++n.stat.aversion; } + } + } + }; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const override + { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperSetACLResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperSetACLRequest & request = dynamic_cast(*zk_request); - auto it = container.find(request.path); - if (it == container.end()) + + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) { - response.error = Coordination::Error::ZNONODE; - } - else if (request.version != -1 && request.version != it->value.stat.aversion) - { - response.error = Coordination::Error::ZBADVERSION; - } - else - { - auto & session_auth_ids = storage.session_and_auth[session_id]; - Coordination::ACLs node_acls; - - if (!fixupACL(request.acls, session_auth_ids, node_acls)) - { - response.error = Coordination::Error::ZINVALIDACL; - return {response_ptr, {}}; - } - - uint64_t acl_id = storage.acl_map.convertACLs(node_acls); - storage.acl_map.addUsage(acl_id); - - storage.container.updateValue(request.path, [acl_id] (KeeperStorage::Node & node) - { - node.acl_id = acl_id; - ++node.stat.aversion; - }); - - response.stat = it->value.stat; - response.error = Coordination::Error::ZOK; + response.error = result; + return response_ptr; } - /// It cannot be used insied multitransaction? - return { response_ptr, {} }; + auto node_it = storage.container.find(request.path); + if (node_it == storage.container.end()) + onStorageInconsistency(); + response.stat = node_it->value.stat; + response.error = Coordination::Error::ZOK; + + return response_ptr; } }; struct KeeperStorageGetACLRequestProcessor final : public KeeperStorageRequestProcessor { - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override { - auto & container = storage.container; - auto it = container.find(zk_request->getPath()); - if (it == container.end()) - return true; - - const auto & node_acls = storage.acl_map.convertNumber(it->value.acl_id); - if (node_acls.empty()) - return true; - - const auto & session_auths = storage.session_and_auth[session_id]; - /// LOL, GetACL require more permissions, then SetACL... - return checkACL(Coordination::ACL::Admin | Coordination::ACL::Read, node_acls, session_auths); + return storage.checkACL(zk_request->getPath(), Coordination::ACL::Admin | Coordination::ACL::Read, session_id, is_local); } + using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t /*zxid*/, int64_t /*session_id*/, int64_t /* time */) const override + std::vector + preprocess(KeeperStorage & storage, int64_t zxid, int64_t /*session_id*/, int64_t /*time*/) const override + { + Coordination::ZooKeeperGetACLRequest & request = dynamic_cast(*zk_request); + + if (!storage.uncommitted_state.hasNode(request.path)) + return {{zxid, Coordination::Error::ZNONODE}}; + + return {}; + } + + template + Coordination::ZooKeeperResponsePtr processImpl(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperGetACLResponse & response = dynamic_cast(*response_ptr); Coordination::ZooKeeperGetACLRequest & request = dynamic_cast(*zk_request); - auto & container = storage.container; - auto it = container.find(request.path); - if (it == container.end()) + + if constexpr (!local) { - response.error = Coordination::Error::ZNONODE; + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + { + response.error = result; + return response_ptr; + } + } + + auto & container = storage.container; + auto node_it = container.find(request.path); + if (node_it == container.end()) + { + if constexpr (local) + response.error = Coordination::Error::ZNONODE; + else + onStorageInconsistency(); } else { - response.stat = it->value.stat; - response.acl = storage.acl_map.convertNumber(it->value.acl_id); + response.stat = node_it->value.stat; + response.acl = storage.acl_map.convertNumber(node_it->value.acl_id); } - return {response_ptr, {}}; + return response_ptr; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); + } + + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + return processImpl(storage, zxid, session_id, time); } }; struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestProcessor { - bool checkAuth(KeeperStorage & storage, int64_t session_id) const override + bool checkAuth(KeeperStorage & storage, int64_t session_id, bool is_local) const override { for (const auto & concrete_request : concrete_requests) - if (!concrete_request->checkAuth(storage, session_id)) + if (!concrete_request->checkAuth(storage, session_id, is_local)) return false; return true; } @@ -889,65 +1289,124 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro concrete_requests.push_back(std::make_shared(sub_zk_request)); break; default: - throw DB::Exception(ErrorCodes::BAD_ARGUMENTS, "Illegal command as part of multi ZooKeeper request {}", sub_zk_request->getOpNum()); + throw DB::Exception( + ErrorCodes::BAD_ARGUMENTS, "Illegal command as part of multi ZooKeeper request {}", sub_zk_request->getOpNum()); } } } - std::pair process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + // manually add deltas so that the result of previous request in the transaction is used in the next request + auto & saved_deltas = storage.uncommitted_state.deltas; + + std::vector response_errors; + response_errors.reserve(concrete_requests.size()); + for (size_t i = 0; i < concrete_requests.size(); ++i) + { + auto new_deltas = concrete_requests[i]->preprocess(storage, zxid, session_id, time); + + if (!new_deltas.empty()) + { + if (auto * error = std::get_if(&new_deltas.back().operation)) + { + std::erase_if(saved_deltas, [zxid](const auto & delta) { return delta.zxid == zxid; }); + + response_errors.push_back(error->error); + + for (size_t j = i + 1; j < concrete_requests.size(); ++j) + { + response_errors.push_back(Coordination::Error::ZRUNTIMEINCONSISTENCY); + } + + return {{zxid, KeeperStorage::FailedMultiDelta{std::move(response_errors)}}}; + } + } + new_deltas.emplace_back(zxid, KeeperStorage::SubDeltaEnd{}); + response_errors.push_back(Coordination::Error::ZOK); + + saved_deltas.insert(saved_deltas.end(), std::make_move_iterator(new_deltas.begin()), std::make_move_iterator(new_deltas.end())); + } + + return {}; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override { Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); Coordination::ZooKeeperMultiResponse & response = dynamic_cast(*response_ptr); - std::vector undo_actions; - try + auto & deltas = storage.uncommitted_state.deltas; + // the deltas will have at least SubDeltaEnd or FailedMultiDelta + assert(!deltas.empty()); + if (auto * failed_multi = std::get_if(&deltas.front().operation)) { - size_t i = 0; - for (const auto & concrete_request : concrete_requests) + for (size_t i = 0; i < concrete_requests.size(); ++i) { - auto [ cur_response, undo_action ] = concrete_request->process(storage, zxid, session_id, time); - - response.responses[i] = cur_response; - if (cur_response->error != Coordination::Error::ZOK) - { - for (size_t j = 0; j <= i; ++j) - { - auto response_error = response.responses[j]->error; - response.responses[j] = std::make_shared(); - response.responses[j]->error = response_error; - } - - for (size_t j = i + 1; j < response.responses.size(); ++j) - { - response.responses[j] = std::make_shared(); - response.responses[j]->error = Coordination::Error::ZRUNTIMEINCONSISTENCY; - } - - for (auto it = undo_actions.rbegin(); it != undo_actions.rend(); ++it) - if (*it) - (*it)(); - - return { response_ptr, {} }; - } - else - undo_actions.emplace_back(std::move(undo_action)); - - ++i; + response.responses[i] = std::make_shared(); + response.responses[i]->error = failed_multi->error_codes[i]; } - response.error = Coordination::Error::ZOK; - return { response_ptr, {} }; + return response_ptr; } - catch (...) + + for (size_t i = 0; i < concrete_requests.size(); ++i) { - for (auto it = undo_actions.rbegin(); it != undo_actions.rend(); ++it) - if (*it) - (*it)(); - throw; + auto cur_response = concrete_requests[i]->process(storage, zxid, session_id, time); + + while (!deltas.empty()) + { + if (std::holds_alternative(deltas.front().operation)) + { + deltas.pop_front(); + break; + } + + deltas.pop_front(); + } + + response.responses[i] = cur_response; } + + response.error = Coordination::Error::ZOK; + return response_ptr; } - KeeperStorage::ResponsesForSessions processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override + Coordination::ZooKeeperResponsePtr processLocal(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t time) const override + { + Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); + Coordination::ZooKeeperMultiResponse & response = dynamic_cast(*response_ptr); + + for (size_t i = 0; i < concrete_requests.size(); ++i) + { + auto cur_response = concrete_requests[i]->process(storage, zxid, session_id, time); + + response.responses[i] = cur_response; + if (cur_response->error != Coordination::Error::ZOK) + { + for (size_t j = 0; j <= i; ++j) + { + auto response_error = response.responses[j]->error; + response.responses[j] = std::make_shared(); + response.responses[j]->error = response_error; + } + + for (size_t j = i + 1; j < response.responses.size(); ++j) + { + response.responses[j] = std::make_shared(); + response.responses[j]->error = Coordination::Error::ZRUNTIMEINCONSISTENCY; + } + + return response_ptr; + } + } + + response.error = Coordination::Error::ZOK; + return response_ptr; + } + + KeeperStorage::ResponsesForSessions + processWatches(KeeperStorage::Watches & watches, KeeperStorage::Watches & list_watches) const override { KeeperStorage::ResponsesForSessions result; for (const auto & generic_request : concrete_requests) @@ -962,7 +1421,7 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro struct KeeperStorageCloseRequestProcessor final : public KeeperStorageRequestProcessor { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage &, int64_t, int64_t, int64_t /* time */) const override + Coordination::ZooKeeperResponsePtr process(KeeperStorage &, int64_t, int64_t, int64_t /* time */) const override { throw DB::Exception("Called process on close request", ErrorCodes::LOGICAL_ERROR); } @@ -971,36 +1430,40 @@ struct KeeperStorageCloseRequestProcessor final : public KeeperStorageRequestPro struct KeeperStorageAuthRequestProcessor final : public KeeperStorageRequestProcessor { using KeeperStorageRequestProcessor::KeeperStorageRequestProcessor; - std::pair process(KeeperStorage & storage, int64_t /*zxid*/, int64_t session_id, int64_t /* time */) const override + std::vector preprocess(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /*time*/) const override { Coordination::ZooKeeperAuthRequest & auth_request = dynamic_cast(*zk_request); Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); - Coordination::ZooKeeperAuthResponse & auth_response = dynamic_cast(*response_ptr); - auto & sessions_and_auth = storage.session_and_auth; if (auth_request.scheme != "digest" || std::count(auth_request.data.begin(), auth_request.data.end(), ':') != 1) + return {{zxid, Coordination::Error::ZAUTHFAILED}}; + + std::vector new_deltas; + auto digest = generateDigest(auth_request.data); + if (digest == storage.superdigest) { - auth_response.error = Coordination::Error::ZAUTHFAILED; + KeeperStorage::AuthID auth{"super", ""}; + new_deltas.emplace_back(zxid, KeeperStorage::AddAuthDelta{session_id, std::move(auth)}); } else { - auto digest = generateDigest(auth_request.data); - if (digest == storage.superdigest) - { - KeeperStorage::AuthID auth{"super", ""}; - sessions_and_auth[session_id].emplace_back(auth); - } - else - { - KeeperStorage::AuthID auth{auth_request.scheme, digest}; - auto & session_ids = sessions_and_auth[session_id]; - if (std::find(session_ids.begin(), session_ids.end(), auth) == session_ids.end()) - sessions_and_auth[session_id].emplace_back(auth); - } - + KeeperStorage::AuthID new_auth{auth_request.scheme, digest}; + if (!storage.uncommitted_state.hasACL(session_id, false, [&](const auto & auth_id) { return new_auth == auth_id; })) + new_deltas.emplace_back(zxid, KeeperStorage::AddAuthDelta{session_id, std::move(new_auth)}); } - return { response_ptr, {} }; + return new_deltas; + } + + Coordination::ZooKeeperResponsePtr process(KeeperStorage & storage, int64_t zxid, int64_t session_id, int64_t /* time */) const override + { + Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); + Coordination::ZooKeeperAuthResponse & auth_response = dynamic_cast(*response_ptr); + + if (const auto result = storage.commit(zxid, session_id); result != Coordination::Error::ZOK) + auth_response.error = result; + + return response_ptr; } }; @@ -1026,7 +1489,6 @@ void KeeperStorage::finalize() class KeeperStorageRequestProcessorsFactory final : private boost::noncopyable { - public: using Creator = std::function; using OpNumToRequest = std::unordered_map; @@ -1039,11 +1501,11 @@ public: KeeperStorageRequestProcessorPtr get(const Coordination::ZooKeeperRequestPtr & zk_request) const { - auto it = op_num_to_request.find(zk_request->getOpNum()); - if (it == op_num_to_request.end()) + auto request_it = op_num_to_request.find(zk_request->getOpNum()); + if (request_it == op_num_to_request.end()) throw DB::Exception("Unknown operation type " + toString(zk_request->getOpNum()), ErrorCodes::LOGICAL_ERROR); - return it->second(zk_request); + return request_it->second(zk_request); } void registerRequest(Coordination::OpNum op_num, Creator creator) @@ -1057,10 +1519,11 @@ private: KeeperStorageRequestProcessorsFactory(); }; -template +template void registerKeeperRequestProcessor(KeeperStorageRequestProcessorsFactory & factory) { - factory.registerRequest(num, [] (const Coordination::ZooKeeperRequestPtr & zk_request) { return std::make_shared(zk_request); }); + factory.registerRequest( + num, [](const Coordination::ZooKeeperRequestPtr & zk_request) { return std::make_shared(zk_request); }); } @@ -1084,13 +1547,66 @@ KeeperStorageRequestProcessorsFactory::KeeperStorageRequestProcessorsFactory() } -KeeperStorage::ResponsesForSessions KeeperStorage::processRequest(const Coordination::ZooKeeperRequestPtr & zk_request, int64_t session_id, int64_t time, std::optional new_last_zxid, bool check_acl) +void KeeperStorage::preprocessRequest( + const Coordination::ZooKeeperRequestPtr & zk_request, int64_t session_id, int64_t time, int64_t new_last_zxid, bool check_acl) +{ + KeeperStorageRequestProcessorPtr request_processor = KeeperStorageRequestProcessorsFactory::instance().get(zk_request); + + if (zk_request->getOpNum() == Coordination::OpNum::Close) /// Close request is special + { + auto & deltas = uncommitted_state.deltas; + auto session_ephemerals = ephemerals.find(session_id); + if (session_ephemerals != ephemerals.end()) + { + for (const auto & ephemeral_path : session_ephemerals->second) + { + // For now just add deltas for removing the node + // On commit, ephemerals nodes will be deleted from storage + // and removed from the session + if (uncommitted_state.hasNode(ephemeral_path)) + { + deltas.emplace_back( + parentPath(ephemeral_path).toString(), + new_last_zxid, + UpdateNodeDelta{[ephemeral_path](Node & parent) + { + --parent.stat.numChildren; + ++parent.stat.cversion; + }}); + + deltas.emplace_back(ephemeral_path, new_last_zxid, RemoveNodeDelta()); + } + } + } + + return; + } + + if (check_acl && !request_processor->checkAuth(*this, session_id, false)) + { + uncommitted_state.deltas.emplace_back(new_last_zxid, Coordination::Error::ZNOAUTH); + return; + } + + auto new_deltas = request_processor->preprocess(*this, new_last_zxid, session_id, time); + uncommitted_state.deltas.insert( + uncommitted_state.deltas.end(), std::make_move_iterator(new_deltas.begin()), std::make_move_iterator(new_deltas.end())); +} + +KeeperStorage::ResponsesForSessions KeeperStorage::processRequest( + const Coordination::ZooKeeperRequestPtr & zk_request, + int64_t session_id, + int64_t time, + std::optional new_last_zxid, + bool check_acl, + bool is_local) { KeeperStorage::ResponsesForSessions results; if (new_last_zxid) { if (zxid >= *new_last_zxid) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal than current {}. It's a bug", *new_last_zxid, zxid); + throw Exception( + ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal than current {}. It's a bug", *new_last_zxid, zxid); zxid = *new_last_zxid; } @@ -1099,26 +1615,22 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest(const Coordina if (zk_request->getOpNum() == Coordination::OpNum::Close) /// Close request is special { - auto it = ephemerals.find(session_id); - if (it != ephemerals.end()) + commit(zxid, session_id); + + for (const auto & delta : uncommitted_state.deltas) { - for (const auto & ephemeral_path : it->second) + if (delta.zxid > zxid) + break; + + if (std::holds_alternative(delta.operation)) { - container.updateValue(parentPath(ephemeral_path), [&ephemeral_path] (KeeperStorage::Node & parent) - { - --parent.stat.numChildren; - ++parent.stat.cversion; - auto base_name = getBaseName(ephemeral_path); - parent.removeChild(base_name); - }); - - container.erase(ephemeral_path); - - auto responses = processWatchesImpl(ephemeral_path, watches, list_watches, Coordination::Event::DELETED); + auto responses = processWatchesImpl(delta.path, watches, list_watches, Coordination::Event::DELETED); results.insert(results.end(), responses.begin(), responses.end()); } - ephemerals.erase(it); } + + std::erase_if(uncommitted_state.deltas, [this](const auto & delta) { return delta.zxid == zxid; }); + clearDeadWatches(session_id); auto auth_it = session_and_auth.find(session_id); if (auth_it != session_and_auth.end()) @@ -1135,7 +1647,7 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest(const Coordina else if (zk_request->getOpNum() == Coordination::OpNum::Heartbeat) /// Heartbeat request is also special { KeeperStorageRequestProcessorPtr storage_request = KeeperStorageRequestProcessorsFactory::instance().get(zk_request); - auto [response, _] = storage_request->process(*this, zxid, session_id, time); + auto response = storage_request->process(*this, zxid, session_id, time); response->xid = zk_request->xid; response->zxid = getZXID(); @@ -1146,15 +1658,24 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest(const Coordina KeeperStorageRequestProcessorPtr request_processor = KeeperStorageRequestProcessorsFactory::instance().get(zk_request); Coordination::ZooKeeperResponsePtr response; - if (check_acl && !request_processor->checkAuth(*this, session_id)) + if (is_local) { - response = zk_request->makeResponse(); - /// Original ZooKeeper always throws no auth, even when user provided some credentials - response->error = Coordination::Error::ZNOAUTH; + assert(zk_request->isReadRequest()); + if (check_acl && !request_processor->checkAuth(*this, session_id, true)) + { + response = zk_request->makeResponse(); + /// Original ZooKeeper always throws no auth, even when user provided some credentials + response->error = Coordination::Error::ZNOAUTH; + } + else + { + response = request_processor->processLocal(*this, zxid, session_id, time); + } } else { - std::tie(response, std::ignore) = request_processor->process(*this, zxid, session_id, time); + response = request_processor->process(*this, zxid, session_id, time); + std::erase_if(uncommitted_state.deltas, [this](const auto & delta) { return delta.zxid == zxid; }); } /// Watches for this requests are added to the watches lists @@ -1162,7 +1683,8 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest(const Coordina { if (response->error == Coordination::Error::ZOK) { - auto & watches_type = zk_request->getOpNum() == Coordination::OpNum::List || zk_request->getOpNum() == Coordination::OpNum::SimpleList + auto & watches_type + = zk_request->getOpNum() == Coordination::OpNum::List || zk_request->getOpNum() == Coordination::OpNum::SimpleList ? list_watches : watches; @@ -1192,6 +1714,16 @@ KeeperStorage::ResponsesForSessions KeeperStorage::processRequest(const Coordina return results; } +void KeeperStorage::rollbackRequest(int64_t rollback_zxid) +{ + // we can only rollback the last zxid (if there is any) + // if there is a delta with a larger zxid, we have invalid state + const auto last_zxid = uncommitted_state.deltas.back().zxid; + if (!uncommitted_state.deltas.empty() && last_zxid > rollback_zxid) + throw DB::Exception{DB::ErrorCodes::LOGICAL_ERROR, "Invalid state of deltas found while trying to rollback request. Last ZXID ({}) is larger than the requested ZXID ({})", last_zxid, rollback_zxid}; + + std::erase_if(uncommitted_state.deltas, [rollback_zxid](const auto & delta) { return delta.zxid == rollback_zxid; }); +} void KeeperStorage::clearDeadWatches(int64_t session_id) { diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index ccbddcf6e19..7d26ae24dd9 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -1,14 +1,14 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include #include @@ -29,7 +29,6 @@ struct KeeperStorageSnapshot; class KeeperStorage { public: - struct Node { uint64_t acl_id = 0; /// 0 -- no ACL by default @@ -41,26 +40,18 @@ public: Node() : size_bytes(sizeof(Node)) { } /// Object memory size - uint64_t sizeInBytes() const - { - return size_bytes; - } + uint64_t sizeInBytes() const { return size_bytes; } void setData(String new_data); - const auto & getData() const noexcept - { - return data; - } + const auto & getData() const noexcept { return data; } void addChild(StringRef child_path); void removeChild(StringRef child_path); - const auto & getChildren() const noexcept - { - return children; - } + const auto & getChildren() const noexcept { return children; } + private: String data; ChildrenSet children{}; @@ -85,10 +76,7 @@ public: std::string scheme; std::string id; - bool operator==(const AuthID & other) const - { - return scheme == other.scheme && id == other.id; - } + bool operator==(const AuthID & other) const { return scheme == other.scheme && id == other.id; } }; using RequestsForSessions = std::vector; @@ -112,6 +100,146 @@ public: /// container. Container container; + // Applying ZooKeeper request to storage consists of two steps: + // - preprocessing which, instead of applying the changes directly to storage, + // generates deltas with those changes, denoted with the request ZXID + // - processing which applies deltas with the correct ZXID to the storage + // + // Delta objects allow us two things: + // - fetch the latest, uncommitted state of an object by getting the committed + // state of that same object from the storage and applying the deltas + // in the same order as they are defined + // - quickly commit the changes to the storage + struct CreateNodeDelta + { + Coordination::Stat stat; + bool is_ephemeral; + bool is_sequental; + Coordination::ACLs acls; + String data; + }; + + struct RemoveNodeDelta + { + int32_t version{-1}; + }; + + struct UpdateNodeDelta + { + std::function update_fn; + int32_t version{-1}; + }; + + struct SetACLDelta + { + Coordination::ACLs acls; + int32_t version{-1}; + }; + + struct ErrorDelta + { + Coordination::Error error; + }; + + struct FailedMultiDelta + { + std::vector error_codes; + }; + + // Denotes end of a subrequest in multi request + struct SubDeltaEnd + { + }; + + struct AddAuthDelta + { + int64_t session_id; + AuthID auth_id; + }; + + using Operation + = std::variant; + + struct Delta + { + Delta(String path_, int64_t zxid_, Operation operation_) : path(std::move(path_)), zxid(zxid_), operation(std::move(operation_)) { } + + Delta(int64_t zxid_, Coordination::Error error) : Delta("", zxid_, ErrorDelta{error}) { } + + Delta(int64_t zxid_, Operation subdelta) : Delta("", zxid_, subdelta) { } + + String path; + int64_t zxid; + Operation operation; + }; + + struct UncommittedState + { + explicit UncommittedState(KeeperStorage & storage_) : storage(storage_) { } + + template + void applyDeltas(StringRef path, const Visitor & visitor) const + { + for (const auto & delta : deltas) + { + if (path.empty() || delta.path == path) + std::visit(visitor, delta.operation); + } + } + + bool hasACL(int64_t session_id, bool is_local, std::function predicate) + { + for (const auto & session_auth : storage.session_and_auth[session_id]) + { + if (predicate(session_auth)) + return true; + } + + if (is_local) + return false; + + + for (const auto & delta : deltas) + { + if (const auto * auth_delta = std::get_if(&delta.operation); + auth_delta && auth_delta->session_id == session_id && predicate(auth_delta->auth_id)) + return true; + } + + return false; + } + + std::shared_ptr getNode(StringRef path); + bool hasNode(StringRef path) const; + Coordination::ACLs getACLs(StringRef path) const; + + std::deque deltas; + KeeperStorage & storage; + }; + + UncommittedState uncommitted_state{*this}; + + Coordination::Error commit(int64_t zxid, int64_t session_id); + + // Create node in the storage + // Returns false if it failed to create the node, true otherwise + // We don't care about the exact failure because we should've caught it during preprocessing + bool createNode( + const std::string & path, + String data, + const Coordination::Stat & stat, + bool is_sequental, + bool is_ephemeral, + Coordination::ACLs node_acls, + int64_t session_id); + + // Remove node in the storage + // Returns false if it failed to remove the node, true otherwise + // We don't care about the exact failure because we should've caught it during preprocessing + bool removeNode(const std::string & path, int32_t version); + + bool checkACL(StringRef path, int32_t permissions, int64_t session_id, bool is_local); + /// Mapping session_id -> set of ephemeral nodes paths Ephemerals ephemerals; /// Mapping session_id -> set of watched nodes paths @@ -130,15 +258,12 @@ public: /// Currently active watches (node_path -> subscribed sessions) Watches watches; - Watches list_watches; /// Watches for 'list' request (watches on children). + Watches list_watches; /// Watches for 'list' request (watches on children). void clearDeadWatches(int64_t session_id); /// Get current zxid - int64_t getZXID() const - { - return zxid; - } + int64_t getZXID() const { return zxid; } const String superdigest; @@ -162,78 +287,53 @@ public: /// Process user request and return response. /// check_acl = false only when converting data from ZooKeeper. - ResponsesForSessions processRequest(const Coordination::ZooKeeperRequestPtr & request, int64_t session_id, int64_t time, std::optional new_last_zxid, bool check_acl = true); + ResponsesForSessions processRequest( + const Coordination::ZooKeeperRequestPtr & request, + int64_t session_id, + int64_t time, + std::optional new_last_zxid, + bool check_acl = true, + bool is_local = false); + void preprocessRequest( + const Coordination::ZooKeeperRequestPtr & request, int64_t session_id, int64_t time, int64_t new_last_zxid, bool check_acl = true); + void rollbackRequest(int64_t rollback_zxid); void finalize(); /// Set of methods for creating snapshots /// Turn on snapshot mode, so data inside Container is not deleted, but replaced with new version. - void enableSnapshotMode(size_t up_to_version) - { - container.enableSnapshotMode(up_to_version); - - } + void enableSnapshotMode(size_t up_to_version) { container.enableSnapshotMode(up_to_version); } /// Turn off snapshot mode. - void disableSnapshotMode() - { - container.disableSnapshotMode(); - } + void disableSnapshotMode() { container.disableSnapshotMode(); } - Container::const_iterator getSnapshotIteratorBegin() const - { - return container.begin(); - } + Container::const_iterator getSnapshotIteratorBegin() const { return container.begin(); } /// Clear outdated data from internal container. - void clearGarbageAfterSnapshot() - { - container.clearOutdatedNodes(); - } + void clearGarbageAfterSnapshot() { container.clearOutdatedNodes(); } /// Get all active sessions - const SessionAndTimeout & getActiveSessions() const - { - return session_and_timeout; - } + const SessionAndTimeout & getActiveSessions() const { return session_and_timeout; } /// Get all dead sessions - std::vector getDeadSessions() const - { - return session_expiry_queue.getExpiredSessions(); - } + std::vector getDeadSessions() const { return session_expiry_queue.getExpiredSessions(); } /// Introspection functions mostly used in 4-letter commands - uint64_t getNodesCount() const - { - return container.size(); - } + uint64_t getNodesCount() const { return container.size(); } - uint64_t getApproximateDataSize() const - { - return container.getApproximateDataSize(); - } + uint64_t getApproximateDataSize() const { return container.getApproximateDataSize(); } - uint64_t getArenaDataSize() const - { - return container.keyArenaSize(); - } + uint64_t getArenaDataSize() const { return container.keyArenaSize(); } uint64_t getTotalWatchesCount() const; - uint64_t getWatchedPathsCount() const - { - return watches.size() + list_watches.size(); - } + uint64_t getWatchedPathsCount() const { return watches.size() + list_watches.size(); } uint64_t getSessionsWithWatchesCount() const; - uint64_t getSessionWithEphemeralNodesCount() const - { - return ephemerals.size(); - } + uint64_t getSessionWithEphemeralNodesCount() const { return ephemerals.size(); } uint64_t getTotalEphemeralNodesCount() const; void dumpWatches(WriteBufferFromOwnString & buf) const; diff --git a/src/Coordination/WriteBufferFromNuraftBuffer.h b/src/Coordination/WriteBufferFromNuraftBuffer.h index d52049edcff..c9ca1e2a227 100644 --- a/src/Coordination/WriteBufferFromNuraftBuffer.h +++ b/src/Coordination/WriteBufferFromNuraftBuffer.h @@ -12,7 +12,6 @@ public: WriteBufferFromNuraftBuffer(); nuraft::ptr getBuffer(); - bool isFinished() const { return finalized; } ~WriteBufferFromNuraftBuffer() override; diff --git a/src/Coordination/ZooKeeperDataReader.cpp b/src/Coordination/ZooKeeperDataReader.cpp index e59c67329ff..4d1745edc6a 100644 --- a/src/Coordination/ZooKeeperDataReader.cpp +++ b/src/Coordination/ZooKeeperDataReader.cpp @@ -520,6 +520,7 @@ bool deserializeTxn(KeeperStorage & storage, ReadBuffer & in, Poco::Logger * /*l if (request->getOpNum() == Coordination::OpNum::Multi && hasErrorsInMultiRequest(request)) return true; + storage.preprocessRequest(request, session_id, time, zxid, /* check_acl = */ false); storage.processRequest(request, session_id, time, zxid, /* check_acl = */ false); } } diff --git a/src/Coordination/tests/gtest_coordination.cpp b/src/Coordination/tests/gtest_coordination.cpp index cf4d1eaf9f2..2742f48f49e 100644 --- a/src/Coordination/tests/gtest_coordination.cpp +++ b/src/Coordination/tests/gtest_coordination.cpp @@ -1,6 +1,8 @@ #include #include +#include "Common/ZooKeeper/IKeeper.h" +#include "Coordination/KeeperStorage.h" #include "config_core.h" #if USE_NURAFT @@ -1261,6 +1263,7 @@ void testLogAndStateMachine(Coordination::CoordinationSettingsPtr settings, uint changelog.append(entry); changelog.end_of_append_batch(0, 0); + state_machine->pre_commit(i, changelog.entry_at(i)->get_buf()); state_machine->commit(i, changelog.entry_at(i)->get_buf()); bool snapshot_created = false; if (i % settings->snapshot_distance == 0) @@ -1305,6 +1308,7 @@ void testLogAndStateMachine(Coordination::CoordinationSettingsPtr settings, uint for (size_t i = restore_machine->last_commit_index() + 1; i < restore_changelog.next_slot(); ++i) { + restore_machine->pre_commit(i, changelog.entry_at(i)->get_buf()); restore_machine->commit(i, changelog.entry_at(i)->get_buf()); } @@ -1407,6 +1411,7 @@ TEST_P(CoordinationTest, TestEphemeralNodeRemove) request_c->path = "/hello"; request_c->is_ephemeral = true; auto entry_c = getLogEntryFromZKRequest(0, 1, request_c); + state_machine->pre_commit(1, entry_c->get_buf()); state_machine->commit(1, entry_c->get_buf()); const auto & storage = state_machine->getStorage(); @@ -1415,6 +1420,7 @@ TEST_P(CoordinationTest, TestEphemeralNodeRemove) request_d->path = "/hello"; /// Delete from other session auto entry_d = getLogEntryFromZKRequest(0, 2, request_d); + state_machine->pre_commit(2, entry_d->get_buf()); state_machine->commit(2, entry_d->get_buf()); EXPECT_EQ(storage.ephemerals.size(), 0); @@ -1777,6 +1783,130 @@ TEST_P(CoordinationTest, TestLogGap) EXPECT_EQ(changelog1.next_slot(), 61); } +template +ResponseType getSingleResponse(const auto & responses) +{ + EXPECT_FALSE(responses.empty()); + return dynamic_cast(*responses[0].response); +} + +TEST_P(CoordinationTest, TestUncommittedStateBasicCrud) +{ + using namespace DB; + using namespace Coordination; + + DB::KeeperStorage storage{500, ""}; + + constexpr std::string_view path = "/test"; + + const auto get_committed_data = [&]() -> std::optional + { + auto request = std::make_shared(); + request->path = path; + auto responses = storage.processRequest(request, 0, 0, std::nullopt, true, true); + const auto & get_response = getSingleResponse(responses); + + if (get_response.error != Error::ZOK) + return std::nullopt; + + return get_response.data; + }; + + const auto preprocess_get = [&](int64_t zxid) + { + auto get_request = std::make_shared(); + get_request->path = path; + storage.preprocessRequest(get_request, 0, 0, zxid); + return get_request; + }; + + const auto create_request = std::make_shared(); + create_request->path = path; + create_request->data = "initial_data"; + storage.preprocessRequest(create_request, 0, 0, 1); + storage.preprocessRequest(create_request, 0, 0, 2); + + ASSERT_FALSE(get_committed_data()); + + const auto after_create_get = preprocess_get(3); + + ASSERT_FALSE(get_committed_data()); + + const auto set_request = std::make_shared(); + set_request->path = path; + set_request->data = "new_data"; + storage.preprocessRequest(set_request, 0, 0, 4); + + const auto after_set_get = preprocess_get(5); + + ASSERT_FALSE(get_committed_data()); + + const auto remove_request = std::make_shared(); + remove_request->path = path; + storage.preprocessRequest(remove_request, 0, 0, 6); + storage.preprocessRequest(remove_request, 0, 0, 7); + + const auto after_remove_get = preprocess_get(8); + + ASSERT_FALSE(get_committed_data()); + + { + const auto responses = storage.processRequest(create_request, 0, 0, 1); + const auto & create_response = getSingleResponse(responses); + ASSERT_EQ(create_response.error, Error::ZOK); + } + + { + const auto responses = storage.processRequest(create_request, 0, 0, 2); + const auto & create_response = getSingleResponse(responses); + ASSERT_EQ(create_response.error, Error::ZNODEEXISTS); + } + + { + const auto responses = storage.processRequest(after_create_get, 0, 0, 3); + const auto & get_response = getSingleResponse(responses); + ASSERT_EQ(get_response.error, Error::ZOK); + ASSERT_EQ(get_response.data, "initial_data"); + } + + ASSERT_EQ(get_committed_data(), "initial_data"); + + { + const auto responses = storage.processRequest(set_request, 0, 0, 4); + const auto & create_response = getSingleResponse(responses); + ASSERT_EQ(create_response.error, Error::ZOK); + } + + { + const auto responses = storage.processRequest(after_set_get, 0, 0, 5); + const auto & get_response = getSingleResponse(responses); + ASSERT_EQ(get_response.error, Error::ZOK); + ASSERT_EQ(get_response.data, "new_data"); + } + + ASSERT_EQ(get_committed_data(), "new_data"); + + { + const auto responses = storage.processRequest(remove_request, 0, 0, 6); + const auto & create_response = getSingleResponse(responses); + ASSERT_EQ(create_response.error, Error::ZOK); + } + + { + const auto responses = storage.processRequest(remove_request, 0, 0, 7); + const auto & create_response = getSingleResponse(responses); + ASSERT_EQ(create_response.error, Error::ZNONODE); + } + + { + const auto responses = storage.processRequest(after_remove_get, 0, 0, 8); + const auto & get_response = getSingleResponse(responses); + ASSERT_EQ(get_response.error, Error::ZNONODE); + } + + ASSERT_FALSE(get_committed_data()); +} + INSTANTIATE_TEST_SUITE_P(CoordinationTestSuite, CoordinationTest, From 4d41121c096918493b4f0ac05dbab4546b8c9331 Mon Sep 17 00:00:00 2001 From: koloshmet Date: Thu, 26 May 2022 10:58:00 +0300 Subject: [PATCH 042/204] added test query --- .../02313_test_fpc_codec.reference | 2 + .../0_stateless/02313_test_fpc_codec.sql | 61 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/queries/0_stateless/02313_test_fpc_codec.reference create mode 100644 tests/queries/0_stateless/02313_test_fpc_codec.sql diff --git a/tests/queries/0_stateless/02313_test_fpc_codec.reference b/tests/queries/0_stateless/02313_test_fpc_codec.reference new file mode 100644 index 00000000000..5e871ea0329 --- /dev/null +++ b/tests/queries/0_stateless/02313_test_fpc_codec.reference @@ -0,0 +1,2 @@ +F64 +F32 diff --git a/tests/queries/0_stateless/02313_test_fpc_codec.sql b/tests/queries/0_stateless/02313_test_fpc_codec.sql new file mode 100644 index 00000000000..e077e59b07b --- /dev/null +++ b/tests/queries/0_stateless/02313_test_fpc_codec.sql @@ -0,0 +1,61 @@ +DROP TABLE IF EXISTS codecTest; + +CREATE TABLE codecTest ( + key UInt64, + name String, + ref_valueF64 Float64, + ref_valueF32 Float32, + valueF64 Float64 CODEC(FPC), + valueF32 Float32 CODEC(FPC) +) Engine = MergeTree ORDER BY key; + +-- best case - same value +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'e()', e() AS v, v, v, v FROM system.numbers LIMIT 1, 100; + +-- good case - values that grow insignificantly +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'log2(n)', log2(n) AS v, v, v, v FROM system.numbers LIMIT 101, 100; + +-- bad case - values differ significantly +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'n*sqrt(n)', n*sqrt(n) AS v, v, v, v FROM system.numbers LIMIT 201, 100; + +-- worst case - almost like a random values +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'sin(n*n*n)*n', sin(n * n * n * n* n) AS v, v, v, v FROM system.numbers LIMIT 301, 100; + + +-- These floating-point values are expected to be BINARY equal, so comparing by-value is Ok here. + +-- referencing previous row key, value, and case name to simplify debugging. +SELECT 'F64'; +SELECT + c1.key, c1.name, + c1.ref_valueF64, c1.valueF64, c1.ref_valueF64 - c1.valueF64 AS dF64, + 'prev:', + c2.key, c2.ref_valueF64 +FROM + codecTest as c1, codecTest as c2 +WHERE + dF64 != 0 +AND + c2.key = c1.key - 1 +LIMIT 10; + + +SELECT 'F32'; +SELECT + c1.key, c1.name, + c1.ref_valueF32, c1.valueF32, c1.ref_valueF32 - c1.valueF32 AS dF32, + 'prev:', + c2.key, c2.ref_valueF32 +FROM + codecTest as c1, codecTest as c2 +WHERE + dF32 != 0 +AND + c2.key = c1.key - 1 +LIMIT 10; + +DROP TABLE IF EXISTS codecTest; From 821100f145cf75c8ef3a3c30d16c4bafcc05378e Mon Sep 17 00:00:00 2001 From: koloshmet Date: Thu, 26 May 2022 11:09:36 +0300 Subject: [PATCH 043/204] code style fixes --- src/Compression/CompressionCodecFPC.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index 269a935da4b..0f68f512ad8 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -69,9 +69,8 @@ CompressionCodecFPC::CompressionCodecFPC(UInt8 float_size, UInt8 compression_lev UInt32 CompressionCodecFPC::getMaxCompressedDataSize(UInt32 uncompressed_size) const { auto float_count = (uncompressed_size + float_width - 1) / float_width; - if (float_count % 2 != 0) { + if (float_count % 2 != 0) ++float_count; - } return HEADER_SIZE + float_count * float_width + float_count / 2; } @@ -106,7 +105,8 @@ UInt8 encodeEndianness(std::endian endian) throw Exception("Unsupported endianness", ErrorCodes::BAD_ARGUMENTS); } -std::endian decodeEndianness(UInt8 endian) { +std::endian decodeEndianness(UInt8 endian) +{ switch (endian) { case 0: @@ -152,7 +152,8 @@ namespace { template requires (sizeof(TUint) >= 4) -class DfcmPredictor { +class DfcmPredictor +{ public: explicit DfcmPredictor(std::size_t table_size): table(table_size, 0), prev_value{0}, hash{0} { @@ -191,7 +192,8 @@ private: }; template requires (sizeof(TUint) >= 4) -class FcmPredictor { +class FcmPredictor +{ public: explicit FcmPredictor(std::size_t table_size): table(table_size, 0), hash{0} { From 7e69779575b712aae530c71f82f9e33f10e48d76 Mon Sep 17 00:00:00 2001 From: koloshmet Date: Thu, 26 May 2022 22:32:56 +0300 Subject: [PATCH 044/204] added fpc codec to float perftest --- src/Compression/CompressionCodecFPC.cpp | 2 + tests/performance/codecs_float_insert.xml | 1 + tests/performance/codecs_float_select.xml | 1 + .../02313_test_fpc_codec.reference | 2 + .../0_stateless/02313_test_fpc_codec.sql | 60 +++++++++++++++++++ 5 files changed, 66 insertions(+) diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index 0f68f512ad8..f3106204e01 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -142,6 +142,8 @@ void registerCodecFPC(CompressionCodecFactory & factory) throw Exception("FPC codec argument must be integer", ErrorCodes::ILLEGAL_CODEC_PARAMETER); level = literal->value.safeGet(); + if (level == 0) + throw Exception("FPC codec level must be at least 1", ErrorCodes::ILLEGAL_CODEC_PARAMETER); } return std::make_shared(float_width, level); }; diff --git a/tests/performance/codecs_float_insert.xml b/tests/performance/codecs_float_insert.xml index b31e0eafdd7..64325d30189 100644 --- a/tests/performance/codecs_float_insert.xml +++ b/tests/performance/codecs_float_insert.xml @@ -12,6 +12,7 @@ ZSTD DoubleDelta Gorilla + FPC diff --git a/tests/performance/codecs_float_select.xml b/tests/performance/codecs_float_select.xml index 82489daf524..4743a756ac3 100644 --- a/tests/performance/codecs_float_select.xml +++ b/tests/performance/codecs_float_select.xml @@ -12,6 +12,7 @@ ZSTD DoubleDelta Gorilla + FPC diff --git a/tests/queries/0_stateless/02313_test_fpc_codec.reference b/tests/queries/0_stateless/02313_test_fpc_codec.reference index 5e871ea0329..23c75ed1ac0 100644 --- a/tests/queries/0_stateless/02313_test_fpc_codec.reference +++ b/tests/queries/0_stateless/02313_test_fpc_codec.reference @@ -1,2 +1,4 @@ F64 F32 +F64 +F32 diff --git a/tests/queries/0_stateless/02313_test_fpc_codec.sql b/tests/queries/0_stateless/02313_test_fpc_codec.sql index e077e59b07b..3b1127350f0 100644 --- a/tests/queries/0_stateless/02313_test_fpc_codec.sql +++ b/tests/queries/0_stateless/02313_test_fpc_codec.sql @@ -44,6 +44,66 @@ AND LIMIT 10; +SELECT 'F32'; +SELECT + c1.key, c1.name, + c1.ref_valueF32, c1.valueF32, c1.ref_valueF32 - c1.valueF32 AS dF32, + 'prev:', + c2.key, c2.ref_valueF32 +FROM + codecTest as c1, codecTest as c2 +WHERE + dF32 != 0 +AND + c2.key = c1.key - 1 +LIMIT 10; + +DROP TABLE IF EXISTS codecTest; + +CREATE TABLE codecTest ( + key UInt64, + name String, + ref_valueF64 Float64, + ref_valueF32 Float32, + valueF64 Float64 CODEC(FPC(4)), + valueF32 Float32 CODEC(FPC(4)) +) Engine = MergeTree ORDER BY key; + +-- best case - same value +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'e()', e() AS v, v, v, v FROM system.numbers LIMIT 1, 100; + +-- good case - values that grow insignificantly +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'log2(n)', log2(n) AS v, v, v, v FROM system.numbers LIMIT 101, 100; + +-- bad case - values differ significantly +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'n*sqrt(n)', n*sqrt(n) AS v, v, v, v FROM system.numbers LIMIT 201, 100; + +-- worst case - almost like a random values +INSERT INTO codecTest (key, name, ref_valueF64, valueF64, ref_valueF32, valueF32) + SELECT number AS n, 'sin(n*n*n)*n', sin(n * n * n * n* n) AS v, v, v, v FROM system.numbers LIMIT 301, 100; + + +-- These floating-point values are expected to be BINARY equal, so comparing by-value is Ok here. + +-- referencing previous row key, value, and case name to simplify debugging. +SELECT 'F64'; +SELECT + c1.key, c1.name, + c1.ref_valueF64, c1.valueF64, c1.ref_valueF64 - c1.valueF64 AS dF64, + 'prev:', + c2.key, c2.ref_valueF64 +FROM + codecTest as c1, codecTest as c2 +WHERE + dF64 != 0 +AND + c2.key = c1.key - 1 +LIMIT 10; + + SELECT 'F32'; SELECT c1.key, c1.name, From 28e0bb78db74144b71d4ef85882982dcf89df292 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Thu, 26 May 2022 13:03:12 +0300 Subject: [PATCH 045/204] tests/stateless: properly wait for server If 10 seconds will not be enough to finish the server, then clickhouse-local (that goes after) cannot obtain the logs due to status file will be locked, like in [1]: Code: 76. DB::Exception: Cannot lock file /var/lib/clickhouse/status. Another server instance in same directory is already running. (CANNOT_OPEN_FILE) [1]: https://s3.amazonaws.com/clickhouse-test-reports/35075/4a064e5b6f81136f2bf923d85001f25fa05d39ce/stateless_tests_flaky_check__address__actions_.html So use proper wait via "clickhouse stop" v2: Fix permissions pid file for replicated database servers They do not use default, /var/run/clickhouse-server, that do not have proper permissions. Fixes: #36885 Signed-off-by: Azat Khuzhin --- docker/test/stateless/run.sh | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/docker/test/stateless/run.sh b/docker/test/stateless/run.sh index e2ad91220d3..a5d59b3bcee 100755 --- a/docker/test/stateless/run.sh +++ b/docker/test/stateless/run.sh @@ -40,15 +40,18 @@ if [ "$NUM_TRIES" -gt "1" ]; then export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_TIME_US=10000 export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_TIME_US=10000 + mkdir -p /var/run/clickhouse-server # simpliest way to forward env variables to server - sudo -E -u clickhouse /usr/bin/clickhouse-server --config /etc/clickhouse-server/config.xml --daemon + sudo -E -u clickhouse /usr/bin/clickhouse-server --config /etc/clickhouse-server/config.xml --daemon --pid-file /var/run/clickhouse-server/clickhouse-server.pid else sudo clickhouse start fi if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]]; then - + mkdir -p /var/run/clickhouse-server1 + sudo chown clickhouse:clickhouse /var/run/clickhouse-server1 sudo -E -u clickhouse /usr/bin/clickhouse server --config /etc/clickhouse-server1/config.xml --daemon \ + --pid-file /var/run/clickhouse-server1/clickhouse-server.pid \ -- --path /var/lib/clickhouse1/ --logger.stderr /var/log/clickhouse-server/stderr1.log \ --logger.log /var/log/clickhouse-server/clickhouse-server1.log --logger.errorlog /var/log/clickhouse-server/clickhouse-server1.err.log \ --tcp_port 19000 --tcp_port_secure 19440 --http_port 18123 --https_port 18443 --interserver_http_port 19009 --tcp_with_proxy_port 19010 \ @@ -56,7 +59,10 @@ if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]] --keeper_server.tcp_port 19181 --keeper_server.server_id 2 \ --macros.replica r2 # It doesn't work :( + mkdir -p /var/run/clickhouse-server2 + sudo chown clickhouse:clickhouse /var/run/clickhouse-server2 sudo -E -u clickhouse /usr/bin/clickhouse server --config /etc/clickhouse-server2/config.xml --daemon \ + --pid-file /var/run/clickhouse-server2/clickhouse-server.pid \ -- --path /var/lib/clickhouse2/ --logger.stderr /var/log/clickhouse-server/stderr2.log \ --logger.log /var/log/clickhouse-server/clickhouse-server2.log --logger.errorlog /var/log/clickhouse-server/clickhouse-server2.err.log \ --tcp_port 29000 --tcp_port_secure 29440 --http_port 28123 --https_port 28443 --interserver_http_port 29009 --tcp_with_proxy_port 29010 \ @@ -134,18 +140,10 @@ clickhouse-client -q "system flush logs" ||: # Stop server so we can safely read data with clickhouse-local. # Why do we read data with clickhouse-local? # Because it's the simplest way to read it when server has crashed. -if [ "$NUM_TRIES" -gt "1" ]; then - clickhouse-client -q "system shutdown" ||: - sleep 10 -else - sudo clickhouse stop ||: -fi - - +sudo clickhouse stop ||: if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]]; then - clickhouse-client --port 19000 -q "system shutdown" ||: - clickhouse-client --port 29000 -q "system shutdown" ||: - sleep 10 + sudo clickhouse stop --pid-path /var/run/clickhouse-server1 ||: + sudo clickhouse stop --pid-path /var/run/clickhouse-server2 ||: fi grep -Fa "Fatal" /var/log/clickhouse-server/clickhouse-server.log ||: From 478b03c5cbad644c140b58bc7518d95a49426a6b Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Thu, 26 May 2022 13:06:48 +0300 Subject: [PATCH 046/204] tests/fuzzer: use "clickhouse stop" to stop the server Signed-off-by: Azat Khuzhin --- docker/test/fuzzer/run-fuzzer.sh | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/docker/test/fuzzer/run-fuzzer.sh b/docker/test/fuzzer/run-fuzzer.sh index 32799a669eb..39270a89763 100755 --- a/docker/test/fuzzer/run-fuzzer.sh +++ b/docker/test/fuzzer/run-fuzzer.sh @@ -125,16 +125,7 @@ function filter_exists_and_template function stop_server { clickhouse-client --query "select elapsed, query from system.processes" ||: - killall clickhouse-server ||: - for _ in {1..10} - do - if ! pgrep -f clickhouse-server - then - break - fi - sleep 1 - done - killall -9 clickhouse-server ||: + clickhouse stop # Debug. date @@ -159,10 +150,12 @@ function fuzz NEW_TESTS_OPT="${NEW_TESTS_OPT:-}" fi + mkdir -p /var/run/clickhouse-server + # interferes with gdb export CLICKHOUSE_WATCHDOG_ENABLE=0 # NOTE: we use process substitution here to preserve keep $! as a pid of clickhouse-server - clickhouse-server --config-file db/config.xml -- --path db > >(tail -100000 > server.log) 2>&1 & + clickhouse-server --config-file db/config.xml --pid-file /var/run/clickhouse-server/clickhouse-server.pid -- --path db > >(tail -100000 > server.log) 2>&1 & server_pid=$! kill -0 $server_pid From d90f21a56951349a63b1d2d99b9de35d846f5e1b Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Thu, 26 May 2022 13:10:27 +0300 Subject: [PATCH 047/204] tests/stateful: properly wait the server v2: fix permissions for /var/run/clickhouse-server? Signed-off-by: Azat Khuzhin --- docker/test/stateful/run.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docker/test/stateful/run.sh b/docker/test/stateful/run.sh index 6aa9d88f5b4..aed70d87160 100755 --- a/docker/test/stateful/run.sh +++ b/docker/test/stateful/run.sh @@ -22,17 +22,23 @@ ln -s /usr/share/clickhouse-test/clickhouse-test /usr/bin/clickhouse-test function start() { if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]]; then + mkdir -p /var/run/clickhouse-server1 + sudo chown clickhouse:clickhouse /var/run/clickhouse-server1 # NOTE We run "clickhouse server" instead of "clickhouse-server" # to make "pidof clickhouse-server" return single pid of the main instance. # We wil run main instance using "service clickhouse-server start" sudo -E -u clickhouse /usr/bin/clickhouse server --config /etc/clickhouse-server1/config.xml --daemon \ + --pid-file /var/run/clickhouse-server1/clickhouse-server.pid \ -- --path /var/lib/clickhouse1/ --logger.stderr /var/log/clickhouse-server/stderr1.log \ --logger.log /var/log/clickhouse-server/clickhouse-server1.log --logger.errorlog /var/log/clickhouse-server/clickhouse-server1.err.log \ --tcp_port 19000 --tcp_port_secure 19440 --http_port 18123 --https_port 18443 --interserver_http_port 19009 --tcp_with_proxy_port 19010 \ --mysql_port 19004 --postgresql_port 19005 \ --keeper_server.tcp_port 19181 --keeper_server.server_id 2 + mkdir -p /var/run/clickhouse-server2 + sudo chown clickhouse:clickhouse /var/run/clickhouse-server2 sudo -E -u clickhouse /usr/bin/clickhouse server --config /etc/clickhouse-server2/config.xml --daemon \ + --pid-file /var/run/clickhouse-server2/clickhouse-server.pid \ -- --path /var/lib/clickhouse2/ --logger.stderr /var/log/clickhouse-server/stderr2.log \ --logger.log /var/log/clickhouse-server/clickhouse-server2.log --logger.errorlog /var/log/clickhouse-server/clickhouse-server2.err.log \ --tcp_port 29000 --tcp_port_secure 29440 --http_port 28123 --https_port 28443 --interserver_http_port 29009 --tcp_with_proxy_port 29010 \ @@ -135,6 +141,12 @@ ls -la / /process_functional_tests_result.py || echo -e "failure\tCannot parse results" > /test_output/check_status.tsv +sudo clickhouse stop ||: +if [[ -n "$USE_DATABASE_REPLICATED" ]] && [[ "$USE_DATABASE_REPLICATED" -eq 1 ]]; then + sudo clickhouse stop --pid-path /var/run/clickhouse-server1 ||: + sudo clickhouse stop --pid-path /var/run/clickhouse-server2 ||: +fi + grep -Fa "Fatal" /var/log/clickhouse-server/clickhouse-server.log ||: pigz < /var/log/clickhouse-server/clickhouse-server.log > /test_output/clickhouse-server.log.gz ||: From 2570136e5ea4a6aff75688ecc0010c4575aed9e0 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Thu, 26 May 2022 13:11:13 +0300 Subject: [PATCH 048/204] tests/sqlancer: remove superior wait of the server stop Signed-off-by: Azat Khuzhin --- docker/test/sqlancer/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/sqlancer/run.sh b/docker/test/sqlancer/run.sh index e465ba1c993..a1891569d34 100755 --- a/docker/test/sqlancer/run.sh +++ b/docker/test/sqlancer/run.sh @@ -21,7 +21,7 @@ export NUM_QUERIES=1000 ( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPDistinct | tee /test_output/TLPDistinct.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPDistinct.err ( java -jar target/sqlancer-*.jar --num-threads 10 --timeout-seconds $TIMEOUT --num-queries $NUM_QUERIES --username default --password "" clickhouse --oracle TLPAggregate | tee /test_output/TLPAggregate.out ) 3>&1 1>&2 2>&3 | tee /test_output/TLPAggregate.err -service clickhouse-server stop && sleep 10 +service clickhouse stop ls /var/log/clickhouse-server/ tar czf /test_output/logs.tar.gz -C /var/log/clickhouse-server/ . From 827e73a2f4d6fa614357fc267d9834eb511a322c Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Thu, 26 May 2022 13:19:17 +0300 Subject: [PATCH 049/204] tests/fasttest: simplify waiting of the server Note, thiat removes "ability to run fasttest locally", but other jobs cannot do this. Signed-off-by: Azat Khuzhin --- docker/test/fasttest/run.sh | 62 ++++++------------------------------- 1 file changed, 9 insertions(+), 53 deletions(-) diff --git a/docker/test/fasttest/run.sh b/docker/test/fasttest/run.sh index cafc62b365e..2bbdd978e5e 100755 --- a/docker/test/fasttest/run.sh +++ b/docker/test/fasttest/run.sh @@ -37,38 +37,13 @@ export FASTTEST_DATA export FASTTEST_OUT export PATH -server_pid=none - -function stop_server -{ - if ! kill -0 -- "$server_pid" - then - echo "ClickHouse server pid '$server_pid' is not running" - return 0 - fi - - for _ in {1..60} - do - if ! pkill -f "clickhouse-server" && ! kill -- "$server_pid" ; then break ; fi - sleep 1 - done - - if kill -0 -- "$server_pid" - then - pstree -apgT - jobs - echo "Failed to kill the ClickHouse server pid '$server_pid'" - return 1 - fi - - server_pid=none -} - function start_server { set -m # Spawn server in its own process groups + local opts=( --config-file "$FASTTEST_DATA/config.xml" + --pid-file "$FASTTEST_DATA/clickhouse-server.pid" -- --path "$FASTTEST_DATA" --user_files_path "$FASTTEST_DATA/user_files" @@ -76,40 +51,22 @@ function start_server --keeper_server.storage_path "$FASTTEST_DATA/coordination" ) clickhouse-server "${opts[@]}" &>> "$FASTTEST_OUTPUT/server.log" & - server_pid=$! set +m - if [ "$server_pid" == "0" ] - then - echo "Failed to start ClickHouse server" - # Avoid zero PID because `kill` treats it as our process group PID. - server_pid="none" - return 1 - fi - - for _ in {1..60} - do - if clickhouse-client --query "select 1" || ! kill -0 -- "$server_pid" - then + for _ in {1..60}; do + if clickhouse-client --query "select 1"; then break fi sleep 1 done - if ! clickhouse-client --query "select 1" - then + if ! clickhouse-client --query "select 1"; then echo "Failed to wait until ClickHouse server starts." - server_pid="none" - return 1 - fi - - if ! kill -0 -- "$server_pid" - then - echo "Wrong clickhouse server started: PID '$server_pid' we started is not running, but '$(pgrep -f clickhouse-server)' is running" - server_pid="none" return 1 fi + local server_pid + server_pid="$(cat "$FASTTEST_DATA/clickhouse-server.pid")" echo "ClickHouse server pid '$server_pid' started and responded" } @@ -254,9 +211,6 @@ function run_tests clickhouse-server --version clickhouse-test --help - # Kill the server in case we are running locally and not in docker - stop_server ||: - start_server set +e @@ -284,6 +238,8 @@ function run_tests | ts '%Y-%m-%d %H:%M:%S' \ | tee "$FASTTEST_OUTPUT/test_result.txt" set -e + + clickhouse stop --pid-path "$FASTTEST_DATA" } case "$stage" in From 8e28ba2583f72fd9547b5fa27b75a3679bc2e3ea Mon Sep 17 00:00:00 2001 From: zvonand Date: Tue, 31 May 2022 18:15:51 +0300 Subject: [PATCH 050/204] fix DT64 reader --- src/IO/ReadHelpers.h | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/IO/ReadHelpers.h b/src/IO/ReadHelpers.h index feacbd085c2..664c4daf8e2 100644 --- a/src/IO/ReadHelpers.h +++ b/src/IO/ReadHelpers.h @@ -908,6 +908,8 @@ inline ReturnType readDateTimeTextImpl(DateTime64 & datetime64, UInt32 scale, Re return ReturnType(false); } + int negative_multiplier = 1; + DB::DecimalUtils::DecimalComponents components{static_cast(whole), 0}; if (!buf.eof() && *buf.position() == '.') @@ -934,29 +936,17 @@ inline ReturnType readDateTimeTextImpl(DateTime64 & datetime64, UInt32 scale, Re while (!buf.eof() && isNumericASCII(*buf.position())) ++buf.position(); - /// Keep sign of fractional part the same with whole part if datetime64 is negative - /// Case1: - /// 1965-12-12 12:12:12.123 - /// => whole = -127914468, fractional = 123(coefficient>0) - /// => new whole = -127914467, new fractional = 877(coefficient<0) - /// - /// Case2: - /// 1969-12-31 23:59:59.123 - /// => whole = -1, fractional = 123(coefficient>0) - /// => new whole = 0, new fractional = -877(coefficient>0) + /// Fractional part (subseconds) is treated as positive by users + /// (as DateTime64 itself is a positive, although underlying decimal is negative), + /// so it needs proper handling if (components.whole < 0 && components.fractional != 0) { const auto scale_multiplier = DecimalUtils::scaleMultiplier(scale); ++components.whole; - if (components.whole) + components.fractional = scale_multiplier - components.fractional; + if (!components.whole) { - /// whole keep the sign, fractional should be non-negative - components.fractional = scale_multiplier - components.fractional; - } - else - { - /// when whole is zero, fractional should keep the sign - components.fractional = components.fractional - scale_multiplier; + negative_multiplier = -1; } } } @@ -969,7 +959,7 @@ inline ReturnType readDateTimeTextImpl(DateTime64 & datetime64, UInt32 scale, Re components.whole = components.whole / common::exp10_i32(scale); } - datetime64 = DecimalUtils::decimalFromComponents(components, scale); + datetime64 = negative_multiplier * DecimalUtils::decimalFromComponents(components, scale); return ReturnType(true); } From 93be56a151cb07abc22e6d94383b79e8d4b2e03b Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Tue, 31 May 2022 14:12:44 -0700 Subject: [PATCH 051/204] Update grpc submodule to PR 9 --- contrib/grpc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/grpc b/contrib/grpc index 7eac189a6ba..5e23e96c0c0 160000 --- a/contrib/grpc +++ b/contrib/grpc @@ -1 +1 @@ -Subproject commit 7eac189a6badddac593580ec2ad1478bd2656fc7 +Subproject commit 5e23e96c0c02e451dbb291cf9f66231d02b6cdb6 From 17fb5d80687d644734e61aa880daf5db1a6d58a6 Mon Sep 17 00:00:00 2001 From: Suzy Wang Date: Wed, 1 Jun 2022 21:30:02 -0700 Subject: [PATCH 052/204] upgrade curl to 7.83.1 --- contrib/curl | 2 +- contrib/curl-cmake/CMakeLists.txt | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/contrib/curl b/contrib/curl index 801bd5138ce..462196e6b4a 160000 --- a/contrib/curl +++ b/contrib/curl @@ -1 +1 @@ -Subproject commit 801bd5138ce31aa0d906fa4e2eabfc599d74e793 +Subproject commit 462196e6b4a47f924293a0e26b8e9c23d37ac26f diff --git a/contrib/curl-cmake/CMakeLists.txt b/contrib/curl-cmake/CMakeLists.txt index b1e1a0ded8a..761ee036e66 100644 --- a/contrib/curl-cmake/CMakeLists.txt +++ b/contrib/curl-cmake/CMakeLists.txt @@ -84,7 +84,6 @@ set (SRCS "${LIBRARY_DIR}/lib/gopher.c" "${LIBRARY_DIR}/lib/idn_win32.c" "${LIBRARY_DIR}/lib/http_proxy.c" - "${LIBRARY_DIR}/lib/non-ascii.c" "${LIBRARY_DIR}/lib/asyn-thread.c" "${LIBRARY_DIR}/lib/curl_gssapi.c" "${LIBRARY_DIR}/lib/http_ntlm.c" @@ -93,10 +92,8 @@ set (SRCS "${LIBRARY_DIR}/lib/curl_sasl.c" "${LIBRARY_DIR}/lib/rand.c" "${LIBRARY_DIR}/lib/curl_multibyte.c" - "${LIBRARY_DIR}/lib/hostcheck.c" "${LIBRARY_DIR}/lib/conncache.c" "${LIBRARY_DIR}/lib/dotdot.c" - "${LIBRARY_DIR}/lib/x509asn1.c" "${LIBRARY_DIR}/lib/http2.c" "${LIBRARY_DIR}/lib/smb.c" "${LIBRARY_DIR}/lib/curl_endian.c" @@ -120,6 +117,9 @@ set (SRCS "${LIBRARY_DIR}/lib/http_aws_sigv4.c" "${LIBRARY_DIR}/lib/mqtt.c" "${LIBRARY_DIR}/lib/rename.c" + "${LIBRARY_DIR}/lib/h2h3.c" + "${LIBRARY_DIR}/lib/headers.c" + "${LIBRARY_DIR}/lib/timediff.c" "${LIBRARY_DIR}/lib/vauth/vauth.c" "${LIBRARY_DIR}/lib/vauth/cleartext.c" "${LIBRARY_DIR}/lib/vauth/cram.c" @@ -142,11 +142,13 @@ set (SRCS "${LIBRARY_DIR}/lib/vtls/sectransp.c" "${LIBRARY_DIR}/lib/vtls/gskit.c" "${LIBRARY_DIR}/lib/vtls/mbedtls.c" - "${LIBRARY_DIR}/lib/vtls/mesalink.c" "${LIBRARY_DIR}/lib/vtls/bearssl.c" "${LIBRARY_DIR}/lib/vtls/keylog.c" + "${LIBRARY_DIR}/lib/vtls/x509asn1.c" + "${LIBRARY_DIR}/lib/vtls/hostcheck.c" "${LIBRARY_DIR}/lib/vquic/ngtcp2.c" "${LIBRARY_DIR}/lib/vquic/quiche.c" + "${LIBRARY_DIR}/lib/vquic/msh3.c" "${LIBRARY_DIR}/lib/vssh/libssh2.c" "${LIBRARY_DIR}/lib/vssh/libssh.c" ) From 0588142b7ecdd21bbafb58c34527d65709266212 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 2 Jun 2022 11:56:06 +0200 Subject: [PATCH 053/204] Clarify that match() & like() assume UTF-8 The previous explanation sentence "The regular expression works with the string as if it is a set of bytes." suggested otherwise and since we don't have separate methods matchUTF8() and likeUTF8(), it makes sense to clarify. --- docs/en/sql-reference/functions/string-search-functions.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/en/sql-reference/functions/string-search-functions.md b/docs/en/sql-reference/functions/string-search-functions.md index 985d9f1e63a..8d35204d783 100644 --- a/docs/en/sql-reference/functions/string-search-functions.md +++ b/docs/en/sql-reference/functions/string-search-functions.md @@ -350,11 +350,12 @@ In all `multiSearch*` functions the number of needles should be less than 2 ## match(haystack, pattern) {#matchhaystack-pattern} -Checks whether the string matches the `pattern` regular expression. A `re2` regular expression. The [syntax](https://github.com/google/re2/wiki/Syntax) of the `re2` regular expressions is more limited than the syntax of the Perl regular expressions. +Checks whether the string matches the regular expression `pattern` in `re2` syntax. `Re2` has a more limited [syntax](https://github.com/google/re2/wiki/Syntax) than Perl regular expressions. Returns 0 if it does not match, or 1 if it matches. -The regular expression works with the string as if it is a set of bytes. The regular expression can’t contain null bytes. +Matching is based on UTF-8, e.g. `.` matches the two-codepoint symbol `¥`. The regular expression must not contain null bytes. + For patterns to search for substrings in a string, it is better to use LIKE or ‘position’, since they work much faster. ## multiMatchAny(haystack, \[pattern1, pattern2, …, patternn\]) {#multimatchanyhaystack-pattern1-pattern2-patternn} @@ -498,6 +499,8 @@ The regular expression can contain the metasymbols `%` and `_`. Use the backslash (`\`) for escaping metasymbols. See the note on escaping in the description of the ‘match’ function. +Matching is based on UTF-8, e.g. `_` matches the two-codepoint symbol `¥`. + For regular expressions like `%needle%`, the code is more optimal and works as fast as the `position` function. For other regular expressions, the code is the same as for the ‘match’ function. From a2b768ec10ea78c9057f7173881891fef1f66820 Mon Sep 17 00:00:00 2001 From: zvonand Date: Fri, 3 Jun 2022 01:30:17 +0300 Subject: [PATCH 054/204] added test --- .../02313_negative_datetime64.reference | 4 ++ .../0_stateless/02313_negative_datetime64.sql | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/queries/0_stateless/02313_negative_datetime64.reference create mode 100644 tests/queries/0_stateless/02313_negative_datetime64.sql diff --git a/tests/queries/0_stateless/02313_negative_datetime64.reference b/tests/queries/0_stateless/02313_negative_datetime64.reference new file mode 100644 index 00000000000..17474c96202 --- /dev/null +++ b/tests/queries/0_stateless/02313_negative_datetime64.reference @@ -0,0 +1,4 @@ +1 1 1 1 +1 1 1 1 +1 1 1 1 +1 1 1 1 diff --git a/tests/queries/0_stateless/02313_negative_datetime64.sql b/tests/queries/0_stateless/02313_negative_datetime64.sql new file mode 100644 index 00000000000..a5728074cf5 --- /dev/null +++ b/tests/queries/0_stateless/02313_negative_datetime64.sql @@ -0,0 +1,39 @@ +-- Before UNIX epoch +WITH + toDateTime64('1959-09-16 19:20:12.999999998', 9, 'UTC') AS dt1, + toDateTime64('1959-09-16 19:20:12.999999999', 9, 'UTC') AS dt2 +SELECT + dt1 < dt2, + (dt1 + INTERVAL 1 NANOSECOND) = dt2, + (dt1 + INTERVAL 2 NANOSECOND) > dt2, + (dt1 + INTERVAL 3 NANOSECOND) > dt2; + +-- At UNIX epoch border +WITH + toDateTime64('1969-12-31 23:59:59.999999998', 9, 'UTC') AS dt1, + toDateTime64('1969-12-31 23:59:59.999999999', 9, 'UTC') AS dt2 +SELECT + dt1 < dt2, + (dt1 + INTERVAL 1 NANOSECOND) = dt2, + (dt1 + INTERVAL 2 NANOSECOND) > dt2, + (dt1 + INTERVAL 3 NANOSECOND) > dt2; + +-- After UNIX epoch +WITH + toDateTime64('2001-12-31 23:59:59.999999998', 9, 'UTC') AS dt1, + toDateTime64('2001-12-31 23:59:59.999999999', 9, 'UTC') AS dt2 +SELECT + dt1 < dt2, + (dt1 + INTERVAL 1 NANOSECOND) = dt2, + (dt1 + INTERVAL 2 NANOSECOND) > dt2, + (dt1 + INTERVAL 3 NANOSECOND) > dt2; + +-- At upper DT64 bound (DT64 precision is lower here by design) +WITH + toDateTime64('2282-12-31 23:59:59.999998', 6, 'UTC') AS dt1, + toDateTime64('2282-12-31 23:59:59.999999', 6, 'UTC') AS dt2 +SELECT + dt1 < dt2, + (dt1 + INTERVAL 1 MICROSECOND) = dt2, + (dt1 + INTERVAL 2 MICROSECOND) > dt2, + (dt1 + INTERVAL 3 MICROSECOND) > dt2; From e6498a3235043a608bd6f59023334210e681780a Mon Sep 17 00:00:00 2001 From: zvonand Date: Fri, 3 Jun 2022 14:52:34 +0300 Subject: [PATCH 055/204] update comment --- src/IO/ReadHelpers.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/IO/ReadHelpers.h b/src/IO/ReadHelpers.h index 664c4daf8e2..8fe8e2aa23e 100644 --- a/src/IO/ReadHelpers.h +++ b/src/IO/ReadHelpers.h @@ -937,8 +937,9 @@ inline ReturnType readDateTimeTextImpl(DateTime64 & datetime64, UInt32 scale, Re ++buf.position(); /// Fractional part (subseconds) is treated as positive by users - /// (as DateTime64 itself is a positive, although underlying decimal is negative), - /// so it needs proper handling + /// (as DateTime64 itself is a positive, although underlying decimal is negative) + /// setting fractional part to be negative when whole is 0 results in wrong value, + /// so we multiply result by -1. if (components.whole < 0 && components.fractional != 0) { const auto scale_multiplier = DecimalUtils::scaleMultiplier(scale); From e55a441604b7146cf19e47eca39f3510658b8a13 Mon Sep 17 00:00:00 2001 From: Suzy Wang Date: Fri, 3 Jun 2022 08:10:55 -0700 Subject: [PATCH 056/204] trigger rebuild From b7b9936cc4635cc993c595242897da7c598fa88d Mon Sep 17 00:00:00 2001 From: Suzy Wang Date: Mon, 6 Jun 2022 07:37:32 -0700 Subject: [PATCH 057/204] Retrigger CI From fddd031385516c0e5150623ae4ea2e385017dd71 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 4 May 2022 23:36:26 +0300 Subject: [PATCH 058/204] Randomize settings related to in-order read/aggregation Signed-off-by: Azat Khuzhin --- tests/clickhouse-test | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 355f7b7a712..648072eea24 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -420,6 +420,10 @@ class SettingsRandomizer: "max_block_size": lambda: random.randint(8000, 100000), "max_threads": lambda: random.randint(1, 64), "optimize_or_like_chain": lambda: random.randint(0, 1), + "optimize_read_in_order": lambda: random.randint(0, 1), + "read_in_order_two_level_merge_threshold": lambda: random.randint(0, 100), + "optimize_aggregation_in_order": lambda: random.randint(0, 1), + "aggregation_in_order_max_block_bytes": lambda: random.randint(0, 50000000), } @staticmethod From 23d4837e0a62dad85c569152b0c8f8cf454dd152 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Thu, 5 May 2022 08:03:56 +0300 Subject: [PATCH 059/204] tests: add explicit settings/ORDER BY for in order reading/aggregation v2: rebase on top of upstream/master v3: fix 01780_column_sparse_full v4: fix 01410_nullable_key_and_index Signed-off-by: Azat Khuzhin --- .../0_stateless/01045_order_by_pk_special_storages.sh | 6 +++--- tests/queries/0_stateless/01232_untuple.reference | 8 ++++---- tests/queries/0_stateless/01232_untuple.sql | 2 +- tests/queries/0_stateless/01268_mergine_sorted_limit.sql | 4 ++-- .../0_stateless/01323_add_scalars_in_time.reference | 2 +- tests/queries/0_stateless/01323_add_scalars_in_time.sql | 3 ++- .../0_stateless/01410_nullable_key_and_index.reference | 4 ++-- .../queries/0_stateless/01410_nullable_key_and_index.sql | 3 ++- .../01513_optimize_aggregation_in_order_memory_long.sql | 2 +- .../0_stateless/01521_alter_enum_and_reverse_read.sql | 2 +- .../01562_optimize_monotonous_functions_in_order_by.sql | 1 + tests/queries/0_stateless/01786_explain_merge_tree.sh | 2 +- .../0_stateless/02149_read_in_order_fixed_prefix.sql | 2 ++ .../0_stateless/02155_read_in_order_max_rows_to_read.sql | 1 + .../02233_optimize_aggregation_in_order_prefix.reference | 4 ++-- .../02233_optimize_aggregation_in_order_prefix.sql | 4 ++-- 16 files changed, 28 insertions(+), 22 deletions(-) diff --git a/tests/queries/0_stateless/01045_order_by_pk_special_storages.sh b/tests/queries/0_stateless/01045_order_by_pk_special_storages.sh index a46fedb4533..bb76f3978cc 100755 --- a/tests/queries/0_stateless/01045_order_by_pk_special_storages.sh +++ b/tests/queries/0_stateless/01045_order_by_pk_special_storages.sh @@ -24,7 +24,7 @@ $CLICKHOUSE_CLIENT -q "SELECT a FROM m ORDER BY a LIMIT 5" $CLICKHOUSE_CLIENT -q "SELECT a, s FROM m ORDER BY a, s LIMIT 10" # Not a single .sql test with max_rows_to_read because it doesn't work with Merge storage -rows_read=$($CLICKHOUSE_CLIENT -q "SELECT a FROM m ORDER BY a LIMIT 10 FORMAT JSON" --max_threads=1 --max_block_size=20 | grep "rows_read" | sed 's/[^0-9]*//g') +rows_read=$($CLICKHOUSE_CLIENT -q "SELECT a FROM m ORDER BY a LIMIT 10 FORMAT JSON" --max_threads=1 --max_block_size=20 --optimize_read_in_order=1 | grep "rows_read" | sed 's/[^0-9]*//g') # Expected number of read rows with a bit margin if [[ $rows_read -lt 500 ]] @@ -36,7 +36,7 @@ fi $CLICKHOUSE_CLIENT -q "SELECT '---StorageBuffer---'" $CLICKHOUSE_CLIENT -q "CREATE TABLE buf (a UInt32, s String) engine = Buffer('$CLICKHOUSE_DATABASE', s2, 16, 10, 100, 10000, 1000000, 10000000, 100000000)" $CLICKHOUSE_CLIENT -q "SELECT a, s FROM buf ORDER BY a, s LIMIT 10" -rows_read=$($CLICKHOUSE_CLIENT -q "SELECT a FROM buf ORDER BY a LIMIT 10 FORMAT JSON" --max_threads=1 --max_block_size=20 | grep "rows_read" | sed 's/[^0-9]*//g') +rows_read=$($CLICKHOUSE_CLIENT -q "SELECT a FROM buf ORDER BY a LIMIT 10 FORMAT JSON" --max_threads=1 --max_block_size=20 --optimize_read_in_order=1 | grep "rows_read" | sed 's/[^0-9]*//g') # Expected number of read rows with a bit margin if [[ $rows_read -lt 500 ]] @@ -48,7 +48,7 @@ fi $CLICKHOUSE_CLIENT -q "SELECT '---MaterializedView---'" $CLICKHOUSE_CLIENT -q "CREATE MATERIALIZED VIEW mv (a UInt32, s String) engine = MergeTree ORDER BY s SETTINGS min_bytes_for_wide_part = 0 POPULATE AS SELECT a, s FROM s1 WHERE a % 7 = 0" $CLICKHOUSE_CLIENT -q "SELECT a, s FROM mv ORDER BY s LIMIT 10" -rows_read=$($CLICKHOUSE_CLIENT -q "SELECT a, s FROM mv ORDER BY s LIMIT 10 FORMAT JSON" --max_threads=1 --max_block_size=20 | grep "rows_read" | sed 's/[^0-9]*//g') +rows_read=$($CLICKHOUSE_CLIENT -q "SELECT a, s FROM mv ORDER BY s LIMIT 10 FORMAT JSON" --max_threads=1 --max_block_size=20 --optimize_read_in_order=1 | grep "rows_read" | sed 's/[^0-9]*//g') if [[ $rows_read -lt 500 ]] then echo "OK" diff --git a/tests/queries/0_stateless/01232_untuple.reference b/tests/queries/0_stateless/01232_untuple.reference index 21fd0c4a8a5..8e1f97d2585 100644 --- a/tests/queries/0_stateless/01232_untuple.reference +++ b/tests/queries/0_stateless/01232_untuple.reference @@ -3,11 +3,11 @@ hello 1 3 world 9 9 (0,1) key tupleElement(argMax(tuple(v1, v2, v3, v4, v5), v1), 1) tupleElement(argMax(tuple(v1, v2, v3, v4, v5), v1), 2) tupleElement(argMax(tuple(v1, v2, v3, v4, v5), v1), 3) tupleElement(argMax(tuple(v1, v2, v3, v4, v5), v1), 4) tupleElement(argMax(tuple(v1, v2, v3, v4, v5), v1), 5) -4 10 20 10 20 30 -3 70 20 10 20 30 -2 11 20 10 20 30 -5 10 20 10 20 30 1 20 20 10 20 30 +2 11 20 10 20 30 +3 70 20 10 20 30 +4 10 20 10 20 30 +5 10 20 10 20 30 6 10 20 10 20 30 7 18 20 10 20 30 8 30 20 10 20 30 diff --git a/tests/queries/0_stateless/01232_untuple.sql b/tests/queries/0_stateless/01232_untuple.sql index 39ee9e82fa7..92150e92b29 100644 --- a/tests/queries/0_stateless/01232_untuple.sql +++ b/tests/queries/0_stateless/01232_untuple.sql @@ -6,5 +6,5 @@ select argMax(untuple(x)), min(x) from (select (number, number + 1) as x from nu drop table if exists kv; create table kv (key int, v1 int, v2 int, v3 int, v4 int, v5 int) engine MergeTree order by key; insert into kv values (1, 10, 20, 10, 20, 30), (2, 11, 20, 10, 20, 30), (1, 18, 20, 10, 20, 30), (1, 20, 20, 10, 20, 30), (3, 70, 20, 10, 20, 30), (4, 10, 20, 10, 20, 30), (1, 10, 20, 10, 20, 30), (5, 10, 20, 10, 20, 30), (1, 10, 20, 10, 20, 30), (8, 30, 20, 10, 20, 30), (1, 10, 20, 10, 20, 30), (6, 10, 20, 10, 20, 30), (1, 10, 20, 10, 20, 30), (7, 18, 20, 10, 20, 30), (1, 10, 20, 10, 20, 30), (7, 10, 20, 10, 20, 30), (1, 10, 20, 10, 20, 30), (8, 10, 20, 10, 20, 30), (1, 10, 20, 10, 20, 30); -select key, untuple(argMax((* except (key),), v1)) from kv group by key format TSVWithNames; +select key, untuple(argMax((* except (key),), v1)) from kv group by key order by key format TSVWithNames; drop table if exists kv; diff --git a/tests/queries/0_stateless/01268_mergine_sorted_limit.sql b/tests/queries/0_stateless/01268_mergine_sorted_limit.sql index fbe047a3a77..49d8161bf83 100644 --- a/tests/queries/0_stateless/01268_mergine_sorted_limit.sql +++ b/tests/queries/0_stateless/01268_mergine_sorted_limit.sql @@ -6,7 +6,7 @@ INSERT INTO tab VALUES (1,1),(1,2),(1,3),(1,4),(1,5); INSERT INTO tab VALUES (2,6),(2,7),(2,8),(2,9),(2,0); -SELECT * FROM tab ORDER BY x LIMIT 3; -SELECT * FROM tab ORDER BY x LIMIT 4; +SELECT * FROM tab ORDER BY x LIMIT 3 SETTINGS optimize_read_in_order=1; +SELECT * FROM tab ORDER BY x LIMIT 4 SETTINGS optimize_read_in_order=1; DROP TABLE IF EXISTS tab; diff --git a/tests/queries/0_stateless/01323_add_scalars_in_time.reference b/tests/queries/0_stateless/01323_add_scalars_in_time.reference index 408efa7f823..bffe4d46ab2 100644 --- a/tests/queries/0_stateless/01323_add_scalars_in_time.reference +++ b/tests/queries/0_stateless/01323_add_scalars_in_time.reference @@ -1,5 +1,5 @@ -[0,2,3] id2 [1,2,3] id1 +[0,2,3] id2 test [1,2,3,4] 2 fre 3 jhg diff --git a/tests/queries/0_stateless/01323_add_scalars_in_time.sql b/tests/queries/0_stateless/01323_add_scalars_in_time.sql index 2ee5603f760..c337cd86f5b 100644 --- a/tests/queries/0_stateless/01323_add_scalars_in_time.sql +++ b/tests/queries/0_stateless/01323_add_scalars_in_time.sql @@ -16,7 +16,8 @@ WITH SELECT arraySort(arrayIntersect(argMax(seqs, create_time), arr1)) AS common, id FROM tags WHERE id LIKE 'id%' -GROUP BY id; +GROUP BY id +ORDER BY id; DROP TABLE tags; diff --git a/tests/queries/0_stateless/01410_nullable_key_and_index.reference b/tests/queries/0_stateless/01410_nullable_key_and_index.reference index da88fbddd7a..37456e6c8d6 100644 --- a/tests/queries/0_stateless/01410_nullable_key_and_index.reference +++ b/tests/queries/0_stateless/01410_nullable_key_and_index.reference @@ -8,9 +8,9 @@ 14 21 16 24 18 27 -\N 0 -\N -1 \N -2 +\N -1 +\N 0 \N 0 \N -1 \N -2 diff --git a/tests/queries/0_stateless/01410_nullable_key_and_index.sql b/tests/queries/0_stateless/01410_nullable_key_and_index.sql index 969432eba01..905d997d95c 100644 --- a/tests/queries/0_stateless/01410_nullable_key_and_index.sql +++ b/tests/queries/0_stateless/01410_nullable_key_and_index.sql @@ -3,13 +3,14 @@ DROP TABLE IF EXISTS nullable_key_without_final_mark; DROP TABLE IF EXISTS nullable_minmax_index; SET max_threads = 1; +SET optimize_read_in_order=0; CREATE TABLE nullable_key (k Nullable(int), v int) ENGINE MergeTree ORDER BY k SETTINGS allow_nullable_key = 1, index_granularity = 1; INSERT INTO nullable_key SELECT number * 2, number * 3 FROM numbers(10); INSERT INTO nullable_key SELECT NULL, -number FROM numbers(3); -SELECT * FROM nullable_key ORDER BY k; +SELECT * FROM nullable_key ORDER BY k, v; SET force_primary_key = 1; SET max_rows_to_read = 3; diff --git a/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql b/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql index cca994e8e4a..784dd73b865 100644 --- a/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql +++ b/tests/queries/0_stateless/01513_optimize_aggregation_in_order_memory_long.sql @@ -12,7 +12,7 @@ set max_memory_usage='500M'; set max_threads=1; set max_block_size=500; -select key, groupArray(repeat('a', 200)), count() from data_01513 group by key format Null; -- { serverError 241; } +select key, groupArray(repeat('a', 200)), count() from data_01513 group by key format Null settings optimize_aggregation_in_order=0; -- { serverError 241; } select key, groupArray(repeat('a', 200)), count() from data_01513 group by key format Null settings optimize_aggregation_in_order=1; -- for WITH TOTALS previous groups should be kept. select key, groupArray(repeat('a', 200)), count() from data_01513 group by key with totals format Null settings optimize_aggregation_in_order=1; -- { serverError 241; } diff --git a/tests/queries/0_stateless/01521_alter_enum_and_reverse_read.sql b/tests/queries/0_stateless/01521_alter_enum_and_reverse_read.sql index 014790a61c1..b5391517c14 100644 --- a/tests/queries/0_stateless/01521_alter_enum_and_reverse_read.sql +++ b/tests/queries/0_stateless/01521_alter_enum_and_reverse_read.sql @@ -8,6 +8,6 @@ ALTER TABLE enum_test MODIFY COLUMN e Enum8('IU' = 1, 'WS' = 2, 'PS' = 3); INSERT INTO enum_test SELECT '2020-10-09 00:00:00', 'h1', 'PS' from numbers(1); -SELECT * FROM enum_test ORDER BY timestamp, e desc; +SELECT * FROM enum_test ORDER BY timestamp, e desc SETTINGS optimize_read_in_order=1; DROP TABLE IF EXISTS enum_test; diff --git a/tests/queries/0_stateless/01562_optimize_monotonous_functions_in_order_by.sql b/tests/queries/0_stateless/01562_optimize_monotonous_functions_in_order_by.sql index b31457d8f68..15ddb5a848f 100644 --- a/tests/queries/0_stateless/01562_optimize_monotonous_functions_in_order_by.sql +++ b/tests/queries/0_stateless/01562_optimize_monotonous_functions_in_order_by.sql @@ -1,4 +1,5 @@ SET optimize_monotonous_functions_in_order_by = 1; +SET optimize_read_in_order = 1; DROP TABLE IF EXISTS test_order_by; diff --git a/tests/queries/0_stateless/01786_explain_merge_tree.sh b/tests/queries/0_stateless/01786_explain_merge_tree.sh index eb47f065044..138905c65e7 100755 --- a/tests/queries/0_stateless/01786_explain_merge_tree.sh +++ b/tests/queries/0_stateless/01786_explain_merge_tree.sh @@ -4,7 +4,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -CLICKHOUSE_CLIENT="$CLICKHOUSE_CLIENT --optimize_move_to_prewhere=1 --convert_query_to_cnf=0" +CLICKHOUSE_CLIENT="$CLICKHOUSE_CLIENT --optimize_move_to_prewhere=1 --convert_query_to_cnf=0 --optimize_read_in_order=1" $CLICKHOUSE_CLIENT -q "drop table if exists test_index" $CLICKHOUSE_CLIENT -q "drop table if exists idx" diff --git a/tests/queries/0_stateless/02149_read_in_order_fixed_prefix.sql b/tests/queries/0_stateless/02149_read_in_order_fixed_prefix.sql index 8fb11ac383c..4dfcbb9bf80 100644 --- a/tests/queries/0_stateless/02149_read_in_order_fixed_prefix.sql +++ b/tests/queries/0_stateless/02149_read_in_order_fixed_prefix.sql @@ -1,4 +1,6 @@ SET max_threads=0; +SET optimize_read_in_order=1; +SET read_in_order_two_level_merge_threshold=100; DROP TABLE IF EXISTS t_read_in_order; diff --git a/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql b/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql index e82c78b5e42..9846c1208a1 100644 --- a/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql +++ b/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql @@ -7,6 +7,7 @@ SETTINGS index_granularity = 4; INSERT INTO t_max_rows_to_read SELECT number FROM numbers(100); SET max_threads = 1; +SET optimize_read_in_order = 1; SELECT a FROM t_max_rows_to_read WHERE a = 10 SETTINGS max_rows_to_read = 4; diff --git a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference index 9d252c9f396..e08e89fdff9 100644 --- a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference +++ b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference @@ -20,7 +20,7 @@ ExpressionTransform × 2 ExpressionTransform (ReadFromMergeTree) MergeTreeInOrder 0 → 1 -explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1; +explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0; (Expression) ExpressionTransform × 2 (Sorting) @@ -103,7 +103,7 @@ select parent_key, child_key, count() from data_02233 group by parent_key, child 9 2 3 0 0 100 -select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1; +select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0; 0 0 4 0 1 3 0 2 3 diff --git a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql index cf1e825b03d..5f64c929a5f 100644 --- a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql +++ b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql @@ -8,10 +8,10 @@ SELECT child_key, parent_key, child_key FROM data_02233 GROUP BY parent_key, chi -- { echoOn } insert into data_02233 select number%10, number%3, number from numbers(100); explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1; -explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1; +explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0; select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1; select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1, max_block_size=1; -select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1; +select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0; -- fuzzer SELECT child_key, parent_key, child_key FROM data_02233 GROUP BY parent_key, child_key, child_key ORDER BY child_key, parent_key ASC NULLS LAST SETTINGS max_threads = 1, optimize_aggregation_in_order = 1; From 6cdb0bcb6e73234087b11c8ec8e69732ca5d888d Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 1 Jun 2022 07:18:20 +0300 Subject: [PATCH 060/204] tests: fix 02233_optimize_aggregation_in_order_prefix --- .../02233_optimize_aggregation_in_order_prefix.reference | 4 ++-- .../02233_optimize_aggregation_in_order_prefix.sql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference index e08e89fdff9..f98effbec67 100644 --- a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference +++ b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.reference @@ -2,7 +2,7 @@ 0 0 0 -- { echoOn } insert into data_02233 select number%10, number%3, number from numbers(100); -explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1; +explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1, read_in_order_two_level_merge_threshold=1; (Expression) ExpressionTransform × 2 (Sorting) @@ -20,7 +20,7 @@ ExpressionTransform × 2 ExpressionTransform (ReadFromMergeTree) MergeTreeInOrder 0 → 1 -explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0; +explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0, read_in_order_two_level_merge_threshold=1; (Expression) ExpressionTransform × 2 (Sorting) diff --git a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql index 5f64c929a5f..233599feb65 100644 --- a/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql +++ b/tests/queries/0_stateless/02233_optimize_aggregation_in_order_prefix.sql @@ -7,8 +7,8 @@ SELECT child_key, parent_key, child_key FROM data_02233 GROUP BY parent_key, chi -- { echoOn } insert into data_02233 select number%10, number%3, number from numbers(100); -explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1; -explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0; +explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1, read_in_order_two_level_merge_threshold=1; +explain pipeline select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0, read_in_order_two_level_merge_threshold=1; select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1; select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=1, max_block_size=1; select parent_key, child_key, count() from data_02233 group by parent_key, child_key with totals order by parent_key, child_key settings max_threads=1, optimize_aggregation_in_order=0; From d5064dd5b5480f93966f847e4752d620eebc6656 Mon Sep 17 00:00:00 2001 From: koloshmet Date: Wed, 8 Jun 2022 02:17:34 +0300 Subject: [PATCH 061/204] so many improvements --- src/Compression/CompressionCodecFPC.cpp | 129 +++++++++++++----------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index f3106204e01..3b66060b6fc 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -17,12 +17,15 @@ namespace DB class CompressionCodecFPC : public ICompressionCodec { public: - explicit CompressionCodecFPC(UInt8 float_size, UInt8 compression_level); + CompressionCodecFPC(UInt8 float_size, UInt8 compression_level); uint8_t getMethodByte() const override; void updateHash(SipHash & hash) const override; + static constexpr UInt8 MAX_COMPRESSION_LEVEL{28}; + static constexpr UInt8 DEFAULT_COMPRESSION_LEVEL{12}; + protected: UInt32 doCompressData(const char * source, UInt32 source_size, char * dest) const override; @@ -33,11 +36,11 @@ protected: bool isCompression() const override { return true; } bool isGenericCompression() const override { return false; } +private: static constexpr UInt32 HEADER_SIZE{3}; -private: - UInt8 float_width; - UInt8 level; + UInt8 float_width; // size of uncompressed float in bytes + UInt8 level; // compression level, 2^level * float_width is the size of predictors table in bytes }; @@ -93,21 +96,21 @@ UInt8 getFloatBytesSize(const IDataType & column_type) column_type.getName()); } -UInt8 encodeEndianness(std::endian endian) +std::byte encodeEndianness(std::endian endian) { switch (endian) { case std::endian::little: - return 0; + return std::byte{0}; case std::endian::big: - return 1; + return std::byte{1}; } throw Exception("Unsupported endianness", ErrorCodes::BAD_ARGUMENTS); } -std::endian decodeEndianness(UInt8 endian) +std::endian decodeEndianness(std::byte endian) { - switch (endian) + switch (std::to_integer(endian)) { case 0: return std::endian::little; @@ -128,7 +131,7 @@ void registerCodecFPC(CompressionCodecFactory & factory) if (column_type != nullptr) float_width = getFloatBytesSize(*column_type); - UInt8 level{12}; + UInt8 level = CompressionCodecFPC::DEFAULT_COMPRESSION_LEVEL; if (arguments && !arguments->children.empty()) { if (arguments->children.size() > 1) @@ -144,6 +147,8 @@ void registerCodecFPC(CompressionCodecFactory & factory) level = literal->value.safeGet(); if (level == 0) throw Exception("FPC codec level must be at least 1", ErrorCodes::ILLEGAL_CODEC_PARAMETER); + if (level > CompressionCodecFPC::MAX_COMPRESSION_LEVEL) + throw Exception("FPC codec level must be at most 28", ErrorCodes::ILLEGAL_CODEC_PARAMETER); } return std::make_shared(float_width, level); }; @@ -153,7 +158,8 @@ void registerCodecFPC(CompressionCodecFactory & factory) namespace { -template requires (sizeof(TUint) >= 4) +template + requires (sizeof(TUint) >= 4) class DfcmPredictor { public: @@ -189,11 +195,12 @@ private: } std::vector table; - TUint prev_value{0}; - std::size_t hash{0}; + TUint prev_value; + std::size_t hash; }; -template requires (sizeof(TUint) >= 4) +template + requires (sizeof(TUint) >= 4) class FcmPredictor { public: @@ -228,7 +235,7 @@ private: } std::vector table; - std::size_t hash{0}; + std::size_t hash; }; template @@ -238,13 +245,15 @@ class FPCOperation static constexpr std::size_t CHUNK_SIZE{64}; static constexpr auto VALUE_SIZE = sizeof(TUint); - static constexpr std::byte DFCM_BIT_1{1u << 7}; - static constexpr std::byte DFCM_BIT_2{1u << 3}; - static constexpr unsigned MAX_COMPRESSED_SIZE{0b111u}; + static constexpr std::byte FCM_BIT{0}; + static constexpr std::byte DFCM_BIT{1u << 3}; + static constexpr auto DFCM_BIT_1 = DFCM_BIT << 4; + static constexpr auto DFCM_BIT_2 = DFCM_BIT; + static constexpr unsigned MAX_ZERO_BYTE_COUNT{0b111u}; public: - explicit FPCOperation(std::span destination, UInt8 compression_level) - : dfcm_predictor(1 << compression_level), fcm_predictor(1 << compression_level), chunk{}, result{destination} + FPCOperation(std::span destination, UInt8 compression_level) + : dfcm_predictor(1u << compression_level), fcm_predictor(1u << compression_level), chunk{}, result{destination} { } @@ -317,22 +326,22 @@ private: { TUint value; unsigned compressed_size; - bool is_dfcm_predictor; + std::byte predictor; }; - unsigned encodeCompressedSize(int compressed) + unsigned encodeCompressedZeroByteCount(int compressed) { - if constexpr (VALUE_SIZE > MAX_COMPRESSED_SIZE) + if constexpr (VALUE_SIZE == MAX_ZERO_BYTE_COUNT + 1) { if (compressed >= 4) --compressed; } - return std::min(static_cast(compressed), MAX_COMPRESSED_SIZE); + return std::min(static_cast(compressed), MAX_ZERO_BYTE_COUNT); } - unsigned decodeCompressedSize(unsigned encoded_size) + unsigned decodeCompressedZeroByteCount(unsigned encoded_size) { - if constexpr (VALUE_SIZE > MAX_COMPRESSED_SIZE) + if constexpr (VALUE_SIZE == MAX_ZERO_BYTE_COUNT + 1) { if (encoded_size > 3) ++encoded_size; @@ -342,6 +351,8 @@ private: CompressedValue compressValue(TUint value) noexcept { + static constexpr auto BITS_PER_BYTE = std::numeric_limits::digits; + TUint compressed_dfcm = dfcm_predictor.predict() ^ value; TUint compressed_fcm = fcm_predictor.predict() ^ value; dfcm_predictor.add(value); @@ -349,29 +360,26 @@ private: auto zeroes_dfcm = std::countl_zero(compressed_dfcm); auto zeroes_fcm = std::countl_zero(compressed_fcm); if (zeroes_dfcm > zeroes_fcm) - return {compressed_dfcm, encodeCompressedSize(zeroes_dfcm / CHAR_BIT), true}; - return {compressed_fcm, encodeCompressedSize(zeroes_fcm / CHAR_BIT), false}; + return {compressed_dfcm, encodeCompressedZeroByteCount(zeroes_dfcm / BITS_PER_BYTE), DFCM_BIT}; + return {compressed_fcm, encodeCompressedZeroByteCount(zeroes_fcm / BITS_PER_BYTE), FCM_BIT}; } void encodePair(TUint first, TUint second) { - auto [value1, compressed_size1, is_dfcm_predictor1] = compressValue(first); - auto [value2, compressed_size2, is_dfcm_predictor2] = compressValue(second); + auto [value1, zero_byte_count1, predictor1] = compressValue(first); + auto [value2, zero_byte_count2, predictor2] = compressValue(second); std::byte header{0x0}; - if (is_dfcm_predictor1) - header |= DFCM_BIT_1; - if (is_dfcm_predictor2) - header |= DFCM_BIT_2; - header |= static_cast((compressed_size1 << 4) | compressed_size2); + header |= (predictor1 << 4) | predictor2; + header |= static_cast((zero_byte_count1 << 4) | zero_byte_count2); result.front() = header; - compressed_size1 = decodeCompressedSize(compressed_size1); - compressed_size2 = decodeCompressedSize(compressed_size2); - auto tail_size1 = VALUE_SIZE - compressed_size1; - auto tail_size2 = VALUE_SIZE - compressed_size2; + zero_byte_count1 = decodeCompressedZeroByteCount(zero_byte_count1); + zero_byte_count2 = decodeCompressedZeroByteCount(zero_byte_count2); + auto tail_size1 = VALUE_SIZE - zero_byte_count1; + auto tail_size2 = VALUE_SIZE - zero_byte_count2; - std::memcpy(result.data() + 1, valueTail(value1, compressed_size1), tail_size1); - std::memcpy(result.data() + 1 + tail_size1, valueTail(value2, compressed_size2), tail_size2); + std::memcpy(result.data() + 1, valueTail(value1, zero_byte_count1), tail_size1); + std::memcpy(result.data() + 1 + tail_size1, valueTail(value2, zero_byte_count2), tail_size2); result = result.subspan(1 + tail_size1 + tail_size2); } @@ -406,11 +414,13 @@ private: if (bytes.empty()) throw Exception(ErrorCodes::CANNOT_DECOMPRESS, "Unexpected end of encoded sequence"); - auto compressed_size1 = decodeCompressedSize(static_cast(bytes.front() >> 4) & MAX_COMPRESSED_SIZE); - auto compressed_size2 = decodeCompressedSize(static_cast(bytes.front()) & MAX_COMPRESSED_SIZE); + auto zero_byte_count1 = decodeCompressedZeroByteCount( + std::to_integer(bytes.front() >> 4) & MAX_ZERO_BYTE_COUNT); + auto zero_byte_count2 = decodeCompressedZeroByteCount( + std::to_integer(bytes.front()) & MAX_ZERO_BYTE_COUNT); - auto tail_size1 = VALUE_SIZE - compressed_size1; - auto tail_size2 = VALUE_SIZE - compressed_size2; + auto tail_size1 = VALUE_SIZE - zero_byte_count1; + auto tail_size2 = VALUE_SIZE - zero_byte_count2; if (bytes.size() < 1 + tail_size1 + tail_size2) throw Exception(ErrorCodes::CANNOT_DECOMPRESS, "Unexpected end of encoded sequence"); @@ -418,13 +428,13 @@ private: TUint value1{0}; TUint value2{0}; - std::memcpy(valueTail(value1, compressed_size1), bytes.data() + 1, tail_size1); - std::memcpy(valueTail(value2, compressed_size2), bytes.data() + 1 + tail_size1, tail_size2); + std::memcpy(valueTail(value1, zero_byte_count1), bytes.data() + 1, tail_size1); + std::memcpy(valueTail(value2, zero_byte_count2), bytes.data() + 1 + tail_size1, tail_size2); - auto is_dfcm_predictor1 = static_cast(bytes.front() & DFCM_BIT_1); - auto is_dfcm_predictor2 = static_cast(bytes.front() & DFCM_BIT_2); - first = decompressValue(value1, is_dfcm_predictor1 != 0); - second = decompressValue(value2, is_dfcm_predictor2 != 0); + auto is_dfcm_predictor1 = std::to_integer(bytes.front() & DFCM_BIT_1) != 0; + auto is_dfcm_predictor2 = std::to_integer(bytes.front() & DFCM_BIT_2) != 0; + first = decompressValue(value1, is_dfcm_predictor1); + second = decompressValue(value2, is_dfcm_predictor2); return 1 + tail_size1 + tail_size2; } @@ -453,7 +463,7 @@ UInt32 CompressionCodecFPC::doCompressData(const char * source, UInt32 source_si { dest[0] = static_cast(float_width); dest[1] = static_cast(level); - dest[2] = static_cast(encodeEndianness(std::endian::native)); + dest[2] = std::to_integer(encodeEndianness(std::endian::native)); auto dest_size = getMaxCompressedDataSize(source_size); auto destination = std::as_writable_bytes(std::span(dest, dest_size).subspan(HEADER_SIZE)); @@ -475,17 +485,16 @@ void CompressionCodecFPC::doDecompressData(const char * source, UInt32 source_si if (source_size < HEADER_SIZE) throw Exception("Cannot decompress. File has wrong header", ErrorCodes::CANNOT_DECOMPRESS); - auto compressed_data = std::span(source, source_size); - if (decodeEndianness(static_cast(compressed_data[2])) != std::endian::native) + auto compressed_data = std::as_bytes(std::span(source, source_size)); + auto compressed_float_width = std::to_integer(compressed_data[0]); + auto compressed_level = std::to_integer(compressed_data[1]); + if (compressed_level == 0 || compressed_level > MAX_COMPRESSION_LEVEL) + throw Exception("Cannot decompress. File has incorrect level", ErrorCodes::CANNOT_DECOMPRESS); + if (decodeEndianness(compressed_data[2]) != std::endian::native) throw Exception("Cannot decompress. File has incorrect endianness", ErrorCodes::CANNOT_DECOMPRESS); - auto compressed_float_width = static_cast(compressed_data[0]); - auto compressed_level = static_cast(compressed_data[1]); - if (compressed_level == 0) - throw Exception("Cannot decompress. File has incorrect level", ErrorCodes::CANNOT_DECOMPRESS); - auto destination = std::as_writable_bytes(std::span(dest, uncompressed_size)); - auto src = std::as_bytes(compressed_data.subspan(HEADER_SIZE)); + auto src = compressed_data.subspan(HEADER_SIZE); switch (compressed_float_width) { case sizeof(Float64): From 6a0b305a143466b6e7e4fa90194fedebe10fc355 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 10 Jun 2022 12:08:56 +0000 Subject: [PATCH 062/204] fix reading of sparse columns from s3 --- .../MergeTree/MergeTreeReaderStream.cpp | 73 +++++-------------- 1 file changed, 19 insertions(+), 54 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeReaderStream.cpp b/src/Storages/MergeTree/MergeTreeReaderStream.cpp index c1f23eab872..ed66c4770f8 100644 --- a/src/Storages/MergeTree/MergeTreeReaderStream.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderStream.cpp @@ -134,63 +134,28 @@ size_t MergeTreeReaderStream::getRightOffset(size_t right_mark_non_included) size_t result_right_offset; if (0 < right_mark_non_included && right_mark_non_included < marks_count) { - auto right_mark = marks_loader.getMark(right_mark_non_included); - result_right_offset = right_mark.offset_in_compressed_file; + /// Find the right border of the last mark we need to read. + /// To do that let's find the upper bound of the offset of the last + /// included mark. + /// + /// LowCardinality dictionary related to current granule may be written + /// to the next granule in some corner cases. So, that's why we have to + /// read one extra granule to the right, while reading dictionary of LowCardinality. + size_t right_mark_included = is_low_cardinality_dictionary + ? right_mark_non_included + : right_mark_non_included - 1; - bool need_to_check_marks_from_the_right = false; - - /// If the end of range is inside the block, we will need to read it too. - if (right_mark.offset_in_decompressed_block > 0) - { - need_to_check_marks_from_the_right = true; - } - else if (is_low_cardinality_dictionary) - { - /// Also, in LowCardinality dictionary several consecutive marks can point to - /// the same offset. So to get true bytes offset we have to get first - /// non-equal mark. - /// Example: - /// Mark 186, points to [2003111, 0] - /// Mark 187, points to [2003111, 0] - /// Mark 188, points to [2003111, 0] <--- for example need to read until 188 - /// Mark 189, points to [2003111, 0] <--- not suitable, because have same offset - /// Mark 190, points to [2003111, 0] - /// Mark 191, points to [2003111, 0] - /// Mark 192, points to [2081424, 0] <--- what we are looking for - /// Mark 193, points to [2081424, 0] - /// Mark 194, points to [2081424, 0] - - /// Also, in some cases, when one granule is not-atomically written (which is possible at merges) - /// one granule may require reading of two dictionaries which starts from different marks. - /// The only correct way is to take offset from at least next different granule from the right one. - - /// Check test_s3_low_cardinality_right_border. - - need_to_check_marks_from_the_right = true; - } - - - /// Let's go to the right and find mark with bigger offset in compressed file - if (need_to_check_marks_from_the_right) - { - bool found_bigger_mark = false; - for (size_t i = right_mark_non_included + 1; i < marks_count; ++i) + auto indices = collections::range(right_mark_included, marks_count); + auto it = std::upper_bound(indices.begin(), indices.end(), right_mark_included, + [&](auto lhs, auto rhs) { - const auto & candidate_mark = marks_loader.getMark(i); - if (result_right_offset < candidate_mark.offset_in_compressed_file) - { - result_right_offset = candidate_mark.offset_in_compressed_file; - found_bigger_mark = true; - break; - } - } + return marks_loader.getMark(lhs).offset_in_compressed_file < marks_loader.getMark(rhs).offset_in_compressed_file; + }); - if (!found_bigger_mark) - { - /// If there are no marks after the end of range, just use file size - result_right_offset = file_size; - } - } + if (it != indices.end()) + result_right_offset = marks_loader.getMark(*it).offset_in_compressed_file; + else + result_right_offset = file_size; } else if (right_mark_non_included == 0) result_right_offset = marks_loader.getMark(right_mark_non_included).offset_in_compressed_file; From b27e6cfed296542ea74b9702ebafd9be05ba7718 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Fri, 10 Jun 2022 18:33:28 +0200 Subject: [PATCH 063/204] Strip less aggressively to make the embedded hash survive - It was noticed that in (*), the crashstack says "There is no information about the reference checksum." - The binaries are pulled via docker hub and upon inspection they indeed lack the hash embedded as ELF section ".note.ClickHouse.hash" in the clickhouse binary. This is weird because docker hub images are "official" builds which should trigger the hash embedding. - Turns out that the docker hub binaries are also stripped which was too aggressive. We now no longer remove sections ".comment" and ".note" which are anyways only 140 bytes in size, i.e. binary size still goes down (on my stystem) from 2.1 GB to 0.47 + 0.40 GB binary + dbg info. (*) https://playground.lodthe.me/ba75d494-95d1-4ff6-a0ad-60c138636c9b --- cmake/strip_binary.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/strip_binary.cmake b/cmake/strip_binary.cmake index be23a4c1c30..dfa2df824db 100644 --- a/cmake/strip_binary.cmake +++ b/cmake/strip_binary.cmake @@ -21,7 +21,7 @@ macro(clickhouse_strip_binary) COMMAND cp "${STRIP_BINARY_PATH}" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" COMMAND "${OBJCOPY_PATH}" --only-keep-debug --compress-debug-sections "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" COMMAND chmod 0644 "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" - COMMAND "${STRIP_PATH}" --remove-section=.comment --remove-section=.note "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" + COMMAND "${STRIP_PATH}" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" COMMAND "${OBJCOPY_PATH}" --add-gnu-debuglink "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" COMMENT "Stripping clickhouse binary" VERBATIM ) From f56b88e94923320bbb2d2a9f35aa46bb2ec4f307 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Fri, 10 Jun 2022 18:34:45 +0200 Subject: [PATCH 064/204] PartialSortingTransform refactoring --- .../Transforms/PartialSortingTransform.cpp | 92 ++++++++++--------- .../Transforms/PartialSortingTransform.h | 9 +- 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/Processors/Transforms/PartialSortingTransform.cpp b/src/Processors/Transforms/PartialSortingTransform.cpp index 3687fa770f0..ca8edae0eea 100644 --- a/src/Processors/Transforms/PartialSortingTransform.cpp +++ b/src/Processors/Transforms/PartialSortingTransform.cpp @@ -1,50 +1,26 @@ #include #include +#include #include namespace DB { -PartialSortingTransform::PartialSortingTransform( - const Block & header_, SortDescription & description_, UInt64 limit_) - : ISimpleTransform(header_, header_, false) - , description(description_), limit(limit_) +namespace { - // Sorting by no columns doesn't make sense. - assert(!description.empty()); -} -static ColumnRawPtrs extractColumns(const Block & block, const SortDescription & description) +ColumnRawPtrs extractRawColumns(const Block & block, const SortDescriptionWithPositions & description) { size_t size = description.size(); - ColumnRawPtrs res; - res.reserve(size); + ColumnRawPtrs result(size); for (size_t i = 0; i < size; ++i) - { - const IColumn * column = block.getByName(description[i].column_name).column.get(); - res.emplace_back(column); - } + result[i] = block.safeGetByPosition(description[i].column_number).column.get(); - return res; + return result; } -bool less(const ColumnRawPtrs & lhs, UInt64 lhs_row_num, - const ColumnRawPtrs & rhs, UInt64 rhs_row_num, const SortDescription & description) -{ - size_t size = description.size(); - for (size_t i = 0; i < size; ++i) - { - int res = description[i].direction * lhs[i]->compareAt(lhs_row_num, rhs_row_num, *rhs[i], description[i].nulls_direction); - if (res < 0) - return true; - else if (res > 0) - return false; - } - return false; -} - -size_t getFilterMask(const ColumnRawPtrs & lhs, const ColumnRawPtrs & rhs, size_t rhs_row_num, +size_t getFilterMask(const ColumnRawPtrs & raw_block_columns, const Columns & threshold_columns, const SortDescription & description, size_t num_rows, IColumn::Filter & filter, PaddedPODArray & rows_to_compare, PaddedPODArray & compare_results) { @@ -54,7 +30,7 @@ size_t getFilterMask(const ColumnRawPtrs & lhs, const ColumnRawPtrs & rhs, size_ if (description.size() == 1) { /// Fast path for single column - lhs[0]->compareColumn(*rhs[0], rhs_row_num, nullptr, compare_results, + raw_block_columns[0]->compareColumn(*threshold_columns[0], 0, nullptr, compare_results, description[0].direction, description[0].nulls_direction); } else @@ -67,7 +43,7 @@ size_t getFilterMask(const ColumnRawPtrs & lhs, const ColumnRawPtrs & rhs, size_ size_t size = description.size(); for (size_t i = 0; i < size; ++i) { - lhs[i]->compareColumn(*rhs[i], rhs_row_num, &rows_to_compare, compare_results, + raw_block_columns[i]->compareColumn(*threshold_columns[i], 0, &rows_to_compare, compare_results, description[i].direction, description[i].nulls_direction); if (rows_to_compare.empty()) @@ -87,6 +63,22 @@ size_t getFilterMask(const ColumnRawPtrs & lhs, const ColumnRawPtrs & rhs, size_ return result_size_hint; } +} + +PartialSortingTransform::PartialSortingTransform( + const Block & header_, SortDescription & description_, UInt64 limit_) + : ISimpleTransform(header_, header_, false) + , description(description_) + , limit(limit_) +{ + // Sorting by no columns doesn't make sense. + assert(!description_.empty()); + + size_t description_size = description.size(); + for (size_t i = 0; i < description_size; ++i) + description_with_positions.emplace_back(description[i], header_.getPositionByName(description[i].column_name)); +} + void PartialSortingTransform::transform(Chunk & chunk) { if (chunk.getNumRows()) @@ -105,13 +97,13 @@ void PartialSortingTransform::transform(Chunk & chunk) /** If we've saved columns from previously blocks we could filter all rows from current block * which are unnecessary for sortBlock(...) because they obviously won't be in the top LIMIT rows. */ - if (!threshold_block_columns.empty()) + if (!sort_description_threshold_columns.empty()) { UInt64 rows_num = block.rows(); - auto block_columns = extractColumns(block, description); + auto block_columns = extractRawColumns(block, description_with_positions); size_t result_size_hint = getFilterMask( - block_columns, threshold_block_columns, limit - 1, + block_columns, sort_description_threshold_columns, description, rows_num, filter, rows_to_compare, compare_results); /// Everything was filtered. Skip whole chunk. @@ -128,15 +120,31 @@ void PartialSortingTransform::transform(Chunk & chunk) sortBlock(block, description, limit); /// Check if we can use this block for optimization. - if (min_limit_for_partial_sort_optimization <= limit && limit <= block.rows()) + if (min_limit_for_partial_sort_optimization <= limit) { - auto block_columns = extractColumns(block, description); + size_t block_rows = block.rows(); - if (threshold_block_columns.empty() || - less(block_columns, limit - 1, threshold_block_columns, limit - 1, description)) + /** In case filtered more than limit rows from block take last row. + * Otherwise take last limit row. + * If min block value is less than current threshold value, update current threshold value. + */ + size_t min_row_to_compare = limit <= block_rows ? (limit - 1) : (block_rows - 1); + auto raw_block_columns = extractRawColumns(block, description_with_positions); + + if (sort_description_threshold_columns.empty() || + less(raw_block_columns, sort_description_threshold_columns, min_row_to_compare, 0, description_with_positions)) { - threshold_block = block; - threshold_block_columns.swap(block_columns); + size_t raw_block_columns_size = raw_block_columns.size(); + Columns sort_description_threshold_columns_updated(raw_block_columns_size); + + for (size_t i = 0; i < raw_block_columns_size; ++i) + { + MutableColumnPtr sort_description_threshold_column_updated = raw_block_columns[i]->cloneEmpty(); + sort_description_threshold_column_updated->insertFrom(*raw_block_columns[i], min_row_to_compare); + sort_description_threshold_columns_updated[i] = std::move(sort_description_threshold_column_updated); + } + + sort_description_threshold_columns = std::move(sort_description_threshold_columns_updated); } } diff --git a/src/Processors/Transforms/PartialSortingTransform.h b/src/Processors/Transforms/PartialSortingTransform.h index bd2465f11a1..78ce80bdeeb 100644 --- a/src/Processors/Transforms/PartialSortingTransform.h +++ b/src/Processors/Transforms/PartialSortingTransform.h @@ -8,7 +8,6 @@ namespace DB { /** Sorts each block individually by the values of the specified columns. - * At the moment, not very optimal algorithm is used. */ class PartialSortingTransform : public ISimpleTransform { @@ -28,15 +27,11 @@ protected: private: SortDescription description; + SortDescriptionWithPositions description_with_positions; UInt64 limit; RowsBeforeLimitCounterPtr read_rows; - /** threshold_block is using for saving columns from previously processed block. - * threshold_block_columns contains pointers to columns from threshold_block which used for comparison. - * That's all for PartialSort optimization - */ - Block threshold_block; - ColumnRawPtrs threshold_block_columns; + Columns sort_description_threshold_columns; /// This are just buffers which reserve memory to reduce the number of allocations. PaddedPODArray rows_to_compare; From bc6f30fd40d77f129a99f4dc903bd0ac976bfba5 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Mon, 13 Jun 2022 08:43:01 +0000 Subject: [PATCH 065/204] Move binary hash to ELF section ".ClickHouse.hash" --- cmake/strip_binary.cmake | 3 ++- programs/CMakeLists.txt | 2 +- programs/main.cpp | 2 +- src/Common/Elf.cpp | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmake/strip_binary.cmake b/cmake/strip_binary.cmake index dfa2df824db..c0fd7626478 100644 --- a/cmake/strip_binary.cmake +++ b/cmake/strip_binary.cmake @@ -21,7 +21,8 @@ macro(clickhouse_strip_binary) COMMAND cp "${STRIP_BINARY_PATH}" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" COMMAND "${OBJCOPY_PATH}" --only-keep-debug --compress-debug-sections "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" COMMAND chmod 0644 "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" - COMMAND "${STRIP_PATH}" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" + # Additionally remove sections '.note' & '.comment' to emulate stripping in Debian: www.debian.org/doc/debian-policy/ch-files.html + COMMAND "${STRIP_PATH}" --remove-section=.comment --remove-section=.note "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" COMMAND "${OBJCOPY_PATH}" --add-gnu-debuglink "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" COMMENT "Stripping clickhouse binary" VERBATIM ) diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index afde5bd9a47..af5afb1d462 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -508,7 +508,7 @@ else () endif() if (USE_BINARY_HASH) - add_custom_command(TARGET clickhouse POST_BUILD COMMAND ./clickhouse hash-binary > hash && ${OBJCOPY_PATH} --add-section .note.ClickHouse.hash=hash clickhouse COMMENT "Adding .note.ClickHouse.hash to clickhouse" VERBATIM) + add_custom_command(TARGET clickhouse POST_BUILD COMMAND ./clickhouse hash-binary > hash && ${OBJCOPY_PATH} --add-section .ClickHouse.hash=hash clickhouse COMMENT "Adding .ClickHouse.hash to clickhouse" VERBATIM) endif() if (INSTALL_STRIPPED_BINARIES) diff --git a/programs/main.cpp b/programs/main.cpp index 4148cbfc4cd..36020f0b921 100644 --- a/programs/main.cpp +++ b/programs/main.cpp @@ -82,7 +82,7 @@ int mainEntryClickHouseDisks(int argc, char ** argv); int mainEntryClickHouseHashBinary(int, char **) { /// Intentionally without newline. So you can run: - /// objcopy --add-section .note.ClickHouse.hash=<(./clickhouse hash-binary) clickhouse + /// objcopy --add-section .ClickHouse.hash=<(./clickhouse hash-binary) clickhouse std::cout << getHashOfLoadedBinaryHex(); return 0; } diff --git a/src/Common/Elf.cpp b/src/Common/Elf.cpp index 27ff3bad310..dcbc836d9cb 100644 --- a/src/Common/Elf.cpp +++ b/src/Common/Elf.cpp @@ -178,7 +178,7 @@ String Elf::getBuildID(const char * nhdr_pos, size_t size) String Elf::getBinaryHash() const { - if (auto section = findSectionByName(".note.ClickHouse.hash")) + if (auto section = findSectionByName(".ClickHouse.hash")) return {section->begin(), section->end()}; else return {}; From d8c4af725afc1f92ce3c456d53b7f35bf29596ee Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Thu, 2 Jun 2022 16:15:21 +0200 Subject: [PATCH 066/204] libunwind update version --- .gitmodules | 2 +- contrib/libunwind | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 55fd684fddb..0a525efed1f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -91,7 +91,7 @@ url = https://github.com/ClickHouse/hyperscan.git [submodule "contrib/libunwind"] path = contrib/libunwind - url = https://github.com/ClickHouse/libunwind.git + url = https://github.com/kitaisreal/libunwind.git [submodule "contrib/simdjson"] path = contrib/simdjson url = https://github.com/simdjson/simdjson.git diff --git a/contrib/libunwind b/contrib/libunwind index c4ea9848a69..970d10cd51a 160000 --- a/contrib/libunwind +++ b/contrib/libunwind @@ -1 +1 @@ -Subproject commit c4ea9848a697747dfa35325af9b3452f30841685 +Subproject commit 970d10cd51a78dc6eecc9e09dafa126985612ae8 From 25a886a78c563a18c9dea83fab6c6bbdf30c43ab Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Fri, 3 Jun 2022 18:15:58 +0200 Subject: [PATCH 067/204] libunwind update version fix sanitizers --- contrib/libunwind | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/libunwind b/contrib/libunwind index 970d10cd51a..71e1ea1f14f 160000 --- a/contrib/libunwind +++ b/contrib/libunwind @@ -1 +1 @@ -Subproject commit 970d10cd51a78dc6eecc9e09dafa126985612ae8 +Subproject commit 71e1ea1f14fef9e9aed554cfe1f16c380b7c7f16 From 8907c4b5b037af2844cf49fb104b6d7fe12eff4c Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Fri, 10 Jun 2022 13:21:21 +0200 Subject: [PATCH 068/204] Updated libunwind --- contrib/libunwind | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/libunwind b/contrib/libunwind index 71e1ea1f14f..a18235dc598 160000 --- a/contrib/libunwind +++ b/contrib/libunwind @@ -1 +1 @@ -Subproject commit 71e1ea1f14fef9e9aed554cfe1f16c380b7c7f16 +Subproject commit a18235dc5986dc63c622b210186e7689d29ef6de From fb54a1cb6a6c2405228b426952c473ac0ed8d3dd Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Sun, 12 Jun 2022 15:36:46 +0200 Subject: [PATCH 069/204] Updated libunwind --- contrib/libunwind | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/libunwind b/contrib/libunwind index a18235dc598..e3f5cd3942e 160000 --- a/contrib/libunwind +++ b/contrib/libunwind @@ -1 +1 @@ -Subproject commit a18235dc5986dc63c622b210186e7689d29ef6de +Subproject commit e3f5cd3942edbb357c450a17e9c5d1321a8ce958 From 411e1ad2278e78b4da948c06907e760488425186 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Mon, 13 Jun 2022 12:30:38 +0200 Subject: [PATCH 070/204] Fixed tests --- .../Transforms/PartialSortingTransform.cpp | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Processors/Transforms/PartialSortingTransform.cpp b/src/Processors/Transforms/PartialSortingTransform.cpp index ca8edae0eea..ccae31bfe9d 100644 --- a/src/Processors/Transforms/PartialSortingTransform.cpp +++ b/src/Processors/Transforms/PartialSortingTransform.cpp @@ -63,6 +63,25 @@ size_t getFilterMask(const ColumnRawPtrs & raw_block_columns, const Columns & th return result_size_hint; } +bool compareWithThreshold(const ColumnRawPtrs & raw_block_columns, size_t min_block_index, const Columns & threshold_columns, const SortDescription & sort_description) +{ + assert(raw_block_columns.size() == threshold_columns.size()); + assert(raw_block_columns.size() == sort_description.size()); + + size_t raw_block_columns_size = raw_block_columns.size(); + for (size_t i = 0; i < raw_block_columns_size; ++i) + { + int res = sort_description[i].direction * raw_block_columns[i]->compareAt(min_block_index, 0, *threshold_columns[0], sort_description[i].nulls_direction); + + if (res < 0) + return true; + else if (res > 0) + return false; + } + + return false; +} + } PartialSortingTransform::PartialSortingTransform( @@ -119,20 +138,22 @@ void PartialSortingTransform::transform(Chunk & chunk) sortBlock(block, description, limit); - /// Check if we can use this block for optimization. - if (min_limit_for_partial_sort_optimization <= limit) - { - size_t block_rows = block.rows(); + size_t block_rows = block.rows(); - /** In case filtered more than limit rows from block take last row. + /// Check if we can use this block for optimization. + if (min_limit_for_partial_sort_optimization <= limit && block_rows > 0) + { + /** If we filtered more than limit rows from block take block last row. * Otherwise take last limit row. + * + * If current threshold value is empty, update current threshold value. * If min block value is less than current threshold value, update current threshold value. */ size_t min_row_to_compare = limit <= block_rows ? (limit - 1) : (block_rows - 1); auto raw_block_columns = extractRawColumns(block, description_with_positions); if (sort_description_threshold_columns.empty() || - less(raw_block_columns, sort_description_threshold_columns, min_row_to_compare, 0, description_with_positions)) + compareWithThreshold(raw_block_columns, min_row_to_compare, sort_description_threshold_columns, description)) { size_t raw_block_columns_size = raw_block_columns.size(); Columns sort_description_threshold_columns_updated(raw_block_columns_size); From 7210be153485eba4b267224934308a18db1fa672 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 5 Mar 2022 16:54:15 +0300 Subject: [PATCH 071/204] Disable send_logs_level for INSERT into Distributed to avoid possible hung In case of INSERT into Distributed table with send_logs_level!=none it is possible to receive tons of Log packets, and w/o consuming it properly the socket buffer will be full, and eventually the query will hung. This happens because receiver will not read data until it will send logs packets, but sender does not reads those Log packets and so receiver hung, and hence the sender will hung too, because receiver do not consume Data packets anymore. In the initial version of this patch I tried to properly consume Log packets, but it is not possible to ensure that before writing Data blocks all Log packets had been consumed, that said that with current protocol implementation it is not possible to fix Log packets consuming properly, to avoid deadlock, so send_logs_level had been simply disabled. But note, that this does not differs to the user, in what ClickHouse did before, since before it simply does not consume those packets, so the client does not saw those messages anyway.
The receiver: Poco::Net::SocketImpl::poll(Poco::Timespan const&, int) Poco::Net::SocketImpl::sendBytes(void const*, int, int) Poco::Net::StreamSocketImpl::sendBytes(void const*, int, int) DB::WriteBufferFromPocoSocket::nextImpl() DB::TCPHandler::sendLogData(DB::Block const&) DB::TCPHandler::sendLogs() DB::TCPHandler::readDataNext() DB::TCPHandler::processInsertQuery() State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 4331792 211637 127.0.0.1:9000 127.0.0.1:24446 users:(("clickhouse-serv",pid=46874,fd=3850)) The sender: Poco::Net::SocketImpl::poll(Poco::Timespan const&, int) Poco::Net::SocketImpl::sendBytes(void const*, int, int) Poco::Net::StreamSocketImpl::sendBytes(void const*, int, int) DB::WriteBufferFromPocoSocket::nextImpl() DB::WriteBuffer::write(char const*, unsigned long) DB::CompressedWriteBuffer::nextImpl() DB::WriteBuffer::write(char const*, unsigned long) DB::SerializationString::serializeBinaryBulk(DB::IColumn const&, DB::WriteBuffer&, unsigned long, unsigned long) const DB::NativeWriter::write(DB::Block const&) DB::Connection::sendData(DB::Block const&, std::__1::basic_string, std::__1::allocator > const&, bool) DB::RemoteInserter::write(DB::Block) DB::RemoteSink::consume(DB::Chunk) DB::SinkToStorage::onConsume(DB::Chunk) State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 67883 3008240 127.0.0.1:24446 127.0.0.1:9000 users:(("clickhouse-serv",pid=41610,fd=25))
v2: rebase to use clickhouse_client_timeout and add clickhouse_test_wait_queries v3: use KILL QUERY v4: adjust the test v5: disable send_logs_level for INSERT into Distributed v6: add no-backward-compatibility-check tag Signed-off-by: Azat Khuzhin --- src/QueryPipeline/RemoteInserter.cpp | 18 +++++- ...dist_insert_send_logs_level_hung.reference | 1 + .../02232_dist_insert_send_logs_level_hung.sh | 56 +++++++++++++++++++ ...2332_dist_insert_send_logs_level.reference | 0 .../02332_dist_insert_send_logs_level.sh | 16 ++++++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.reference create mode 100755 tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh create mode 100644 tests/queries/0_stateless/02332_dist_insert_send_logs_level.reference create mode 100755 tests/queries/0_stateless/02332_dist_insert_send_logs_level.sh diff --git a/src/QueryPipeline/RemoteInserter.cpp b/src/QueryPipeline/RemoteInserter.cpp index d5cef72b020..ce2ba23576d 100644 --- a/src/QueryPipeline/RemoteInserter.cpp +++ b/src/QueryPipeline/RemoteInserter.cpp @@ -47,10 +47,26 @@ RemoteInserter::RemoteInserter( } } + Settings settings = settings_; + /// With current protocol it is impossible to avoid deadlock in case of send_logs_level!=none. + /// + /// RemoteInserter send Data blocks/packets to the remote shard, + /// while remote side can send Log packets to the initiator (this RemoteInserter instance). + /// + /// But it is not enough to pull Log packets just before writing the next block + /// since there is no way to ensure that all Log packets had been consumed. + /// + /// And if enough Log packets will be queued by the remote side, + /// it will wait send_timeout until initiator will consume those packets, + /// while initiator already starts writing Data blocks, + /// and will not consume Log packets. + /// + /// So that is why send_logs_level had been disabled here. + settings.send_logs_level = "none"; /** Send query and receive "header", that describes table structure. * Header is needed to know, what structure is required for blocks to be passed to 'write' method. */ - connection.sendQuery(timeouts, query, "", QueryProcessingStage::Complete, &settings_, &modified_client_info, false, {}); + connection.sendQuery(timeouts, query, "", QueryProcessingStage::Complete, &settings, &modified_client_info, false, {}); while (true) { diff --git a/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.reference b/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.reference new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.reference @@ -0,0 +1 @@ +0 diff --git a/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh b/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh new file mode 100755 index 00000000000..90d053afaae --- /dev/null +++ b/tests/queries/0_stateless/02232_dist_insert_send_logs_level_hung.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Tags: long, no-parallel, no-backward-compatibility-check +# Tag: no-parallel - to heavy +# Tag: long - to heavy + +# This is the regression test when remote peer send some logs for INSERT, +# it is easy to archive using materialized views, with small block size. + +CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL=trace + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +# NOTE: that since we use CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL we need to apply +# --server_logs_file for every clickhouse-client invocation. +client_opts=( + # For --send_logs_level see $CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL + --server_logs_file /dev/null + # we need lots of blocks to get log entry for each of them + --min_insert_block_size_rows 1 + # we need to terminate ASAP + --max_block_size 1 +) + +$CLICKHOUSE_CLIENT "${client_opts[@]}" -nm -q " + drop table if exists mv_02232; + drop table if exists in_02232; + drop table if exists out_02232; + + create table out_02232 (key Int) engine=Null(); + create table in_02232 (key Int) engine=Null(); + create materialized view mv_02232 to out_02232 as select * from in_02232; +" + +insert_client_opts=( + # Increase timeouts to avoid timeout during trying to send Log packet to + # the remote side, when the socket is full. + --send_timeout 86400 + --receive_timeout 86400 +) +# 250 seconds is enough to trigger the query hung (even in debug build) +# +# NOTE: using proper termination (via SIGINT) is too long, +# hence timeout+KILL QUERY. +timeout 250s $CLICKHOUSE_CLIENT "${client_opts[@]}" "${insert_client_opts[@]}" -q "insert into function remote('127.2', currentDatabase(), in_02232) select * from numbers(1e6)" + +# Kill underlying query of remote() to make KILL faster +timeout 30s $CLICKHOUSE_CLIENT "${client_opts[@]}" -q "KILL QUERY WHERE Settings['log_comment'] = '$CLICKHOUSE_LOG_COMMENT' SYNC" --format Null +echo $? + +$CLICKHOUSE_CLIENT "${client_opts[@]}" -nm -q " + drop table in_02232; + drop table mv_02232; + drop table out_02232; +" diff --git a/tests/queries/0_stateless/02332_dist_insert_send_logs_level.reference b/tests/queries/0_stateless/02332_dist_insert_send_logs_level.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02332_dist_insert_send_logs_level.sh b/tests/queries/0_stateless/02332_dist_insert_send_logs_level.sh new file mode 100755 index 00000000000..653cb25172a --- /dev/null +++ b/tests/queries/0_stateless/02332_dist_insert_send_logs_level.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Tags: no-backward-compatibility-check + +CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL=trace +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT --server_logs_file /dev/null -q "CREATE TABLE data_02332 (key Int) Engine=Null()" +# If ClickHouse server will forward logs from the remote nodes, than it will definitely will have the following message in the log: +# +# executeQuery: (from 127.0.0.1:53440, initial_query_id: fc1f7dbd-845b-4142-9306-158ddd564e61) INSERT INTO default.data (key) VALUES (stage: Complete) +# +# And if the server will forward logs, then the query may hung. +$CLICKHOUSE_CLIENT -q "INSERT INTO FUNCTION remote('127.2', currentDatabase(), data_02332) SELECT * FROM numbers(10)" |& grep 'executeQuery.*initial_query_id.*INSERT INTO' +exit 0 From e25fbb92928538a24808ffc949ed0b3f493b4e35 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Mon, 13 Jun 2022 12:17:54 +0000 Subject: [PATCH 072/204] Log official/non-official build and integrated/separate debug symbols The goal is to find out why some of the binaries with official name (BuilderDebRelease, BuilderBinRelease) produced by CI still contain no hash section. (also, strip and objcopy are mandatory tools and we don't need to check for their existence at this point) --- CMakeLists.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6a09afc489..042acafcda5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,16 +244,18 @@ endif () # Add a section with the hash of the compiled machine code for integrity checks. # Only for official builds, because adding a section can be time consuming (rewrite of several GB). # And cross compiled binaries are not supported (since you cannot execute clickhouse hash-binary) -if (OBJCOPY_PATH AND CLICKHOUSE_OFFICIAL_BUILD AND (NOT CMAKE_TOOLCHAIN_FILE OR CMAKE_TOOLCHAIN_FILE MATCHES "linux/toolchain-x86_64.cmake$")) +if (CLICKHOUSE_OFFICIAL_BUILD AND (NOT CMAKE_TOOLCHAIN_FILE OR CMAKE_TOOLCHAIN_FILE MATCHES "linux/toolchain-x86_64.cmake$")) + message(STATUS "Official build: Will add a checksum hash to the clickhouse binary") set (USE_BINARY_HASH 1 CACHE STRING "Calculate binary hash and store it in the separate section") +else () + message(STATUS "Not an official build: Will not add a checksum hash to the clickhouse binary") endif () -# Allows to build stripped binary in a separate directory -if (OBJCOPY_PATH AND STRIP_PATH) - option(INSTALL_STRIPPED_BINARIES "Build stripped binaries with debug info in separate directory" OFF) - if (INSTALL_STRIPPED_BINARIES) - set(STRIPPED_BINARIES_OUTPUT "stripped" CACHE STRING "A separate directory for stripped information") - endif() +# Optionally split binaries and debug symbols. +option(INSTALL_STRIPPED_BINARIES "Split binaries and debug symbols" OFF) +if (INSTALL_STRIPPED_BINARIES) + message(STATUS "Will split binaries and debug symbols") + set(STRIPPED_BINARIES_OUTPUT "stripped" CACHE STRING "A separate directory for stripped information") endif() cmake_host_system_information(RESULT AVAILABLE_PHYSICAL_MEMORY QUERY AVAILABLE_PHYSICAL_MEMORY) # Not available under freebsd From de273b043dfba3c246c3033dfc850541ef5dd6ce Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Mon, 13 Jun 2022 13:42:54 +0000 Subject: [PATCH 073/204] Decimal: noexcept move constructor/assignment operator --- base/base/Decimal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/base/Decimal.h b/base/base/Decimal.h index aefe852b749..1efb8ba8d92 100644 --- a/base/base/Decimal.h +++ b/base/base/Decimal.h @@ -49,7 +49,7 @@ struct Decimal using NativeType = T; constexpr Decimal() = default; - constexpr Decimal(Decimal &&) = default; + constexpr Decimal(Decimal &&) noexcept = default; constexpr Decimal(const Decimal &) = default; constexpr Decimal(const T & value_): value(value_) {} @@ -57,7 +57,7 @@ struct Decimal template constexpr Decimal(const Decimal & x): value(x.value) {} - constexpr Decimal & operator = (Decimal &&) = default; + constexpr Decimal & operator=(Decimal &&) noexcept = default; constexpr Decimal & operator = (const Decimal &) = default; constexpr operator T () const { return value; } From eb861499c78df4acadf0ba76774bfc3220b5d082 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Mon, 13 Jun 2022 16:43:11 +0200 Subject: [PATCH 074/204] Fixed tests --- src/Processors/Transforms/PartialSortingTransform.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Processors/Transforms/PartialSortingTransform.cpp b/src/Processors/Transforms/PartialSortingTransform.cpp index ccae31bfe9d..fc2dfe8fdaa 100644 --- a/src/Processors/Transforms/PartialSortingTransform.cpp +++ b/src/Processors/Transforms/PartialSortingTransform.cpp @@ -112,6 +112,7 @@ void PartialSortingTransform::transform(Chunk & chunk) read_rows->add(chunk.getNumRows()); auto block = getInputPort().getHeader().cloneWithColumns(chunk.detachColumns()); + size_t block_rows_before_filter = block.rows(); /** If we've saved columns from previously blocks we could filter all rows from current block * which are unnecessary for sortBlock(...) because they obviously won't be in the top LIMIT rows. @@ -125,6 +126,8 @@ void PartialSortingTransform::transform(Chunk & chunk) block_columns, sort_description_threshold_columns, description, rows_num, filter, rows_to_compare, compare_results); + std::cout << "Filter result size " << result_size_hint << std::endl; + /// Everything was filtered. Skip whole chunk. if (result_size_hint == 0) return; @@ -138,10 +141,10 @@ void PartialSortingTransform::transform(Chunk & chunk) sortBlock(block, description, limit); - size_t block_rows = block.rows(); + size_t block_rows_after_filter = block.rows(); /// Check if we can use this block for optimization. - if (min_limit_for_partial_sort_optimization <= limit && block_rows > 0) + if (min_limit_for_partial_sort_optimization <= limit && block_rows_after_filter > 0 && limit <= block_rows_before_filter) { /** If we filtered more than limit rows from block take block last row. * Otherwise take last limit row. @@ -149,7 +152,7 @@ void PartialSortingTransform::transform(Chunk & chunk) * If current threshold value is empty, update current threshold value. * If min block value is less than current threshold value, update current threshold value. */ - size_t min_row_to_compare = limit <= block_rows ? (limit - 1) : (block_rows - 1); + size_t min_row_to_compare = limit <= block_rows_after_filter ? (limit - 1) : (block_rows_after_filter - 1); auto raw_block_columns = extractRawColumns(block, description_with_positions); if (sort_description_threshold_columns.empty() || From 695d5866040afd24659fb778760e714b69046309 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 13 Jun 2022 15:21:43 +0000 Subject: [PATCH 075/204] Fix possible crash in Distributed async insert in case of removing a replica from config. --- src/Common/PoolWithFailoverBase.h | 3 +++ .../configs/another_remote_servers.xml | 25 +++++++++++++++++++ .../test_distributed_format/test.py | 19 +++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/integration/test_distributed_format/configs/another_remote_servers.xml diff --git a/src/Common/PoolWithFailoverBase.h b/src/Common/PoolWithFailoverBase.h index 42b5b3d0990..f8789e3c827 100644 --- a/src/Common/PoolWithFailoverBase.h +++ b/src/Common/PoolWithFailoverBase.h @@ -64,6 +64,9 @@ public: , shared_pool_states(nested_pools.size()) , log(log_) { + if (nested_pools.empty()) + throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "Cannot create PoolWithFailover cause nested pools are empty"); + for (size_t i = 0;i < nested_pools.size(); ++i) shared_pool_states[i].config_priority = nested_pools[i]->getPriority(); } diff --git a/tests/integration/test_distributed_format/configs/another_remote_servers.xml b/tests/integration/test_distributed_format/configs/another_remote_servers.xml new file mode 100644 index 00000000000..2655f1d864e --- /dev/null +++ b/tests/integration/test_distributed_format/configs/another_remote_servers.xml @@ -0,0 +1,25 @@ + + + + + + not_existing + 9000 + + + not_existing2 + 9000 + + + + + + + + 127.0.0.1 + 9000 + + + + + diff --git a/tests/integration/test_distributed_format/test.py b/tests/integration/test_distributed_format/test.py index 415141be021..0359e6b7d6f 100644 --- a/tests/integration/test_distributed_format/test.py +++ b/tests/integration/test_distributed_format/test.py @@ -6,7 +6,7 @@ import pytest from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__) -node = cluster.add_instance("node", main_configs=["configs/remote_servers.xml"]) +node = cluster.add_instance("node", main_configs=["configs/remote_servers.xml", 'configs/another_remote_servers.xml'], stay_alive=True) cluster_param = pytest.mark.parametrize( "cluster", @@ -143,3 +143,20 @@ def test_single_file_old(started_cluster, cluster): assert out == "1\ta\n2\tbb\n3\tccc\n" node.query("drop table test.distr_3") + +def test_remove_replica(started_cluster): + node.query( + "create table test.local_4 (x UInt64, s String) engine = MergeTree order by x" + ) + node.query( + "create table test.distr_4 (x UInt64, s String) engine = Distributed('test_cluster_remove_replica1', test, local_4)" + ) + node.query("insert into test.distr_4 values (1, 'a'), (2, 'bb'), (3, 'ccc'), (4, 'dddd')") + node.query("detach table test.distr_4") + + node.exec_in_container(["sed", "-i", "s/test_cluster_remove_replica1/test_cluster_remove_replica_tmp/g", "/etc/clickhouse-server/config.d/another_remote_servers.xml"]) + node.exec_in_container(["sed", "-i", "s/test_cluster_remove_replica2/test_cluster_remove_replica1/g", "/etc/clickhouse-server/config.d/another_remote_servers.xml"]) + node.query("SYSTEM RELOAD CONFIG") + node.query("attach table test.distr_4", ignore_error=True) + node.query("SYSTEM FLUSH DISTRIBUTED test.distr_4", ignore_error=True) + assert node.query("select 1") == '1\n' From 20227e071864b9c7d69c0752be378ef165a85868 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Mon, 13 Jun 2022 18:49:20 +0200 Subject: [PATCH 076/204] Fixed style check --- src/Processors/Transforms/PartialSortingTransform.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Processors/Transforms/PartialSortingTransform.cpp b/src/Processors/Transforms/PartialSortingTransform.cpp index fc2dfe8fdaa..131bf4f8e7c 100644 --- a/src/Processors/Transforms/PartialSortingTransform.cpp +++ b/src/Processors/Transforms/PartialSortingTransform.cpp @@ -126,8 +126,6 @@ void PartialSortingTransform::transform(Chunk & chunk) block_columns, sort_description_threshold_columns, description, rows_num, filter, rows_to_compare, compare_results); - std::cout << "Filter result size " << result_size_hint << std::endl; - /// Everything was filtered. Skip whole chunk. if (result_size_hint == 0) return; From e6517164f2c1a6be379b6d20572f639cf8debbc0 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 13 Jun 2022 14:18:14 +0000 Subject: [PATCH 077/204] add test and comment --- .../MergeTree/MergeTreeReaderStream.cpp | 23 ++++++++-- .../02336_sparse_columns_s3.reference | 2 + .../0_stateless/02336_sparse_columns_s3.sql | 42 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 tests/queries/0_stateless/02336_sparse_columns_s3.reference create mode 100644 tests/queries/0_stateless/02336_sparse_columns_s3.sql diff --git a/src/Storages/MergeTree/MergeTreeReaderStream.cpp b/src/Storages/MergeTree/MergeTreeReaderStream.cpp index ed66c4770f8..aad3787bb36 100644 --- a/src/Storages/MergeTree/MergeTreeReaderStream.cpp +++ b/src/Storages/MergeTree/MergeTreeReaderStream.cpp @@ -137,10 +137,27 @@ size_t MergeTreeReaderStream::getRightOffset(size_t right_mark_non_included) /// Find the right border of the last mark we need to read. /// To do that let's find the upper bound of the offset of the last /// included mark. + + /// In LowCardinality dictionary and in values of Sparse columns + /// several consecutive marks can point to the same offset. /// - /// LowCardinality dictionary related to current granule may be written - /// to the next granule in some corner cases. So, that's why we have to - /// read one extra granule to the right, while reading dictionary of LowCardinality. + /// Example: + /// Mark 186, points to [2003111, 0] + /// Mark 187, points to [2003111, 0] + /// Mark 188, points to [2003111, 0] <--- for example need to read until 188 + /// Mark 189, points to [2003111, 0] <--- not suitable, because have same offset + /// Mark 190, points to [2003111, 0] + /// Mark 191, points to [2003111, 0] + /// Mark 192, points to [2081424, 0] <--- what we are looking for + /// Mark 193, points to [2081424, 0] + /// Mark 194, points to [2081424, 0] + + /// Also, in some cases, when one granule is not-atomically written (which is possible at merges) + /// one granule may require reading of two dictionaries which starts from different marks. + /// The only correct way is to take offset from at least next different granule from the right one. + /// So, that's why we have to read one extra granule to the right, + /// while reading dictionary of LowCardinality. + size_t right_mark_included = is_low_cardinality_dictionary ? right_mark_non_included : right_mark_non_included - 1; diff --git a/tests/queries/0_stateless/02336_sparse_columns_s3.reference b/tests/queries/0_stateless/02336_sparse_columns_s3.reference new file mode 100644 index 00000000000..d814c4a2430 --- /dev/null +++ b/tests/queries/0_stateless/02336_sparse_columns_s3.reference @@ -0,0 +1,2 @@ +Sparse +50787 diff --git a/tests/queries/0_stateless/02336_sparse_columns_s3.sql b/tests/queries/0_stateless/02336_sparse_columns_s3.sql new file mode 100644 index 00000000000..23512359728 --- /dev/null +++ b/tests/queries/0_stateless/02336_sparse_columns_s3.sql @@ -0,0 +1,42 @@ +-- Tags: no-parallel, no-fasttest, no-s3-storage + +DROP TABLE IF EXISTS t_sparse_s3; + +CREATE TABLE t_sparse_s3 (id UInt32, cond UInt8, s String) +engine = MergeTree ORDER BY id +settings ratio_of_defaults_for_sparse_serialization = 0.01, storage_policy = 's3_cache', +min_bytes_for_wide_part = 0, min_compress_block_size = 1; + +INSERT INTO t_sparse_s3 SELECT 1, number % 2, '' FROM numbers(8192); +INSERT INTO t_sparse_s3 SELECT 2, number % 2, '' FROM numbers(24576); +INSERT INTO t_sparse_s3 SELECT 3, number % 2, '' FROM numbers(8192); +INSERT INTO t_sparse_s3 SELECT 4, number % 2, '' FROM numbers(24576); +INSERT INTO t_sparse_s3 SELECT 5, number % 2, '' FROM numbers(8192); +INSERT INTO t_sparse_s3 SELECT 6, number % 2, '' FROM numbers(24576); +INSERT INTO t_sparse_s3 SELECT 7, number % 2, '' FROM numbers(8192); +INSERT INTO t_sparse_s3 SELECT 8, number % 2, '' FROM numbers(24576); +INSERT INTO t_sparse_s3 SELECT 9, number % 2, '' FROM numbers(8192); +INSERT INTO t_sparse_s3 SELECT 10, number % 2, '' FROM numbers(24576); +INSERT INTO t_sparse_s3 SELECT 11, number % 2, '' FROM numbers(8000); +INSERT INTO t_sparse_s3 SELECT 12, number % 2, 'foo' FROM numbers(192); +INSERT INTO t_sparse_s3 SELECT 13, number % 2, '' FROM numbers(24576); +INSERT INTO t_sparse_s3 SELECT 14, number % 2, 'foo' FROM numbers(8192); +INSERT INTO t_sparse_s3 SELECT 15, number % 2, '' FROM numbers(24576); +INSERT INTO t_sparse_s3 SELECT 16, number % 2, 'foo' FROM numbers(4730); +INSERT INTO t_sparse_s3 SELECT 17, number % 2, 'foo' FROM numbers(3462); +INSERT INTO t_sparse_s3 SELECT 18, number % 2, '' FROM numbers(24576); + +OPTIMIZE TABLE t_sparse_s3 FINAL; + +SELECT serialization_kind FROM system.parts_columns +WHERE table = 't_sparse_s3' AND active AND column = 's' +AND database = currentDatabase(); + +SET max_threads = 1; + +SELECT count() FROM t_sparse_s3 +PREWHERE cond +WHERE id IN (1, 3, 5, 7, 9, 11, 13, 15, 17) +AND NOT ignore(s); + +DROP TABLE t_sparse_s3; From 43dff9d34c2b7dc061a2ce0cab6e6d1f2a43750a Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 13 Jun 2022 19:44:12 +0000 Subject: [PATCH 078/204] Black --- .../test_distributed_format/test.py | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_distributed_format/test.py b/tests/integration/test_distributed_format/test.py index 0359e6b7d6f..5611f465e8b 100644 --- a/tests/integration/test_distributed_format/test.py +++ b/tests/integration/test_distributed_format/test.py @@ -6,7 +6,11 @@ import pytest from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__) -node = cluster.add_instance("node", main_configs=["configs/remote_servers.xml", 'configs/another_remote_servers.xml'], stay_alive=True) +node = cluster.add_instance( + "node", + main_configs=["configs/remote_servers.xml", "configs/another_remote_servers.xml"], + stay_alive=True, +) cluster_param = pytest.mark.parametrize( "cluster", @@ -144,6 +148,7 @@ def test_single_file_old(started_cluster, cluster): node.query("drop table test.distr_3") + def test_remove_replica(started_cluster): node.query( "create table test.local_4 (x UInt64, s String) engine = MergeTree order by x" @@ -151,12 +156,28 @@ def test_remove_replica(started_cluster): node.query( "create table test.distr_4 (x UInt64, s String) engine = Distributed('test_cluster_remove_replica1', test, local_4)" ) - node.query("insert into test.distr_4 values (1, 'a'), (2, 'bb'), (3, 'ccc'), (4, 'dddd')") + node.query( + "insert into test.distr_4 values (1, 'a'), (2, 'bb'), (3, 'ccc'), (4, 'dddd')" + ) node.query("detach table test.distr_4") - node.exec_in_container(["sed", "-i", "s/test_cluster_remove_replica1/test_cluster_remove_replica_tmp/g", "/etc/clickhouse-server/config.d/another_remote_servers.xml"]) - node.exec_in_container(["sed", "-i", "s/test_cluster_remove_replica2/test_cluster_remove_replica1/g", "/etc/clickhouse-server/config.d/another_remote_servers.xml"]) + node.exec_in_container( + [ + "sed", + "-i", + "s/test_cluster_remove_replica1/test_cluster_remove_replica_tmp/g", + "/etc/clickhouse-server/config.d/another_remote_servers.xml", + ] + ) + node.exec_in_container( + [ + "sed", + "-i", + "s/test_cluster_remove_replica2/test_cluster_remove_replica1/g", + "/etc/clickhouse-server/config.d/another_remote_servers.xml", + ] + ) node.query("SYSTEM RELOAD CONFIG") node.query("attach table test.distr_4", ignore_error=True) node.query("SYSTEM FLUSH DISTRIBUTED test.distr_4", ignore_error=True) - assert node.query("select 1") == '1\n' + assert node.query("select 1") == "1\n" From b3928dcb5522cbf06e7b4d6f9ca15f2dffba512a Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Mon, 13 Jun 2022 21:17:05 -0400 Subject: [PATCH 079/204] add docs --- docs/en/operations/settings/merge-tree-settings.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/en/operations/settings/merge-tree-settings.md b/docs/en/operations/settings/merge-tree-settings.md index cf6b3459fe8..73d06fc82ca 100644 --- a/docs/en/operations/settings/merge-tree-settings.md +++ b/docs/en/operations/settings/merge-tree-settings.md @@ -2,6 +2,18 @@ The values of `merge_tree` settings (for all MergeTree tables) can be viewed in the table `system.merge_tree_settings`, they can be overridden in `config.xml` in the `merge_tree` section, or set in the `SETTINGS` section of each table. +These are example overrides for `max_suspicious_broken_parts`: + +## max_suspicious_broken_parts + +If the number of broken parts in a single partition exceeds the `max_suspicious_broken_parts` value, automatic deletion is denied. + +Possible values: + +- Any positive integer. + +Default value: 10. + Override example in `config.xml`: ``` text From 7e99e9fe43c5c1c5e87aa287b3a818c5a9f9b94a Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 14 Jun 2022 07:17:11 +0000 Subject: [PATCH 080/204] Fix child num assert --- src/Coordination/KeeperStorage.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 007068fcdcf..3e86e860de8 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -558,7 +558,15 @@ bool KeeperStorage::createNode( auto [map_key, _] = container.insert(path, created_node); /// Take child path from key owned by map. auto child_path = getBaseName(map_key->getKey()); - container.updateValue(parent_path, [child_path](KeeperStorage::Node & parent) { parent.addChild(child_path); }); + container.updateValue( + parent_path, + [child_path](KeeperStorage::Node & parent) + { + ++parent.stat.numChildren; + parent.addChild(child_path); + chassert(parent.stat.numChildren == static_cast(parent.getChildren().size())); + } + ); addDigest(map_key->getMapped()->value, map_key->getKey().toView()); return true; @@ -581,7 +589,13 @@ bool KeeperStorage::removeNode(const std::string & path, int32_t version) container.updateValue( parentPath(path), - [child_basename = getBaseName(node_it->key)](KeeperStorage::Node & parent) { parent.removeChild(child_basename); }); + [child_basename = getBaseName(node_it->key)](KeeperStorage::Node & parent) + { + --parent.stat.numChildren; + parent.removeChild(child_basename); + chassert(parent.stat.numChildren == static_cast(parent.getChildren().size())); + } + ); container.erase(path); @@ -784,8 +798,6 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr if (zxid > node.stat.pzxid) node.stat.pzxid = zxid; - ++node.stat.numChildren; - chassert(node.stat.numChildren == static_cast(node.getChildren().size())); }}); digest = storage.calculateNodesDigest(digest, new_deltas); @@ -941,9 +953,7 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr zxid, KeeperStorage::UpdateNodeDelta{[](KeeperStorage::Node & parent) { - --parent.stat.numChildren; ++parent.stat.cversion; - chassert(parent.stat.numChildren == static_cast(parent.getChildren().size())); }}); if (node->stat.ephemeralOwner != 0) @@ -1810,9 +1820,7 @@ void KeeperStorage::preprocessRequest( { [ephemeral_path](Node & parent) { - --parent.stat.numChildren; ++parent.stat.cversion; - chassert(parent.stat.numChildren == static_cast(parent.getChildren().size())); } } ); From f9fa9c094f3d5afcbe815259e6f5f5f0a774ce5c Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 14 Jun 2022 07:32:13 +0000 Subject: [PATCH 081/204] Change error code --- src/Common/PoolWithFailoverBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/PoolWithFailoverBase.h b/src/Common/PoolWithFailoverBase.h index f8789e3c827..3ce7be4b3c5 100644 --- a/src/Common/PoolWithFailoverBase.h +++ b/src/Common/PoolWithFailoverBase.h @@ -65,7 +65,7 @@ public: , log(log_) { if (nested_pools.empty()) - throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "Cannot create PoolWithFailover cause nested pools are empty"); + throw DB::Exception(DB::ErrorCodes::ALL_CONNECTION_TRIES_FAILED, "Cannot create PoolWithFailover cause nested pools are empty"); for (size_t i = 0;i < nested_pools.size(); ++i) shared_pool_states[i].config_priority = nested_pools[i]->getPriority(); From bf27fe284cc95d95e80c751229b56b9223299415 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 14 Jun 2022 07:37:02 +0000 Subject: [PATCH 082/204] Use std::list for deltas --- src/Coordination/KeeperStorage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 6adfd1c7020..3b9465edba6 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -250,7 +250,7 @@ public: }; mutable std::unordered_map nodes; - std::deque deltas; + std::list deltas; KeeperStorage & storage; }; From bc46cef63cedb0ff4bc08cfcdc90cb9873d1dd61 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 14 Jun 2022 08:50:53 +0000 Subject: [PATCH 083/204] Minor follow-up - change ELF section name to ".clickhouse.hash" (lowercase seems standard) - more expressive/concise integrity check messages at startup --- CMakeLists.txt | 4 ++-- programs/CMakeLists.txt | 2 +- programs/main.cpp | 2 +- programs/server/Server.cpp | 20 ++++++++++---------- src/Common/Elf.cpp | 4 ++-- src/Common/Elf.h | 2 +- src/Daemon/BaseDaemon.cpp | 22 +++++++++++----------- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 042acafcda5..c8bb1a2d1ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -245,10 +245,10 @@ endif () # Only for official builds, because adding a section can be time consuming (rewrite of several GB). # And cross compiled binaries are not supported (since you cannot execute clickhouse hash-binary) if (CLICKHOUSE_OFFICIAL_BUILD AND (NOT CMAKE_TOOLCHAIN_FILE OR CMAKE_TOOLCHAIN_FILE MATCHES "linux/toolchain-x86_64.cmake$")) - message(STATUS "Official build: Will add a checksum hash to the clickhouse binary") + message(STATUS "Official build: A checksum hash will be added to the clickhouse executable") set (USE_BINARY_HASH 1 CACHE STRING "Calculate binary hash and store it in the separate section") else () - message(STATUS "Not an official build: Will not add a checksum hash to the clickhouse binary") + message(STATUS "No official build: A checksum hash will not be added to the clickhouse executable") endif () # Optionally split binaries and debug symbols. diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index af5afb1d462..a2c6eb1a27e 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -508,7 +508,7 @@ else () endif() if (USE_BINARY_HASH) - add_custom_command(TARGET clickhouse POST_BUILD COMMAND ./clickhouse hash-binary > hash && ${OBJCOPY_PATH} --add-section .ClickHouse.hash=hash clickhouse COMMENT "Adding .ClickHouse.hash to clickhouse" VERBATIM) + add_custom_command(TARGET clickhouse POST_BUILD COMMAND ./clickhouse hash-binary > hash && ${OBJCOPY_PATH} --add-section .clickhouse.hash=hash clickhouse COMMENT "Adding section '.clickhouse.hash' to clickhouse binary" VERBATIM) endif() if (INSTALL_STRIPPED_BINARIES) diff --git a/programs/main.cpp b/programs/main.cpp index 36020f0b921..175504a85fa 100644 --- a/programs/main.cpp +++ b/programs/main.cpp @@ -82,7 +82,7 @@ int mainEntryClickHouseDisks(int argc, char ** argv); int mainEntryClickHouseHashBinary(int, char **) { /// Intentionally without newline. So you can run: - /// objcopy --add-section .ClickHouse.hash=<(./clickhouse hash-binary) clickhouse + /// objcopy --add-section .clickhouse.hash=<(./clickhouse hash-binary) clickhouse std::cout << getHashOfLoadedBinaryHex(); return 0; } diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index bc5a959c88b..cd2ecd56bf5 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -748,12 +748,12 @@ int Server::main(const std::vector & /*args*/) if (stored_binary_hash.empty()) { - LOG_WARNING(log, "Calculated checksum of the binary: {}." - " There is no information about the reference checksum.", calculated_binary_hash); + LOG_WARNING(log, "Integrity check of the executable skipped because the reference checksum could not be read." + " (calculated checksum: {})", calculated_binary_hash); } else if (calculated_binary_hash == stored_binary_hash) { - LOG_INFO(log, "Calculated checksum of the binary: {}, integrity check passed.", calculated_binary_hash); + LOG_INFO(log, "Integrity check of the executable successfully passed (checksum: {})", calculated_binary_hash); } else { @@ -769,14 +769,14 @@ int Server::main(const std::vector & /*args*/) else { throw Exception(ErrorCodes::CORRUPTED_DATA, - "Calculated checksum of the ClickHouse binary ({0}) does not correspond" - " to the reference checksum stored in the binary ({1})." - " It may indicate one of the following:" - " - the file {2} was changed just after startup;" - " - the file {2} is damaged on disk due to faulty hardware;" - " - the loaded executable is damaged in memory due to faulty hardware;" + "Calculated checksum of the executable ({0}) does not correspond" + " to the reference checksum stored in the executable ({1})." + " This may indicate one of the following:" + " - the executable {2} was changed just after startup;" + " - the executable {2} was corrupted on disk due to faulty hardware;" + " - the loaded executable was corrupted in memory due to faulty hardware;" " - the file {2} was intentionally modified;" - " - logical error in code." + " - a logical error in the code." , calculated_binary_hash, stored_binary_hash, executable_path); } } diff --git a/src/Common/Elf.cpp b/src/Common/Elf.cpp index dcbc836d9cb..b735367b179 100644 --- a/src/Common/Elf.cpp +++ b/src/Common/Elf.cpp @@ -176,9 +176,9 @@ String Elf::getBuildID(const char * nhdr_pos, size_t size) #endif // OS_SUNOS -String Elf::getBinaryHash() const +String Elf::getStoredBinaryHash() const { - if (auto section = findSectionByName(".ClickHouse.hash")) + if (auto section = findSectionByName(".clickhouse.hash")) return {section->begin(), section->end()}; else return {}; diff --git a/src/Common/Elf.h b/src/Common/Elf.h index f23458cfc2e..5a6bd9e302d 100644 --- a/src/Common/Elf.h +++ b/src/Common/Elf.h @@ -61,7 +61,7 @@ public: static String getBuildID(const char * nhdr_pos, size_t size); /// Hash of the binary for integrity checks. - String getBinaryHash() const; + String getStoredBinaryHash() const; private: MMapReadBufferFromFile in; diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index c203a96ff11..30f96592366 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -355,23 +355,23 @@ private: String calculated_binary_hash = getHashOfLoadedBinaryHex(); if (daemon.stored_binary_hash.empty()) { - LOG_FATAL(log, "Calculated checksum of the binary: {}." - " There is no information about the reference checksum.", calculated_binary_hash); + LOG_FATAL(log, "Integrity check of the executable skipped because the reference checksum could not be read." + " (calculated checksum: {})", calculated_binary_hash); } else if (calculated_binary_hash == daemon.stored_binary_hash) { - LOG_FATAL(log, "Checksum of the binary: {}, integrity check passed.", calculated_binary_hash); + LOG_FATAL(log, "Integrity check of the executable successfully passed (checksum: {})", calculated_binary_hash); } else { - LOG_FATAL(log, "Calculated checksum of the ClickHouse binary ({0}) does not correspond" - " to the reference checksum stored in the binary ({1})." - " It may indicate one of the following:" - " - the file was changed just after startup;" - " - the file is damaged on disk due to faulty hardware;" - " - the loaded executable is damaged in memory due to faulty hardware;" + LOG_FATAL(log, "Calculated checksum of the executable ({0}) does not correspond" + " to the reference checksum stored in the executable ({1})." + " This may indicate one of the following:" + " - the executable was changed just after startup;" + " - the executable was corrupted on disk due to faulty hardware;" + " - the loaded executable was corrupted in memory due to faulty hardware;" " - the file was intentionally modified;" - " - logical error in code." + " - a logical error in the code." , calculated_binary_hash, daemon.stored_binary_hash); } #endif @@ -872,7 +872,7 @@ void BaseDaemon::initializeTerminationAndSignalProcessing() std::string executable_path = getExecutablePath(); if (!executable_path.empty()) - stored_binary_hash = DB::Elf(executable_path).getBinaryHash(); + stored_binary_hash = DB::Elf(executable_path).getStoredBinaryHash(); #endif } From 5d4b28955039ff1d331b07a1c8b6f7013e386631 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 14 Jun 2022 08:34:42 +0000 Subject: [PATCH 084/204] Update stats in update delta --- src/Coordination/KeeperStorage.cpp | 47 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 3e86e860de8..c6ce81fb3a5 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -562,7 +562,6 @@ bool KeeperStorage::createNode( parent_path, [child_path](KeeperStorage::Node & parent) { - ++parent.stat.numChildren; parent.addChild(child_path); chassert(parent.stat.numChildren == static_cast(parent.getChildren().size())); } @@ -591,7 +590,6 @@ bool KeeperStorage::removeNode(const std::string & path, int32_t version) parentPath(path), [child_basename = getBaseName(node_it->key)](KeeperStorage::Node & parent) { - --parent.stat.numChildren; parent.removeChild(child_basename); chassert(parent.stat.numChildren == static_cast(parent.getChildren().size())); } @@ -765,6 +763,24 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr if (request.is_ephemeral) storage.ephemerals[session_id].emplace(path_created); + int32_t parent_cversion = request.parent_cversion; + + new_deltas.emplace_back( + std::string{parent_path}, + zxid, + KeeperStorage::UpdateNodeDelta{[parent_cversion, zxid](KeeperStorage::Node & node) + { + ++node.seq_num; + if (parent_cversion == -1) + ++node.stat.cversion; + else if (parent_cversion > node.stat.cversion) + node.stat.cversion = parent_cversion; + + if (zxid > node.stat.pzxid) + node.stat.pzxid = zxid; + ++node.stat.numChildren; + }}); + Coordination::Stat stat; stat.czxid = zxid; stat.mzxid = zxid; @@ -783,23 +799,6 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr zxid, KeeperStorage::CreateNodeDelta{stat, request.is_sequential, std::move(node_acls), request.data}); - int32_t parent_cversion = request.parent_cversion; - - new_deltas.emplace_back( - std::string{parent_path}, - zxid, - KeeperStorage::UpdateNodeDelta{[parent_cversion, zxid](KeeperStorage::Node & node) - { - ++node.seq_num; - if (parent_cversion == -1) - ++node.stat.cversion; - else if (parent_cversion > node.stat.cversion) - node.stat.cversion = parent_cversion; - - if (zxid > node.stat.pzxid) - node.stat.pzxid = zxid; - }}); - digest = storage.calculateNodesDigest(digest, new_deltas); return new_deltas; } @@ -946,16 +945,17 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr if (request.restored_from_zookeeper_log) update_parent_pzxid(); - new_deltas.emplace_back(request.path, zxid, KeeperStorage::RemoveNodeDelta{request.version, node->stat.ephemeralOwner}); - new_deltas.emplace_back( std::string{parentPath(request.path)}, zxid, KeeperStorage::UpdateNodeDelta{[](KeeperStorage::Node & parent) { ++parent.stat.cversion; + --parent.stat.numChildren; }}); + new_deltas.emplace_back(request.path, zxid, KeeperStorage::RemoveNodeDelta{request.version, node->stat.ephemeralOwner}); + if (node->stat.ephemeralOwner != 0) storage.unregisterEphemeralPath(node->stat.ephemeralOwner, request.path); @@ -1810,8 +1810,6 @@ void KeeperStorage::preprocessRequest( { for (const auto & ephemeral_path : session_ephemerals->second) { - new_deltas.emplace_back(ephemeral_path, transaction.zxid, RemoveNodeDelta{.ephemeral_owner = session_id}); - new_deltas.emplace_back ( parentPath(ephemeral_path).toString(), @@ -1821,9 +1819,12 @@ void KeeperStorage::preprocessRequest( [ephemeral_path](Node & parent) { ++parent.stat.cversion; + --parent.stat.numChildren; } } ); + + new_deltas.emplace_back(ephemeral_path, transaction.zxid, RemoveNodeDelta{.ephemeral_owner = session_id}); } ephemerals.erase(session_ephemerals); From 4a37e36d78550d3defbefbafa300e80dba702375 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Tue, 14 Jun 2022 12:52:52 +0200 Subject: [PATCH 085/204] Updated libunwind --- contrib/libunwind | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/libunwind b/contrib/libunwind index e3f5cd3942e..8e21568a41f 160000 --- a/contrib/libunwind +++ b/contrib/libunwind @@ -1 +1 @@ -Subproject commit e3f5cd3942edbb357c450a17e9c5d1321a8ce958 +Subproject commit 8e21568a41ff753c02a2a5c092b65302c4e5c6ae From 9a6e6ccfafb7cbebab741d568de3d644497d2194 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Tue, 14 Jun 2022 21:14:18 +0800 Subject: [PATCH 086/204] Fix significant join performance regression --- src/Interpreters/HashJoin.cpp | 22 ++++++++++------------ tests/performance/join_append_block.xml | 3 +++ 2 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 tests/performance/join_append_block.xml diff --git a/src/Interpreters/HashJoin.cpp b/src/Interpreters/HashJoin.cpp index fef741446bf..0cec83e964b 100644 --- a/src/Interpreters/HashJoin.cpp +++ b/src/Interpreters/HashJoin.cpp @@ -1003,15 +1003,13 @@ public: /// If it's joinGetOrNull, we need to wrap not-nullable columns in StorageJoin. for (size_t j = 0, size = right_indexes.size(); j < size; ++j) { - auto column_from_block = block.getByPosition(right_indexes[j]); - if (type_name[j].type->lowCardinality() != column_from_block.type->lowCardinality()) - { - JoinCommon::changeLowCardinalityInplace(column_from_block); - } - + const auto & column_from_block = block.getByPosition(right_indexes[j]); if (auto * nullable_col = typeid_cast(columns[j].get()); nullable_col && !column_from_block.column->isNullable()) nullable_col->insertFromNotNullable(*column_from_block.column, row_num); + else if (auto * lowcard_col = typeid_cast(columns[j].get()); + lowcard_col && !typeid_cast(column_from_block.column.get())) + lowcard_col->insertFromFullColumn(*column_from_block.column, row_num); else columns[j]->insertFrom(*column_from_block.column, row_num); } @@ -1020,12 +1018,12 @@ public: { for (size_t j = 0, size = right_indexes.size(); j < size; ++j) { - auto column_from_block = block.getByPosition(right_indexes[j]); - if (type_name[j].type->lowCardinality() != column_from_block.type->lowCardinality()) - { - JoinCommon::changeLowCardinalityInplace(column_from_block); - } - columns[j]->insertFrom(*column_from_block.column, row_num); + const auto & column_from_block = block.getByPosition(right_indexes[j]); + if (auto * lowcard_col = typeid_cast(columns[j].get()); + lowcard_col && !typeid_cast(column_from_block.column.get())) + lowcard_col->insertFromFullColumn(*column_from_block.column, row_num); + else + columns[j]->insertFrom(*column_from_block.column, row_num); } } } diff --git a/tests/performance/join_append_block.xml b/tests/performance/join_append_block.xml new file mode 100644 index 00000000000..15859e95941 --- /dev/null +++ b/tests/performance/join_append_block.xml @@ -0,0 +1,3 @@ + + SELECT count(c) FROM numbers_mt(100000000) AS a INNER JOIN (SELECT number, toString(number) AS c FROM numbers(2000000)) AS b ON (a.number % 10000000) = b.number + From 6e5559339874958928c8896600fc8eb440c058ca Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 14 Jun 2022 13:23:46 +0000 Subject: [PATCH 087/204] Fix remove preprocess --- src/Coordination/KeeperStorage.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index c6ce81fb3a5..a688c4d0ce1 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -939,7 +939,7 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr } else if (request.version != -1 && request.version != node->stat.version) return {{zxid, Coordination::Error::ZBADVERSION}}; - else if (!node->getChildren().empty()) + else if (node->stat.numChildren != 0) return {{zxid, Coordination::Error::ZNOTEMPTY}}; if (request.restored_from_zookeeper_log) @@ -959,7 +959,6 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr if (node->stat.ephemeralOwner != 0) storage.unregisterEphemeralPath(node->stat.ephemeralOwner, request.path); - digest = storage.calculateNodesDigest(digest, new_deltas); return new_deltas; From 4ae0dccf003e94fea8ac39a6f8852dca70833437 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 13 Jun 2022 18:28:23 +0300 Subject: [PATCH 088/204] docker/test/util/process_functional_tests_result.py: fix typo Signed-off-by: Azat Khuzhin --- docker/test/util/process_functional_tests_result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/util/process_functional_tests_result.py b/docker/test/util/process_functional_tests_result.py index 687c03697d7..878ffaadb79 100755 --- a/docker/test/util/process_functional_tests_result.py +++ b/docker/test/util/process_functional_tests_result.py @@ -113,7 +113,7 @@ def process_result(result_path): test_results, ) = process_test_log(result_path) is_flacky_check = 1 < int(os.environ.get("NUM_TRIES", 1)) - logging.info("Is flacky check: %s", is_flacky_check) + logging.info("Is flaky check: %s", is_flacky_check) # If no tests were run (success == 0) it indicates an error (e.g. server did not start or crashed immediately) # But it's Ok for "flaky checks" - they can contain just one test for check which is marked as skipped. if failed != 0 or unknown != 0 or (success == 0 and (not is_flacky_check)): From 23494e99578406efe938919adc420fe6c0bfc195 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 13 Jun 2022 18:36:57 +0300 Subject: [PATCH 089/204] tests: avoid "_csv.Error: field larger than field limit (131072)" error v2: apply black v3: ignore only hung check report v4: ignore everything after "Database: " Signed-off-by: Azat Khuzhin --- .../util/process_functional_tests_result.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docker/test/util/process_functional_tests_result.py b/docker/test/util/process_functional_tests_result.py index 878ffaadb79..fdd193e9c7f 100755 --- a/docker/test/util/process_functional_tests_result.py +++ b/docker/test/util/process_functional_tests_result.py @@ -11,6 +11,7 @@ TIMEOUT_SIGN = "[ Timeout! " UNKNOWN_SIGN = "[ UNKNOWN " SKIPPED_SIGN = "[ SKIPPED " HUNG_SIGN = "Found hung queries in processlist" +DATABASE_SIGN = "Database: " SUCCESS_FINISH_SIGNS = ["All tests have finished", "No tests were run"] @@ -27,14 +28,19 @@ def process_test_log(log_path): retries = False success_finish = False test_results = [] + test_end = True with open(log_path, "r") as test_file: for line in test_file: original_line = line line = line.strip() + if any(s in line for s in SUCCESS_FINISH_SIGNS): success_finish = True + # Ignore hung check report, since it may be quite large. + # (and may break python parser which has limit of 128KiB for each row). if HUNG_SIGN in line: hung = True + break if RETRIES_SIGN in line: retries = True if any( @@ -67,8 +73,17 @@ def process_test_log(log_path): else: success += int(OK_SIGN in line) test_results.append((test_name, "OK", test_time, [])) - elif len(test_results) > 0 and test_results[-1][1] == "FAIL": + test_end = False + elif ( + len(test_results) > 0 and test_results[-1][1] == "FAIL" and not test_end + ): test_results[-1][3].append(original_line) + # Database printed after everything else in case of failures, + # so this is a stop marker for capturing test output. + # + # And it is handled after everything else to include line with database into the report. + if DATABASE_SIGN in line: + test_end = True test_results = [ (test[0], test[1], test[2], "".join(test[3])) for test in test_results From d5a7a5be8ec71a7758392ea7fb39de0c5ef1d1f2 Mon Sep 17 00:00:00 2001 From: Amos Bird Date: Fri, 13 May 2022 18:59:24 +0800 Subject: [PATCH 090/204] Fix use-after-free in system.projection_parts --- src/Storages/MergeTree/MergeTreeData.cpp | 124 +++++++++--------- src/Storages/MergeTree/MergeTreeData.h | 21 +-- .../System/StorageSystemPartsBase.cpp | 26 +++- src/Storages/System/StorageSystemPartsBase.h | 5 +- .../System/StorageSystemProjectionParts.cpp | 11 +- .../StorageSystemProjectionPartsColumns.cpp | 11 +- 6 files changed, 110 insertions(+), 88 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 6a58b4344c3..539c0f2f05e 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4381,10 +4381,7 @@ std::set MergeTreeData::getPartitionIdsAffectedByCommands( MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVectorForInternalUsage( - const DataPartStates & affordable_states, - const DataPartsLock & /*lock*/, - DataPartStateVector * out_states, - bool require_projection_parts) const + const DataPartStates & affordable_states, const DataPartsLock & /*lock*/, DataPartStateVector * out_states) const { DataPartsVector res; DataPartsVector buf; @@ -4392,86 +4389,89 @@ MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVectorForInternalUsage for (auto state : affordable_states) { auto range = getDataPartsStateRange(state); - - if (require_projection_parts) - { - for (const auto & part : range) - { - for (const auto & [_, projection_part] : part->getProjectionParts()) - res.push_back(projection_part); - } - } - else - { - std::swap(buf, res); - res.clear(); - std::merge(range.begin(), range.end(), buf.begin(), buf.end(), std::back_inserter(res), LessDataPart()); //-V783 - } + std::swap(buf, res); + res.clear(); + std::merge(range.begin(), range.end(), buf.begin(), buf.end(), std::back_inserter(res), LessDataPart()); //-V783 } if (out_states != nullptr) { out_states->resize(res.size()); - if (require_projection_parts) - { - for (size_t i = 0; i < res.size(); ++i) - (*out_states)[i] = res[i]->getParentPart()->getState(); - } - else - { - for (size_t i = 0; i < res.size(); ++i) - (*out_states)[i] = res[i]->getState(); - } + for (size_t i = 0; i < res.size(); ++i) + (*out_states)[i] = res[i]->getState(); } return res; } -MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVectorForInternalUsage( - const DataPartStates & affordable_states, - DataPartStateVector * out_states, - bool require_projection_parts) const +MergeTreeData::DataPartsVector +MergeTreeData::getDataPartsVectorForInternalUsage(const DataPartStates & affordable_states, DataPartStateVector * out_states) const { auto lock = lockParts(); - return getDataPartsVectorForInternalUsage(affordable_states, lock, out_states, require_projection_parts); + return getDataPartsVectorForInternalUsage(affordable_states, lock, out_states); } -MergeTreeData::DataPartsVector -MergeTreeData::getAllDataPartsVector(MergeTreeData::DataPartStateVector * out_states, bool require_projection_parts) const +MergeTreeData::ProjectionPartsVector +MergeTreeData::getProjectionPartsVectorForInternalUsage(const DataPartStates & affordable_states, DataPartStateVector * out_states) const +{ + auto lock = lockParts(); + ProjectionPartsVector res; + for (auto state : affordable_states) + { + auto range = getDataPartsStateRange(state); + for (const auto & part : range) + { + res.data_parts.push_back(part); + for (const auto & [_, projection_part] : part->getProjectionParts()) + res.projection_parts.push_back(projection_part); + } + } + + if (out_states != nullptr) + { + out_states->resize(res.projection_parts.size()); + for (size_t i = 0; i < res.projection_parts.size(); ++i) + (*out_states)[i] = res.projection_parts[i]->getParentPart()->getState(); + } + + return res; +} + +MergeTreeData::DataPartsVector MergeTreeData::getAllDataPartsVector(MergeTreeData::DataPartStateVector * out_states) const { DataPartsVector res; - if (require_projection_parts) + auto lock = lockParts(); + res.assign(data_parts_by_info.begin(), data_parts_by_info.end()); + if (out_states != nullptr) { - auto lock = lockParts(); - for (const auto & part : data_parts_by_info) - { - for (const auto & [p_name, projection_part] : part->getProjectionParts()) - res.push_back(projection_part); - } - - if (out_states != nullptr) - { - out_states->resize(res.size()); - for (size_t i = 0; i < res.size(); ++i) - (*out_states)[i] = res[i]->getParentPart()->getState(); - } - } - else - { - auto lock = lockParts(); - res.assign(data_parts_by_info.begin(), data_parts_by_info.end()); - - if (out_states != nullptr) - { - out_states->resize(res.size()); - for (size_t i = 0; i < res.size(); ++i) - (*out_states)[i] = res[i]->getState(); - } + out_states->resize(res.size()); + for (size_t i = 0; i < res.size(); ++i) + (*out_states)[i] = res[i]->getState(); } return res; } +MergeTreeData::ProjectionPartsVector MergeTreeData::getAllProjectionPartsVector(MergeTreeData::DataPartStateVector * out_states) const +{ + ProjectionPartsVector res; + auto lock = lockParts(); + for (const auto & part : data_parts_by_info) + { + res.data_parts.push_back(part); + for (const auto & [p_name, projection_part] : part->getProjectionParts()) + res.projection_parts.push_back(projection_part); + } + + if (out_states != nullptr) + { + out_states->resize(res.projection_parts.size()); + for (size_t i = 0; i < res.projection_parts.size(); ++i) + (*out_states)[i] = res.projection_parts[i]->getParentPart()->getState(); + } + return res; +} + DetachedPartsInfo MergeTreeData::getDetachedParts() const { DetachedPartsInfo res; diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index b8aa326c6bc..c5cadf9a117 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -449,26 +449,31 @@ public: Int64 getMaxBlockNumber() const; + struct ProjectionPartsVector + { + DataPartsVector projection_parts; + DataPartsVector data_parts; + }; /// Returns a copy of the list so that the caller shouldn't worry about locks. DataParts getDataParts(const DataPartStates & affordable_states) const; DataPartsVector getDataPartsVectorForInternalUsage( - const DataPartStates & affordable_states, - const DataPartsLock & lock, - DataPartStateVector * out_states = nullptr, - bool require_projection_parts = false) const; + const DataPartStates & affordable_states, const DataPartsLock & lock, DataPartStateVector * out_states = nullptr) const; /// Returns sorted list of the parts with specified states /// out_states will contain snapshot of each part state DataPartsVector getDataPartsVectorForInternalUsage( - const DataPartStates & affordable_states, DataPartStateVector * out_states = nullptr, bool require_projection_parts = false) const; + const DataPartStates & affordable_states, DataPartStateVector * out_states = nullptr) const; + /// Same as above but only returns projection parts + ProjectionPartsVector getProjectionPartsVectorForInternalUsage( + const DataPartStates & affordable_states, DataPartStateVector * out_states = nullptr) const; /// Returns absolutely all parts (and snapshot of their states) - DataPartsVector getAllDataPartsVector( - DataPartStateVector * out_states = nullptr, - bool require_projection_parts = false) const; + DataPartsVector getAllDataPartsVector(DataPartStateVector * out_states = nullptr) const; + /// Same as above but only returns projection parts + ProjectionPartsVector getAllProjectionPartsVector(MergeTreeData::DataPartStateVector * out_states = nullptr) const; /// Returns parts in Active state DataParts getDataPartsForInternalUsage() const; diff --git a/src/Storages/System/StorageSystemPartsBase.cpp b/src/Storages/System/StorageSystemPartsBase.cpp index 0b976680d00..b272c080262 100644 --- a/src/Storages/System/StorageSystemPartsBase.cpp +++ b/src/Storages/System/StorageSystemPartsBase.cpp @@ -48,9 +48,25 @@ bool StorageSystemPartsBase::hasStateColumn(const Names & column_names, const St } MergeTreeData::DataPartsVector -StoragesInfo::getParts(MergeTreeData::DataPartStateVector & state, bool has_state_column, bool require_projection_parts) const +StoragesInfo::getParts(MergeTreeData::DataPartStateVector & state, bool has_state_column) const { - if (require_projection_parts && data->getInMemoryMetadataPtr()->projections.empty()) + using State = MergeTreeData::DataPartState; + if (need_inactive_parts) + { + /// If has_state_column is requested, return all states. + if (!has_state_column) + return data->getDataPartsVectorForInternalUsage({State::Active, State::Outdated}, &state); + + return data->getAllDataPartsVector(&state); + } + + return data->getDataPartsVectorForInternalUsage({State::Active}, &state); +} + +MergeTreeData::ProjectionPartsVector +StoragesInfo::getProjectionParts(MergeTreeData::DataPartStateVector & state, bool has_state_column) const +{ + if (data->getInMemoryMetadataPtr()->projections.empty()) return {}; using State = MergeTreeData::DataPartState; @@ -58,12 +74,12 @@ StoragesInfo::getParts(MergeTreeData::DataPartStateVector & state, bool has_stat { /// If has_state_column is requested, return all states. if (!has_state_column) - return data->getDataPartsVectorForInternalUsage({State::Active, State::Outdated}, &state, require_projection_parts); + return data->getProjectionPartsVectorForInternalUsage({State::Active, State::Outdated}, &state); - return data->getAllDataPartsVector(&state, require_projection_parts); + return data->getAllProjectionPartsVector(&state); } - return data->getDataPartsVectorForInternalUsage({State::Active}, &state, require_projection_parts); + return data->getProjectionPartsVectorForInternalUsage({State::Active}, &state); } StoragesInfoStream::StoragesInfoStream(const SelectQueryInfo & query_info, ContextPtr context) diff --git a/src/Storages/System/StorageSystemPartsBase.h b/src/Storages/System/StorageSystemPartsBase.h index 36c0fd551df..8db96700e1a 100644 --- a/src/Storages/System/StorageSystemPartsBase.h +++ b/src/Storages/System/StorageSystemPartsBase.h @@ -23,8 +23,9 @@ struct StoragesInfo MergeTreeData * data = nullptr; explicit operator bool() const { return storage != nullptr; } - MergeTreeData::DataPartsVector - getParts(MergeTreeData::DataPartStateVector & state, bool has_state_column, bool require_projection_parts = false) const; + + MergeTreeData::DataPartsVector getParts(MergeTreeData::DataPartStateVector & state, bool has_state_column) const; + MergeTreeData::ProjectionPartsVector getProjectionParts(MergeTreeData::DataPartStateVector & state, bool has_state_column) const; }; /** A helper class that enumerates the storages that match given query. */ diff --git a/src/Storages/System/StorageSystemProjectionParts.cpp b/src/Storages/System/StorageSystemProjectionParts.cpp index 591277c1a66..21ca1f57703 100644 --- a/src/Storages/System/StorageSystemProjectionParts.cpp +++ b/src/Storages/System/StorageSystemProjectionParts.cpp @@ -94,14 +94,13 @@ void StorageSystemProjectionParts::processNextStorage( { using State = IMergeTreeDataPart::State; MergeTreeData::DataPartStateVector all_parts_state; - MergeTreeData::DataPartsVector all_parts; - - all_parts = info.getParts(all_parts_state, has_state_column, true /* require_projection_parts */); - - for (size_t part_number = 0; part_number < all_parts.size(); ++part_number) + MergeTreeData::ProjectionPartsVector all_parts = info.getProjectionParts(all_parts_state, has_state_column); + for (size_t part_number = 0; part_number < all_parts.projection_parts.size(); ++part_number) { - const auto & part = all_parts[part_number]; + const auto & part = all_parts.projection_parts[part_number]; const auto * parent_part = part->getParentPart(); + chassert(parent_part); + auto part_state = all_parts_state[part_number]; ColumnSize columns_size = part->getTotalColumnsSize(); diff --git a/src/Storages/System/StorageSystemProjectionPartsColumns.cpp b/src/Storages/System/StorageSystemProjectionPartsColumns.cpp index 8f6db9fcbe8..1eec6825d5a 100644 --- a/src/Storages/System/StorageSystemProjectionPartsColumns.cpp +++ b/src/Storages/System/StorageSystemProjectionPartsColumns.cpp @@ -100,14 +100,15 @@ void StorageSystemProjectionPartsColumns::processNextStorage( } } - /// Go through the list of parts. + /// Go through the list of projection parts. MergeTreeData::DataPartStateVector all_parts_state; - MergeTreeData::DataPartsVector all_parts; - all_parts = info.getParts(all_parts_state, has_state_column, true /* require_projection_parts */); - for (size_t part_number = 0; part_number < all_parts.size(); ++part_number) + MergeTreeData::ProjectionPartsVector all_parts = info.getProjectionParts(all_parts_state, has_state_column); + for (size_t part_number = 0; part_number < all_parts.projection_parts.size(); ++part_number) { - const auto & part = all_parts[part_number]; + const auto & part = all_parts.projection_parts[part_number]; const auto * parent_part = part->getParentPart(); + chassert(parent_part); + auto part_state = all_parts_state[part_number]; auto columns_size = part->getTotalColumnsSize(); auto parent_columns_size = parent_part->getTotalColumnsSize(); From bf7dd3928231219ac3c68f83a8bfd89b27eac7ef Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Mon, 13 Jun 2022 13:36:22 +0000 Subject: [PATCH 091/204] Fix: decimal rounding Fixes #37531 --- src/Functions/FunctionsRound.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Functions/FunctionsRound.h b/src/Functions/FunctionsRound.h index 518b969d441..9bf5ed2a6fd 100644 --- a/src/Functions/FunctionsRound.h +++ b/src/Functions/FunctionsRound.h @@ -168,7 +168,7 @@ struct IntegerRoundingComputation __builtin_unreachable(); } - static ALWAYS_INLINE void compute(const T * __restrict in, size_t scale, T * __restrict out) + static ALWAYS_INLINE void compute(const T * __restrict in, size_t scale, T * __restrict out) requires std::integral { if constexpr (sizeof(T) <= sizeof(scale) && scale_mode == ScaleMode::Negative) { @@ -181,6 +181,10 @@ struct IntegerRoundingComputation *out = compute(*in, scale); } + static ALWAYS_INLINE void compute(const T * __restrict in, T scale, T * __restrict out) requires(!std::integral) + { + *out = compute(*in, scale); + } }; @@ -432,7 +436,7 @@ public: scale_arg = in_scale - scale_arg; if (scale_arg > 0) { - size_t scale = intExp10(scale_arg); + auto scale = intExp10OfSize(scale_arg); const NativeType * __restrict p_in = reinterpret_cast(in.data()); const NativeType * end_in = reinterpret_cast(in.data()) + in.size(); From 94116a1ee08519a0d4500fdd245436541eb624d6 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Tue, 14 Jun 2022 11:22:03 +0000 Subject: [PATCH 092/204] Decimal128/256 rouding tests --- .../0_stateless/00700_decimal_round.reference | 12 ++++++++++++ tests/queries/0_stateless/00700_decimal_round.sql | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tests/queries/0_stateless/00700_decimal_round.reference b/tests/queries/0_stateless/00700_decimal_round.reference index d0f03c07849..4b4994106fe 100644 --- a/tests/queries/0_stateless/00700_decimal_round.reference +++ b/tests/queries/0_stateless/00700_decimal_round.reference @@ -73,3 +73,15 @@ 12345678901234567890123456789.123456789 -12345678901234567890123456789.123456789 12345678901234567890123456790 -12345678901234567890123456789 12345678901234567890123457000 -12345678901234567890123456000 12345678901234567890123456789.123456789 -12345678901234567890123456789.123456789 12345678901234567890123456789 -12345678901234567890123456790 12345678901234567890123456000 -12345678901234567890123457000 12345678901234567890123456789.123456789 -12345678901234567890123456789.123456789 12345678901234567890123456789 -12345678901234567890123456789 12345678901234567890123456000 -12345678901234567890123456000 +-- Decimal128, Scale 20 +round() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567890 1234568000 -1234568000 +roundBankers() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567890 1234568000 -1234568000 +ceil() : 1234567890.123456789 -1234567890.123456789 1234567891 -1234567890 1234568000 -1234567000 +floor() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567891 1234567000 -1234568000 +trunc() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567890 1234567000 -1234567000 +-- Decimal256, Scale 40 +round() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567890 1234568000 -1234568000 +roundBankers() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567890 1234568000 -1234568000 +ceil() : 1234567890.123456789 -1234567890.123456789 1234567891 -1234567890 1234568000 -1234567000 +floor() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567891 1234567000 -1234568000 +trunc() : 1234567890.123456789 -1234567890.123456789 1234567890 -1234567890 1234567000 -1234567000 diff --git a/tests/queries/0_stateless/00700_decimal_round.sql b/tests/queries/0_stateless/00700_decimal_round.sql index c5b8dbb520b..bf2749ac03f 100644 --- a/tests/queries/0_stateless/00700_decimal_round.sql +++ b/tests/queries/0_stateless/00700_decimal_round.sql @@ -78,3 +78,17 @@ SELECT toDecimal128('12345678901234567890123456789.123456789', 9) AS x, -x AS y, SELECT toDecimal128('12345678901234567890123456789.123456789', 9) AS x, -x AS y, ceil(x), ceil(y), ceil(x, -3), ceil(y, -3); SELECT toDecimal128('12345678901234567890123456789.123456789', 9) AS x, -x AS y, floor(x), floor(y), floor(x, -3), floor(y, -3); SELECT toDecimal128('12345678901234567890123456789.123456789', 9) AS x, -x AS y, trunc(x), trunc(y), trunc(x, -3), trunc(y, -3); + +select '-- Decimal128, Scale 20'; +SELECT 'round() : ', toDecimal128('1234567890.123456789', 20) AS x, -x AS y, round(x), round(y), round(x, -3), round(y, -3); +SELECT 'roundBankers() : ', toDecimal128('1234567890.123456789', 20) AS x, -x AS y, roundBankers(x), roundBankers(y), roundBankers(x, -3), roundBankers(y, -3); +SELECT 'ceil() : ', toDecimal128('1234567890.123456789', 20) AS x, -x AS y, ceil(x), ceil(y), ceil(x, -3), ceil(y, -3); +SELECT 'floor() : ', toDecimal128('1234567890.123456789', 20) AS x, -x AS y, floor(x), floor(y), floor(x, -3), floor(y, -3); +SELECT 'trunc() : ', toDecimal128('1234567890.123456789', 20) AS x, -x AS y, trunc(x), trunc(y), trunc(x, -3), trunc(y, -3); + +select '-- Decimal256, Scale 40'; +SELECT 'round() : ', toDecimal256('1234567890.123456789', 40) AS x, -x AS y, round(x), round(y), round(x, -3), round(y, -3); +SELECT 'roundBankers() : ', toDecimal256('1234567890.123456789', 40) AS x, -x AS y, roundBankers(x), roundBankers(y), roundBankers(x, -3), roundBankers(y, -3); +SELECT 'ceil() : ', toDecimal256('1234567890.123456789', 40) AS x, -x AS y, ceil(x), ceil(y), ceil(x, -3), ceil(y, -3); +SELECT 'floor() : ', toDecimal256('1234567890.123456789', 40) AS x, -x AS y, floor(x), floor(y), floor(x, -3), floor(y, -3); +SELECT 'trunc() : ', toDecimal256('1234567890.123456789', 40) AS x, -x AS y, trunc(x), trunc(y), trunc(x, -3), trunc(y, -3); From c39f4a862c8ea07efd8925ab2989cb8a9e705477 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 14 Jun 2022 18:12:07 +0000 Subject: [PATCH 093/204] Retain .clickhouse.hash when stripping --- cmake/strip_binary.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/strip_binary.cmake b/cmake/strip_binary.cmake index c0fd7626478..6e38c86fc70 100644 --- a/cmake/strip_binary.cmake +++ b/cmake/strip_binary.cmake @@ -19,10 +19,12 @@ macro(clickhouse_strip_binary) COMMAND mkdir -p "${STRIP_DESTINATION_DIR}/lib/debug/bin" COMMAND mkdir -p "${STRIP_DESTINATION_DIR}/bin" COMMAND cp "${STRIP_BINARY_PATH}" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" + # Splits debug symbols into separate file, leaves the binary untouched: COMMAND "${OBJCOPY_PATH}" --only-keep-debug --compress-debug-sections "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" COMMAND chmod 0644 "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" - # Additionally remove sections '.note' & '.comment' to emulate stripping in Debian: www.debian.org/doc/debian-policy/ch-files.html - COMMAND "${STRIP_PATH}" --remove-section=.comment --remove-section=.note "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" + # Strips binary, sections '.note' & '.comment' are removed in line with Debian's stripping policy: www.debian.org/doc/debian-policy/ch-files.html, section '.clickhouse.hash' is needed for integrity check: + COMMAND "${STRIP_PATH}" --remove-section=.comment --remove-section=.note --keep-section=.clickhouse.hash "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" + # Associate stripped binary with debug symbols: COMMAND "${OBJCOPY_PATH}" --add-gnu-debuglink "${STRIP_DESTINATION_DIR}/lib/debug/bin/${STRIP_TARGET}.debug" "${STRIP_DESTINATION_DIR}/bin/${STRIP_TARGET}" COMMENT "Stripping clickhouse binary" VERBATIM ) From 45d309108781842876baa8e4bd51827eb242bcaf Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Tue, 14 Jun 2022 17:30:16 +0000 Subject: [PATCH 094/204] (Temporary / to be reverted) Force an official build. --- tests/ci/build_check.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/ci/build_check.py b/tests/ci/build_check.py index 9730ac2cc46..eb03152c1a9 100644 --- a/tests/ci/build_check.py +++ b/tests/ci/build_check.py @@ -250,14 +250,17 @@ def main(): logging.info("Got version from repo %s", version.string) - official_flag = pr_info.number == 0 - if "official" in build_config: - official_flag = build_config["official"] + # official_flag = pr_info.number == 0 + # if "official" in build_config: + # official_flag = build_config["official"] + # + # version_type = "testing" + # if "release" in pr_info.labels or "release-lts" in pr_info.labels: + # version_type = "stable" + # official_flag = True version_type = "testing" - if "release" in pr_info.labels or "release-lts" in pr_info.labels: - version_type = "stable" - official_flag = True + official_flag = True update_version_local(version, version_type) From 6a421100881c40df9800d84ff475267e7ea21a27 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Tue, 14 Jun 2022 19:34:41 +0000 Subject: [PATCH 095/204] Fixing test. --- src/Client/ConnectionPoolWithFailover.cpp | 6 ++++++ src/Common/PoolWithFailoverBase.h | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Client/ConnectionPoolWithFailover.cpp b/src/Client/ConnectionPoolWithFailover.cpp index 13d39980e1c..a2bc4ca5059 100644 --- a/src/Client/ConnectionPoolWithFailover.cpp +++ b/src/Client/ConnectionPoolWithFailover.cpp @@ -45,6 +45,9 @@ IConnectionPool::Entry ConnectionPoolWithFailover::get(const ConnectionTimeouts const Settings * settings, bool /*force_connected*/) { + if (nested_pools.empty()) + throw DB::Exception(DB::ErrorCodes::ALL_CONNECTION_TRIES_FAILED, "Cannot get connection from ConnectionPoolWithFailover cause nested pools are empty"); + TryGetEntryFunc try_get_entry = [&](NestedPool & pool, std::string & fail_message) { return tryGetEntry(pool, timeouts, fail_message, settings); @@ -167,6 +170,9 @@ std::vector ConnectionPoolWithFailover::g PoolMode pool_mode, const TryGetEntryFunc & try_get_entry) { + if (nested_pools.empty()) + throw DB::Exception(DB::ErrorCodes::ALL_CONNECTION_TRIES_FAILED, "Cannot get connection from ConnectionPoolWithFailover cause nested pools are empty"); + size_t min_entries = (settings && settings->skip_unavailable_shards) ? 0 : 1; size_t max_tries = (settings ? size_t{settings->connections_with_failover_max_tries} : diff --git a/src/Common/PoolWithFailoverBase.h b/src/Common/PoolWithFailoverBase.h index 3ce7be4b3c5..42b5b3d0990 100644 --- a/src/Common/PoolWithFailoverBase.h +++ b/src/Common/PoolWithFailoverBase.h @@ -64,9 +64,6 @@ public: , shared_pool_states(nested_pools.size()) , log(log_) { - if (nested_pools.empty()) - throw DB::Exception(DB::ErrorCodes::ALL_CONNECTION_TRIES_FAILED, "Cannot create PoolWithFailover cause nested pools are empty"); - for (size_t i = 0;i < nested_pools.size(); ++i) shared_pool_states[i].config_priority = nested_pools[i]->getPriority(); } From bd1d54d689cdaf523c0eea7314b3642ba759ce77 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Mon, 13 Jun 2022 16:51:15 +0200 Subject: [PATCH 096/204] BackgroundSchedulePool remove Poco::NotificationQueue --- src/Core/BackgroundSchedulePool.cpp | 79 +++++++++++++++-------------- src/Core/BackgroundSchedulePool.h | 14 +++-- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/Core/BackgroundSchedulePool.cpp b/src/Core/BackgroundSchedulePool.cpp index 344860ca5dd..916a3f5f902 100644 --- a/src/Core/BackgroundSchedulePool.cpp +++ b/src/Core/BackgroundSchedulePool.cpp @@ -10,18 +10,6 @@ namespace DB { - -class TaskNotification final : public Poco::Notification -{ -public: - explicit TaskNotification(const BackgroundSchedulePoolTaskInfoPtr & task_) : task(task_) {} - void execute() { task->execute(); } - -private: - BackgroundSchedulePoolTaskInfoPtr task; -}; - - BackgroundSchedulePoolTaskInfo::BackgroundSchedulePoolTaskInfo( BackgroundSchedulePool & pool_, const std::string & log_name_, const BackgroundSchedulePool::TaskFunc & function_) : pool(pool_), log_name(log_name_), function(function_) @@ -39,7 +27,7 @@ bool BackgroundSchedulePoolTaskInfo::schedule() return true; } -bool BackgroundSchedulePoolTaskInfo::scheduleAfter(size_t ms, bool overwrite) +bool BackgroundSchedulePoolTaskInfo::scheduleAfter(size_t milliseconds, bool overwrite) { std::lock_guard lock(schedule_mutex); @@ -48,7 +36,7 @@ bool BackgroundSchedulePoolTaskInfo::scheduleAfter(size_t ms, bool overwrite) if (delayed && !overwrite) return false; - pool.scheduleDelayedTask(shared_from_this(), ms, lock); + pool.scheduleDelayedTask(shared_from_this(), milliseconds, lock); return true; } @@ -121,7 +109,7 @@ void BackgroundSchedulePoolTaskInfo::execute() /// will have their chance to execute if (scheduled) - pool.queue.enqueueNotification(new TaskNotification(shared_from_this())); + pool.scheduleTask(shared_from_this()); } } @@ -136,14 +124,14 @@ void BackgroundSchedulePoolTaskInfo::scheduleImpl(std::lock_guard & /// But if it is currently executing, do nothing because it will be enqueued /// at the end of the execute() method. if (!executing) - pool.queue.enqueueNotification(new TaskNotification(shared_from_this())); + pool.scheduleTask(shared_from_this()); } Coordination::WatchCallback BackgroundSchedulePoolTaskInfo::getWatchCallback() { - return [t = shared_from_this()](const Coordination::WatchResponse &) + return [task = shared_from_this()](const Coordination::WatchResponse &) { - t->schedule(); + task->schedule(); }; } @@ -184,15 +172,18 @@ BackgroundSchedulePool::~BackgroundSchedulePool() try { { - std::unique_lock lock(delayed_tasks_mutex); + std::lock_guard lock_tasks(tasks_mutex); + std::lock_guard lock_delayed_tasks(delayed_tasks_mutex); + shutdown = true; - wakeup_cond.notify_all(); } - queue.wakeUpAll(); - delayed_thread.join(); + tasks_cond_var.notify_all(); + delayed_tasks_cond_var.notify_all(); LOG_TRACE(&Poco::Logger::get("BackgroundSchedulePool/" + thread_name), "Waiting for threads to finish."); + delayed_thread.join(); + for (auto & thread : threads) thread.join(); } @@ -208,6 +199,15 @@ BackgroundSchedulePool::TaskHolder BackgroundSchedulePool::createTask(const std: return TaskHolder(std::make_shared(*this, name, function)); } +void BackgroundSchedulePool::scheduleTask(TaskInfoPtr task_info) +{ + { + std::lock_guard tasks_lock(tasks_mutex); + tasks.push_back(std::move(task_info)); + } + + tasks_cond_var.notify_one(); +} void BackgroundSchedulePool::scheduleDelayedTask(const TaskInfoPtr & task, size_t ms, std::lock_guard & /* task_schedule_mutex_lock */) { @@ -223,7 +223,7 @@ void BackgroundSchedulePool::scheduleDelayedTask(const TaskInfoPtr & task, size_ task->delayed = true; } - wakeup_cond.notify_all(); + delayed_tasks_cond_var.notify_all(); } @@ -235,7 +235,7 @@ void BackgroundSchedulePool::cancelDelayedTask(const TaskInfoPtr & task, std::lo task->delayed = false; } - wakeup_cond.notify_all(); + delayed_tasks_cond_var.notify_all(); } @@ -264,20 +264,23 @@ void BackgroundSchedulePool::threadFunction() while (!shutdown) { - /// We have to wait with timeout to prevent very rare deadlock, caused by the following race condition: - /// 1. Background thread N: threadFunction(): checks for shutdown (it's false) - /// 2. Main thread: ~BackgroundSchedulePool(): sets shutdown to true, calls queue.wakeUpAll(), it triggers - /// all existing Poco::Events inside Poco::NotificationQueue which background threads are waiting on. - /// 3. Background thread N: threadFunction(): calls queue.waitDequeueNotification(), it creates - /// new Poco::Event inside Poco::NotificationQueue and starts to wait on it - /// Background thread N will never be woken up. - /// TODO Do we really need Poco::NotificationQueue? Why not to use std::queue + mutex + condvar or maybe even DB::ThreadPool? - constexpr size_t wait_timeout_ms = 500; - if (Poco::AutoPtr notification = queue.waitDequeueNotification(wait_timeout_ms)) + TaskInfoPtr task; + std::unique_lock tasks_lock(tasks_mutex); + { - TaskNotification & task_notification = static_cast(*notification); - task_notification.execute(); + tasks_cond_var.wait(tasks_lock, [&](){ + return shutdown || !tasks.empty(); + }); + + if (!tasks.empty()) + { + task = tasks.front(); + tasks.pop_front(); + } } + + if (task) + task->execute(); } } @@ -309,7 +312,7 @@ void BackgroundSchedulePool::delayExecutionThreadFunction() if (!task) { - wakeup_cond.wait(lock); + delayed_tasks_cond_var.wait(lock); continue; } @@ -317,7 +320,7 @@ void BackgroundSchedulePool::delayExecutionThreadFunction() if (min_time > current_time) { - wakeup_cond.wait_for(lock, std::chrono::microseconds(min_time - current_time)); + delayed_tasks_cond_var.wait_for(lock, std::chrono::microseconds(min_time - current_time)); continue; } else diff --git a/src/Core/BackgroundSchedulePool.h b/src/Core/BackgroundSchedulePool.h index fbd7e3f749a..ebd0d52ee20 100644 --- a/src/Core/BackgroundSchedulePool.h +++ b/src/Core/BackgroundSchedulePool.h @@ -62,6 +62,8 @@ private: void threadFunction(); void delayExecutionThreadFunction(); + void scheduleTask(TaskInfoPtr task_info); + /// Schedule task for execution after specified delay from now. void scheduleDelayedTask(const TaskInfoPtr & task_info, size_t ms, std::lock_guard & task_schedule_mutex_lock); @@ -69,12 +71,16 @@ private: void cancelDelayedTask(const TaskInfoPtr & task_info, std::lock_guard & task_schedule_mutex_lock); std::atomic shutdown {false}; + + /// Tasks. + std::condition_variable tasks_cond_var; + std::mutex tasks_mutex; + std::deque tasks; Threads threads; - Poco::NotificationQueue queue; - /// Delayed notifications. + /// Delayed tasks. - std::condition_variable wakeup_cond; + std::condition_variable delayed_tasks_cond_var; std::mutex delayed_tasks_mutex; /// Thread waiting for next delayed task. ThreadFromGlobalPool delayed_thread; @@ -102,7 +108,7 @@ public: /// Schedule for execution after specified delay. /// If overwrite is set then the task will be re-scheduled (if it was already scheduled, i.e. delayed == true). - bool scheduleAfter(size_t ms, bool overwrite = true); + bool scheduleAfter(size_t milliseconds, bool overwrite = true); /// Further attempts to schedule become no-op. Will wait till the end of the current execution of the task. void deactivate(); From 6ae20d6282d88a73459d0121c7d8a458ddb9a4f4 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Tue, 14 Jun 2022 12:10:11 +0200 Subject: [PATCH 097/204] Fixed style check --- src/Core/BackgroundSchedulePool.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Core/BackgroundSchedulePool.cpp b/src/Core/BackgroundSchedulePool.cpp index 916a3f5f902..e4582a733d6 100644 --- a/src/Core/BackgroundSchedulePool.cpp +++ b/src/Core/BackgroundSchedulePool.cpp @@ -94,7 +94,7 @@ void BackgroundSchedulePoolTaskInfo::execute() UInt64 milliseconds = watch.elapsedMilliseconds(); /// If the task is executed longer than specified time, it will be logged. - static const int32_t slow_execution_threshold_ms = 200; + static constexpr UInt64 slow_execution_threshold_ms = 200; if (milliseconds >= slow_execution_threshold_ms) LOG_TRACE(&Poco::Logger::get(log_name), "Execution took {} ms.", milliseconds); @@ -268,7 +268,8 @@ void BackgroundSchedulePool::threadFunction() std::unique_lock tasks_lock(tasks_mutex); { - tasks_cond_var.wait(tasks_lock, [&](){ + tasks_cond_var.wait(tasks_lock, [&]() + { return shutdown || !tasks.empty(); }); From bff82b961b13cda2d7be4901850f10ba081a9092 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Tue, 14 Jun 2022 21:43:17 +0200 Subject: [PATCH 098/204] Fixed tests --- src/Core/BackgroundSchedulePool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/BackgroundSchedulePool.cpp b/src/Core/BackgroundSchedulePool.cpp index e4582a733d6..6f5a5a1fa54 100644 --- a/src/Core/BackgroundSchedulePool.cpp +++ b/src/Core/BackgroundSchedulePool.cpp @@ -265,9 +265,10 @@ void BackgroundSchedulePool::threadFunction() while (!shutdown) { TaskInfoPtr task; - std::unique_lock tasks_lock(tasks_mutex); { + std::unique_lock tasks_lock(tasks_mutex); + tasks_cond_var.wait(tasks_lock, [&]() { return shutdown || !tasks.empty(); From 0317e15b3f9960f6f49b050ca84cc88d9485489d Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Tue, 14 Jun 2022 19:47:10 -0400 Subject: [PATCH 099/204] added more mergetree settings --- .../settings/merge-tree-settings.md | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/docs/en/operations/settings/merge-tree-settings.md b/docs/en/operations/settings/merge-tree-settings.md index 73d06fc82ca..2a010d71f78 100644 --- a/docs/en/operations/settings/merge-tree-settings.md +++ b/docs/en/operations/settings/merge-tree-settings.md @@ -164,6 +164,187 @@ Default value: 604800 (1 week). Similar to [replicated_deduplication_window](#replicated-deduplication-window), `replicated_deduplication_window_seconds` specifies how long to store hash sums of blocks for insert deduplication. Hash sums older than `replicated_deduplication_window_seconds` are removed from ClickHouse Keeper, even if they are less than ` replicated_deduplication_window`. +## max_replicated_logs_to_keep + +How many records may be in log, if there is inactive replica. Inactive replica becomes lost when when this number exceed. + +Possible values: + +- Any positive integer. + +Default value: 1000 + +## min_replicated_logs_to_keep + +Keep about this number of last records in ZooKeeper log, even if they are obsolete. It doesn't affect work of tables: used only to diagnose ZooKeeper log before cleaning. + +Possible values: + +- Any positive integer. + +Default value: 10 + +## prefer_fetch_merged_part_time_threshold + +If time passed after replication log entry creation exceeds this threshold and sum size of parts is greater than `prefer_fetch_merged_part_size_threshold`, prefer fetching merged part from replica instead of doing merge locally. To speed up very long merges. + +Possible values: + +- Any positive integer. + +Default value: 3600 + +## prefer_fetch_merged_part_size_threshold + +If sum size of parts exceeds this threshold and time passed after replication log entry creation is greater than `prefer_fetch_merged_part_time_threshold`, prefer fetching merged part from replica instead of doing merge locally. To speed up very long merges. + +Possible values: + +- Any positive integer. + +Default value: 10,737,418,240 + +## execute_merges_on_single_replica_time_threshold + +When greater than zero only a single replica starts the merge immediately, others wait up to that amount of time to download the result instead of doing merges locally. If the chosen replica doesn't finish the merge during that amount of time, fallback to standard behavior happens. + +Possible values: + +- Any positive integer. + +Default value: 0 + +## remote_fs_execute_merges_on_single_replica_time_threshold + +When greater than zero only a single replica starts the merge immediately if merged part on shared storage and `allow_remote_fs_zero_copy_replication` is enabled. + +Possible values: + +- Any positive integer. + +Default value: 1800 + +## try_fetch_recompressed_part_timeout + +Recompression works slow in most cases, so we don't start merge with recompression until this timeout and trying to fetch recompressed part from replica which assigned this merge with recompression. + +Possible values: + +- Any positive integer. + +Default value: 7200 + +## always_fetch_merged_part + +If true, replica never merge parts and always download merged parts from other replicas. + +Possible values: + +- true, false + +Default value: false + +## max_suspicious_broken_parts + +Max broken parts, if more - deny automatic deletion. + +Possible values: + +- Any positive integer. + +Default value: 10 + +## max_suspicious_broken_parts_bytes + + +Max size of all broken parts, if more - deny automatic deletion. + +Possible values: + +- Any positive integer. + +Default value: 1,073,741,824 + +## max_files_to_modify_in_alter_columns + +Not apply ALTER if number of files for modification(deletion, addition) more than this. + +Possible values: + +- Any positive integer. + +Default value: 75 + +## max_files_to_remove_in_alter_columns + +Not apply ALTER, if number of files for deletion more than this. + +Possible values: + +- Any positive integer. + +Default value: 50 + +## replicated_max_ratio_of_wrong_parts + +If ratio of wrong parts to total number of parts is less than this - allow to start. + +Possible values: + +- Float, 0.0 - 1.0 + +Default value: 0.5 + +## replicated_max_parallel_fetches_for_host + +Limit parallel fetches from endpoint (actually pool size). + +Possible values: + +- Any positive integer. + +Default value: 15 + +## replicated_fetches_http_connection_timeout + +HTTP connection timeout for part fetch requests. Inherited from default profile `http_connection_timeout` if not set explicitly. + +Possible values: + +- Any positive integer. + +Default value: Inherited from default profile `http_connection_timeout` if not set explicitly. + +## replicated_can_become_leader + +If true, Replicated tables replicas on this node will try to acquire leadership. + +Possible values: + +- true, false + +Default value: true + +## zookeeper_session_expiration_check_period + +ZooKeeper session expiration check period, in seconds. + +Possible values: + +- Any positive integer. + +Default value: 60 + +## detach_old_local_parts_when_cloning_replica + +Do not remove old local parts when repairing lost replica. + +Possible values: + +- true, false + +Default value: true + ## replicated_fetches_http_connection_timeout {#replicated_fetches_http_connection_timeout} HTTP connection timeout (in seconds) for part fetch requests. Inherited from default profile [http_connection_timeout](./settings.md#http_connection_timeout) if not set explicitly. From 411695bd97ee09eaeda1c6e3b1dd0f37ec4fdb15 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Tue, 14 Jun 2022 22:26:50 -0400 Subject: [PATCH 100/204] do not fill 'to' boundary --- src/Interpreters/FillingRow.cpp | 2 +- .../0_stateless/00995_order_by_with_fill.reference | 5 ----- .../0_stateless/02019_multiple_weird_with_fill.reference | 9 --------- .../0_stateless/02112_with_fill_interval.reference | 4 ---- 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/Interpreters/FillingRow.cpp b/src/Interpreters/FillingRow.cpp index bb8661d0ef9..561ac255326 100644 --- a/src/Interpreters/FillingRow.cpp +++ b/src/Interpreters/FillingRow.cpp @@ -76,7 +76,7 @@ bool FillingRow::next(const FillingRow & to_row) auto next_value = row[pos]; getFillDescription(pos).step_func(next_value); - if (less(to_row.row[pos], next_value, getDirection(pos))) + if (less(to_row.row[pos], next_value, getDirection(pos)) || equals(next_value, getFillDescription(pos).fill_to)) return false; row[pos] = next_value; diff --git a/tests/queries/0_stateless/00995_order_by_with_fill.reference b/tests/queries/0_stateless/00995_order_by_with_fill.reference index adb0e1aa2c3..0036aabda40 100644 --- a/tests/queries/0_stateless/00995_order_by_with_fill.reference +++ b/tests/queries/0_stateless/00995_order_by_with_fill.reference @@ -376,11 +376,6 @@ 2019-05-03 4 2019-05-03 1 2019-05-03 -2 -2019-05-01 10 -2019-05-01 7 -2019-05-01 4 -2019-05-01 1 -2019-05-01 -2 *** date WITH FILL TO 2019-06-23 STEP 3, val WITH FILL FROM -10 STEP 2 2019-05-07 -10 2019-05-07 -8 diff --git a/tests/queries/0_stateless/02019_multiple_weird_with_fill.reference b/tests/queries/0_stateless/02019_multiple_weird_with_fill.reference index 822d290564a..703dd6e6aac 100644 --- a/tests/queries/0_stateless/02019_multiple_weird_with_fill.reference +++ b/tests/queries/0_stateless/02019_multiple_weird_with_fill.reference @@ -34,12 +34,3 @@ 6 -4 6 -3 6 -2 -7 -10 -7 -9 -7 -8 -7 -7 -7 -6 -7 -5 -7 -4 -7 -3 -7 -2 diff --git a/tests/queries/0_stateless/02112_with_fill_interval.reference b/tests/queries/0_stateless/02112_with_fill_interval.reference index 4bb99803eb1..a1b1d3b0d4d 100644 --- a/tests/queries/0_stateless/02112_with_fill_interval.reference +++ b/tests/queries/0_stateless/02112_with_fill_interval.reference @@ -103,10 +103,6 @@ 2020-04-01 2 0 2020-04-01 3 0 2020-04-01 4 0 -2020-05-01 1 0 -2020-05-01 2 0 -2020-05-01 3 0 -2020-05-01 4 0 1970-01-04 1970-01-03 1970-01-02 From 1c841fadcd3f3ab7129f8b20a78dc72c5e2be3ca Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Wed, 15 Jun 2022 10:59:23 +0200 Subject: [PATCH 101/204] Updated libunwind --- .gitmodules | 2 +- contrib/libunwind | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0a525efed1f..55fd684fddb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -91,7 +91,7 @@ url = https://github.com/ClickHouse/hyperscan.git [submodule "contrib/libunwind"] path = contrib/libunwind - url = https://github.com/kitaisreal/libunwind.git + url = https://github.com/ClickHouse/libunwind.git [submodule "contrib/simdjson"] path = contrib/simdjson url = https://github.com/simdjson/simdjson.git diff --git a/contrib/libunwind b/contrib/libunwind index 8e21568a41f..5022f30f3e0 160000 --- a/contrib/libunwind +++ b/contrib/libunwind @@ -1 +1 @@ -Subproject commit 8e21568a41ff753c02a2a5c092b65302c4e5c6ae +Subproject commit 5022f30f3e092a54a7c101c335ce5e08769db366 From dcfe4eea3ce895b1f9a4b12f0f508d19314e984b Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Jun 2022 12:53:53 +0300 Subject: [PATCH 102/204] Update 01417_freeze_partition_verbose.sh --- tests/queries/0_stateless/01417_freeze_partition_verbose.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01417_freeze_partition_verbose.sh b/tests/queries/0_stateless/01417_freeze_partition_verbose.sh index 38f84c2fa15..1f67100a4b6 100755 --- a/tests/queries/0_stateless/01417_freeze_partition_verbose.sh +++ b/tests/queries/0_stateless/01417_freeze_partition_verbose.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-replicated-database, no-parallel +# Tags: no-replicated-database, no-parallel, no-ordinary-database # Tag no-replicated-database: Unsupported type of ALTER query CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) From 56f2121c1a57ee2069e27ebb5b153e1bdbcccd17 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Jun 2022 13:04:01 +0300 Subject: [PATCH 103/204] Revert "Add backoff to merges in replicated queue if `always_fetch_merged_part` is enabled" --- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index ebd34474066..9f679f121b8 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1256,27 +1256,6 @@ bool ReplicatedMergeTreeQueue::shouldExecuteLogEntry( { ignore_max_size = max_source_parts_size == data_settings->max_bytes_to_merge_at_max_space_in_pool; - if (data_settings->always_fetch_merged_part && entry.num_tries > 0) - { - static constexpr auto MAX_SECONDS_TO_WAIT = 300L; - static constexpr auto BACKOFF_SECONDS = 3; - - auto time_to_wait_seconds = std::min(MAX_SECONDS_TO_WAIT, entry.num_tries * BACKOFF_SECONDS); - auto time_since_last_try_seconds = std::time(nullptr) - entry.last_attempt_time; - /// Otherwise we will constantly look for part on other replicas - /// and load zookeeper too much. - if (time_to_wait_seconds > time_since_last_try_seconds) - { - out_postpone_reason = fmt::format( - "Not executing log entry ({}) to merge parts for part {} because `always_fetch_merged_part` enabled and " - " not enough time had been passed since last try, have to wait {} seconds", - entry.znode_name, entry.new_part_name, time_to_wait_seconds - time_since_last_try_seconds); - - LOG_DEBUG(log, fmt::runtime(out_postpone_reason)); - return false; - } - } - if (isTTLMergeType(entry.merge_type)) { if (merger_mutator.ttl_merges_blocker.isCancelled()) From c8afeafe0e974a0070f04a056705fabc75cc998e Mon Sep 17 00:00:00 2001 From: Nikita Taranov Date: Wed, 15 Jun 2022 12:44:20 +0200 Subject: [PATCH 104/204] More parallel execution for queries with `FINAL` (#36396) --- src/Processors/Merges/IMergingTransform.cpp | 65 ----- src/Processors/Merges/IMergingTransform.h | 10 - src/Processors/QueryPlan/IQueryPlanStep.cpp | 3 + src/Processors/QueryPlan/PartsSplitter.cpp | 274 ++++++++++++++++++ src/Processors/QueryPlan/PartsSplitter.h | 25 ++ .../QueryPlan/ReadFromMergeTree.cpp | 138 ++++----- .../Transforms/AddingSelectorTransform.cpp | 76 ----- .../Transforms/AddingSelectorTransform.h | 26 -- .../Transforms/FilterSortedStreamByRange.h | 66 +++++ src/Processors/Transforms/FilterTransform.h | 1 - src/Processors/Transforms/SelectorInfo.h | 14 - src/QueryPipeline/printPipeline.h | 5 +- tests/performance/parallel_final.xml | 5 + .../01861_explain_pipeline.reference | 17 +- .../02286_parallel_final.reference | 9 + .../0_stateless/02286_parallel_final.sh | 31 ++ 16 files changed, 485 insertions(+), 280 deletions(-) create mode 100644 src/Processors/QueryPlan/PartsSplitter.cpp create mode 100644 src/Processors/QueryPlan/PartsSplitter.h delete mode 100644 src/Processors/Transforms/AddingSelectorTransform.cpp delete mode 100644 src/Processors/Transforms/AddingSelectorTransform.h create mode 100644 src/Processors/Transforms/FilterSortedStreamByRange.h delete mode 100644 src/Processors/Transforms/SelectorInfo.h create mode 100644 tests/queries/0_stateless/02286_parallel_final.reference create mode 100755 tests/queries/0_stateless/02286_parallel_final.sh diff --git a/src/Processors/Merges/IMergingTransform.cpp b/src/Processors/Merges/IMergingTransform.cpp index f09c7c5339f..226f55b3e92 100644 --- a/src/Processors/Merges/IMergingTransform.cpp +++ b/src/Processors/Merges/IMergingTransform.cpp @@ -1,5 +1,4 @@ #include -#include namespace DB { @@ -181,68 +180,4 @@ IProcessor::Status IMergingTransformBase::prepare() return Status::Ready; } -static void filterChunk(IMergingAlgorithm::Input & input, size_t selector_position) -{ - if (!input.chunk.getChunkInfo()) - throw Exception("IMergingTransformBase expected ChunkInfo for input chunk", ErrorCodes::LOGICAL_ERROR); - - const auto * chunk_info = typeid_cast(input.chunk.getChunkInfo().get()); - if (!chunk_info) - throw Exception("IMergingTransformBase expected SelectorInfo for input chunk", ErrorCodes::LOGICAL_ERROR); - - const auto & selector = chunk_info->selector; - - IColumn::Filter filter; - filter.resize_fill(selector.size()); - - size_t num_rows = input.chunk.getNumRows(); - auto columns = input.chunk.detachColumns(); - - size_t num_result_rows = 0; - - for (size_t row = 0; row < num_rows; ++row) - { - if (selector[row] == selector_position) - { - ++num_result_rows; - filter[row] = 1; - } - } - - if (!filter.empty() && filter.back() == 0) - { - filter.back() = 1; - ++num_result_rows; - input.skip_last_row = true; - } - - for (auto & column : columns) - column = column->filter(filter, num_result_rows); - - input.chunk.clear(); - input.chunk.setColumns(std::move(columns), num_result_rows); -} - -void IMergingTransformBase::filterChunks() -{ - if (state.selector_position < 0) - return; - - if (!state.init_chunks.empty()) - { - for (size_t i = 0; i < input_states.size(); ++i) - { - auto & input = state.init_chunks[i]; - if (!input.chunk) - continue; - - filterChunk(input, state.selector_position); - } - } - - if (state.has_input) - filterChunk(state.input_chunk, state.selector_position); -} - - } diff --git a/src/Processors/Merges/IMergingTransform.h b/src/Processors/Merges/IMergingTransform.h index ea6f6aed37f..144c47c96f5 100644 --- a/src/Processors/Merges/IMergingTransform.h +++ b/src/Processors/Merges/IMergingTransform.h @@ -28,17 +28,10 @@ public: Status prepare() override; - /// Set position which will be used in selector if input chunk has attached SelectorInfo (see SelectorInfo.h). - /// Columns will be filtered, keep only rows labeled with this position. - /// It is used in parallel final. - void setSelectorPosition(size_t position) { state.selector_position = position; } - protected: virtual void onNewInput(); /// Is called when new input is added. Only if have_all_inputs = false. virtual void onFinish() {} /// Is called when all data is processed. - void filterChunks(); /// Filter chunks if selector position was set. For parallel final. - /// Processor state. struct State { @@ -50,7 +43,6 @@ protected: size_t next_input_to_read = 0; IMergingAlgorithm::Inputs init_chunks; - ssize_t selector_position = -1; }; State state; @@ -92,8 +84,6 @@ public: void work() override { - filterChunks(); - if (!state.init_chunks.empty()) algorithm.initialize(std::move(state.init_chunks)); diff --git a/src/Processors/QueryPlan/IQueryPlanStep.cpp b/src/Processors/QueryPlan/IQueryPlanStep.cpp index f06897e8488..b36d1f0e12f 100644 --- a/src/Processors/QueryPlan/IQueryPlanStep.cpp +++ b/src/Processors/QueryPlan/IQueryPlanStep.cpp @@ -86,6 +86,9 @@ static void doDescribeProcessor(const IProcessor & processor, size_t count, IQue doDescribeHeader(*last_header, num_equal_headers, settings); } + if (!processor.getDescription().empty()) + settings.out << String(settings.offset, settings.indent_char) << "Description: " << processor.getDescription() << '\n'; + settings.offset += settings.indent; } diff --git a/src/Processors/QueryPlan/PartsSplitter.cpp b/src/Processors/QueryPlan/PartsSplitter.cpp new file mode 100644 index 00000000000..25574c6dcc5 --- /dev/null +++ b/src/Processors/QueryPlan/PartsSplitter.cpp @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace DB; + +namespace +{ + +using Value = std::vector; + +std::string toString(const Value & value) +{ + return fmt::format("({})", fmt::join(value, ", ")); +} + +/// Adaptor to access PK values from index. +class IndexAccess +{ +public: + explicit IndexAccess(const RangesInDataParts & parts_) : parts(parts_) { } + + Value getValue(size_t part_idx, size_t mark) const + { + const auto & index = parts[part_idx].data_part->index; + Value value(index.size()); + for (size_t i = 0; i < value.size(); ++i) + index[i]->get(mark, value[i]); + return value; + } + + size_t getMarkRows(size_t part_idx, size_t mark) const { return parts[part_idx].data_part->index_granularity.getMarkRows(mark); } + + size_t getTotalRowCount() const + { + size_t total = 0; + for (const auto & part : parts) + total += part.getRowsCount(); + return total; + } + +private: + const RangesInDataParts & parts; +}; + + +/// Splits parts into layers, each layer will contain parts subranges with PK values from its own range. +/// Will try to produce exactly max_layer layers but may return less if data is distributed in not a very parallelizable way. +std::pair, std::vector> split(RangesInDataParts parts, size_t max_layers) +{ + // We will advance the iterator pointing to the mark with the smallest PK value until there will be not less than rows_per_layer rows in the current layer (roughly speaking). + // Then we choose the last observed value as the new border, so the current layer will consists of granules with values greater than the previous mark and less or equal + // than the new border. + + struct PartsRangesIterator + { + struct RangeInDataPart : MarkRange + { + size_t part_idx; + }; + + enum class EventType + { + RangeBeginning, + RangeEnding, + }; + + bool operator<(const PartsRangesIterator & other) const { return std::tie(value, event) > std::tie(other.value, other.event); } + + Value value; + RangeInDataPart range; + EventType event; + }; + + const auto index_access = std::make_unique(parts); + std::priority_queue parts_ranges_queue; + for (size_t part_idx = 0; part_idx < parts.size(); ++part_idx) + { + for (const auto & range : parts[part_idx].ranges) + { + parts_ranges_queue.push( + {index_access->getValue(part_idx, range.begin), {range, part_idx}, PartsRangesIterator::EventType::RangeBeginning}); + const auto & index_granularity = parts[part_idx].data_part->index_granularity; + if (index_granularity.hasFinalMark() && range.end + 1 == index_granularity.getMarksCount()) + parts_ranges_queue.push( + {index_access->getValue(part_idx, range.end), {range, part_idx}, PartsRangesIterator::EventType::RangeEnding}); + } + } + + /// The beginning of currently started (but not yet finished) range of marks of a part in the current layer. + std::unordered_map current_part_range_begin; + /// The current ending of a range of marks of a part in the current layer. + std::unordered_map current_part_range_end; + + /// Determine borders between layers. + std::vector borders; + std::vector result_layers; + + const size_t rows_per_layer = std::max(index_access->getTotalRowCount() / max_layers, 1); + + while (!parts_ranges_queue.empty()) + { + // New layer should include last granules of still open ranges from the previous layer, + // because they may already contain values greater than the last border. + size_t rows_in_current_layer = 0; + size_t marks_in_current_layer = 0; + + // Intersection between the current and next layers is just the last observed marks of each still open part range. Ratio is empirical. + auto layers_intersection_is_too_big = [&]() + { + const auto intersected_parts = current_part_range_end.size(); + return marks_in_current_layer < intersected_parts * 2; + }; + + result_layers.emplace_back(); + + while (rows_in_current_layer < rows_per_layer || layers_intersection_is_too_big() || result_layers.size() == max_layers) + { + // We're advancing iterators until a new value showed up. + Value last_value; + while (!parts_ranges_queue.empty() && (last_value.empty() || last_value == parts_ranges_queue.top().value)) + { + auto current = parts_ranges_queue.top(); + parts_ranges_queue.pop(); + const auto part_idx = current.range.part_idx; + + if (current.event == PartsRangesIterator::EventType::RangeEnding) + { + result_layers.back().emplace_back( + parts[part_idx].data_part, + parts[part_idx].part_index_in_query, + MarkRanges{{current_part_range_begin[part_idx], current.range.end}}); + current_part_range_begin.erase(part_idx); + current_part_range_end.erase(part_idx); + continue; + } + + last_value = std::move(current.value); + rows_in_current_layer += index_access->getMarkRows(part_idx, current.range.begin); + marks_in_current_layer++; + current_part_range_begin.try_emplace(part_idx, current.range.begin); + current_part_range_end[part_idx] = current.range.begin; + if (current.range.begin + 1 < current.range.end) + { + current.range.begin++; + current.value = index_access->getValue(part_idx, current.range.begin); + parts_ranges_queue.push(std::move(current)); + } + } + if (parts_ranges_queue.empty()) + break; + if (rows_in_current_layer >= rows_per_layer && !layers_intersection_is_too_big() && result_layers.size() < max_layers) + borders.push_back(last_value); + } + for (const auto & [part_idx, last_mark] : current_part_range_end) + { + result_layers.back().emplace_back( + parts[part_idx].data_part, + parts[part_idx].part_index_in_query, + MarkRanges{{current_part_range_begin[part_idx], last_mark + 1}}); + current_part_range_begin[part_idx] = current_part_range_end[part_idx]; + } + } + for (auto & layer : result_layers) + { + std::stable_sort( + layer.begin(), + layer.end(), + [](const auto & lhs, const auto & rhs) { return lhs.part_index_in_query < rhs.part_index_in_query; }); + } + + return std::make_pair(std::move(borders), std::move(result_layers)); +} + + +/// Will return borders.size()+1 filters in total, i-th filter will accept rows with PK values within the range [borders[i-1], borders[i]). +std::vector buildFilters(const KeyDescription & primary_key, const std::vector & borders) +{ + auto add_and_condition = [&](ASTPtr & result, const ASTPtr & foo) { result = !result ? foo : makeASTFunction("and", result, foo); }; + + /// Produces ASTPtr to predicate (pk_col0, pk_col1, ... , pk_colN) > (value[0], value[1], ... , value[N]) + auto lexicographically_greater = [&](const Value & value) + { + // PK may contain functions of the table columns, so we need the actual PK AST with all expressions it contains. + ASTPtr pk_columns_as_tuple = makeASTFunction("tuple", primary_key.expression_list_ast->children); + + ASTPtr value_ast = std::make_shared(); + for (size_t i = 0; i < value.size(); ++i) + { + const auto & types = primary_key.data_types; + ASTPtr component_ast = std::make_shared(value[i]); + // Values of some types (e.g. Date, DateTime) are stored in columns as numbers and we get them as just numbers from the index. + // So we need an explicit Cast for them. + if (isColumnedAsNumber(types.at(i)->getTypeId()) && !isNumber(types.at(i)->getTypeId())) + component_ast = makeASTFunction("cast", std::move(component_ast), std::make_shared(types.at(i)->getName())); + value_ast->children.push_back(std::move(component_ast)); + } + ASTPtr value_as_tuple = makeASTFunction("tuple", value_ast->children); + + return makeASTFunction("greater", pk_columns_as_tuple, value_as_tuple); + }; + + std::vector filters(borders.size() + 1); + for (size_t layer = 0; layer <= borders.size(); ++layer) + { + if (layer > 0) + add_and_condition(filters[layer], lexicographically_greater(borders[layer - 1])); + if (layer < borders.size()) + add_and_condition(filters[layer], makeASTFunction("not", lexicographically_greater(borders[layer]))); + } + return filters; +} +} + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +Pipes buildPipesForReadingByPKRanges( + const KeyDescription & primary_key, + RangesInDataParts parts, + size_t max_layers, + ContextPtr context, + ReadingInOrderStepGetter && reading_step_getter) +{ + if (max_layers <= 1) + throw Exception(ErrorCodes::LOGICAL_ERROR, "max_layer should be greater than 1."); + + auto && [borders, result_layers] = split(std::move(parts), max_layers); + auto filters = buildFilters(primary_key, borders); + Pipes pipes(result_layers.size()); + for (size_t i = 0; i < result_layers.size(); ++i) + { + pipes[i] = reading_step_getter(std::move(result_layers[i])); + auto & filter_function = filters[i]; + if (!filter_function) + continue; + auto syntax_result = TreeRewriter(context).analyze(filter_function, primary_key.expression->getRequiredColumnsWithTypes()); + auto actions = ExpressionAnalyzer(filter_function, syntax_result, context).getActionsDAG(false); + ExpressionActionsPtr expression_actions = std::make_shared(std::move(actions)); + auto description = fmt::format( + "filter values in [{}, {})", i ? ::toString(borders[i - 1]) : "-inf", i < borders.size() ? ::toString(borders[i]) : "+inf"); + auto pk_expression = std::make_shared(primary_key.expression->getActionsDAG().clone()); + pipes[i].addSimpleTransform([pk_expression](const Block & header) + { return std::make_shared(header, pk_expression); }); + pipes[i].addSimpleTransform( + [&](const Block & header) + { + auto step = std::make_shared(header, expression_actions, filter_function->getColumnName(), true); + step->setDescription(description); + return step; + }); + } + return pipes; +} + +} diff --git a/src/Processors/QueryPlan/PartsSplitter.h b/src/Processors/QueryPlan/PartsSplitter.h new file mode 100644 index 00000000000..56bca688c2d --- /dev/null +++ b/src/Processors/QueryPlan/PartsSplitter.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include +#include +#include +#include + + +namespace DB +{ + +using ReadingInOrderStepGetter = std::function; + +/// Splits parts into layers, each layer will contain parts subranges with PK values from its own range. +/// A separate pipe will be constructed for each layer with a reading step (provided by the reading_step_getter) and a filter for this layer's range of PK values. +/// Will try to produce exactly max_layer pipes but may return less if data is distributed in not a very parallelizable way. +Pipes buildPipesForReadingByPKRanges( + const KeyDescription & primary_key, + RangesInDataParts parts, + size_t max_layers, + ContextPtr context, + ReadingInOrderStepGetter && reading_step_getter); +} diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 8adaf2f1027..1caee41160f 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -1,14 +1,16 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include #include #include #include @@ -16,17 +18,22 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include -#include -#include #include -#include -#include -#include #include #include +#include #include namespace ProfileEvents @@ -560,7 +567,6 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsWithOrder( static void addMergingFinal( Pipe & pipe, - size_t num_output_streams, const SortDescription & sort_description, MergeTreeData::MergingParams merging_params, Names partition_key_columns, @@ -607,56 +613,7 @@ static void addMergingFinal( __builtin_unreachable(); }; - if (num_output_streams <= 1 || sort_description.empty()) - { - pipe.addTransform(get_merging_processor()); - return; - } - - ColumnNumbers key_columns; - key_columns.reserve(sort_description.size()); - for (const auto & desc : sort_description) - key_columns.push_back(header.getPositionByName(desc.column_name)); - - pipe.addSimpleTransform([&](const Block & stream_header) - { - return std::make_shared(stream_header, num_output_streams, key_columns); - }); - - pipe.transform([&](OutputPortRawPtrs ports) - { - Processors transforms; - std::vector output_ports; - transforms.reserve(ports.size() + num_output_streams); - output_ports.reserve(ports.size()); - - for (auto & port : ports) - { - auto copier = std::make_shared(header, num_output_streams); - connect(*port, copier->getInputPort()); - output_ports.emplace_back(copier->getOutputs().begin()); - transforms.emplace_back(std::move(copier)); - } - - for (size_t i = 0; i < num_output_streams; ++i) - { - auto merge = get_merging_processor(); - merge->setSelectorPosition(i); - auto input = merge->getInputs().begin(); - - /// Connect i-th merge with i-th input port of every copier. - for (size_t j = 0; j < ports.size(); ++j) - { - connect(*output_ports[j], *input); - ++output_ports[j]; - ++input; - } - - transforms.emplace_back(std::move(merge)); - } - - return transforms; - }); + pipe.addTransform(get_merging_processor()); } @@ -710,8 +667,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( for (size_t range_index = 0; range_index < parts_to_merge_ranges.size() - 1; ++range_index) { - Pipe pipe; - + Pipes pipes; { RangesInDataParts new_parts; @@ -738,21 +694,39 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( if (new_parts.empty()) continue; - pipe = read(std::move(new_parts), column_names, ReadFromMergeTree::ReadType::InOrder, - num_streams, 0, info.use_uncompressed_cache); + if (num_streams > 1 && metadata_for_reading->hasPrimaryKey()) + { + // Let's split parts into layers to ensure data parallelism of final. + auto reading_step_getter = [this, &column_names, &info](auto parts) + { + return read( + std::move(parts), + column_names, + ReadFromMergeTree::ReadType::InOrder, + 1 /* num_streams */, + 0 /* min_marks_for_concurrent_read */, + info.use_uncompressed_cache); + }; + pipes = buildPipesForReadingByPKRanges( + metadata_for_reading->getPrimaryKey(), std::move(new_parts), num_streams, context, std::move(reading_step_getter)); + } + else + { + pipes.emplace_back(read( + std::move(new_parts), column_names, ReadFromMergeTree::ReadType::InOrder, num_streams, 0, info.use_uncompressed_cache)); + } /// Drop temporary columns, added by 'sorting_key_expr' if (!out_projection) - out_projection = createProjection(pipe.getHeader()); + out_projection = createProjection(pipes.front().getHeader()); } auto sorting_expr = std::make_shared( metadata_for_reading->getSortingKey().expression->getActionsDAG().clone()); - pipe.addSimpleTransform([sorting_expr](const Block & header) - { - return std::make_shared(header, sorting_expr); - }); + for (auto & pipe : pipes) + pipe.addSimpleTransform([sorting_expr](const Block & header) + { return std::make_shared(header, sorting_expr); }); /// If do_not_merge_across_partitions_select_final is true and there is only one part in partition /// with level > 0 then we won't postprocess this part @@ -760,7 +734,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( std::distance(parts_to_merge_ranges[range_index], parts_to_merge_ranges[range_index + 1]) == 1 && parts_to_merge_ranges[range_index]->data_part->info.level > 0) { - partition_pipes.emplace_back(std::move(pipe)); + partition_pipes.emplace_back(Pipe::unitePipes(std::move(pipes))); continue; } @@ -777,21 +751,21 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( for (size_t i = 0; i < sort_columns_size; ++i) sort_description.emplace_back(sort_columns[i], 1, 1); - addMergingFinal( - pipe, - std::min(num_streams, settings.max_final_threads), - sort_description, data.merging_params, partition_key_columns, max_block_size); + for (auto & pipe : pipes) + addMergingFinal( + pipe, + sort_description, + data.merging_params, + partition_key_columns, + max_block_size); - partition_pipes.emplace_back(std::move(pipe)); + partition_pipes.emplace_back(Pipe::unitePipes(std::move(pipes))); } if (!lonely_parts.empty()) { - RangesInDataParts new_parts; - size_t num_streams_for_lonely_parts = num_streams * lonely_parts.size(); - const size_t min_marks_for_concurrent_read = MergeTreeDataSelectExecutor::minMarksForConcurrentRead( settings.merge_tree_min_rows_for_concurrent_read, settings.merge_tree_min_bytes_for_concurrent_read, diff --git a/src/Processors/Transforms/AddingSelectorTransform.cpp b/src/Processors/Transforms/AddingSelectorTransform.cpp deleted file mode 100644 index f75a5920072..00000000000 --- a/src/Processors/Transforms/AddingSelectorTransform.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - -AddingSelectorTransform::AddingSelectorTransform( - const Block & header, size_t num_outputs_, ColumnNumbers key_columns_) - : ISimpleTransform(header, header, false) - , num_outputs(num_outputs_) - , key_columns(std::move(key_columns_)) - , hash(0) -{ - setInputNotNeededAfterRead(false); - - if (num_outputs <= 1) - throw Exception("SplittingByHashTransform expects more than 1 outputs, got " + std::to_string(num_outputs), - ErrorCodes::LOGICAL_ERROR); - - if (key_columns.empty()) - throw Exception("SplittingByHashTransform cannot split by empty set of key columns", - ErrorCodes::LOGICAL_ERROR); - - for (auto & column : key_columns) - if (column >= header.columns()) - throw Exception("Invalid column number: " + std::to_string(column) + - ". There is only " + std::to_string(header.columns()) + " columns in header", - ErrorCodes::LOGICAL_ERROR); -} - -static void calculateWeakHash32(const Chunk & chunk, const ColumnNumbers & key_columns, WeakHash32 & hash) -{ - auto num_rows = chunk.getNumRows(); - const auto & columns = chunk.getColumns(); - - hash.reset(num_rows); - - for (const auto & column_number : key_columns) - columns[column_number]->updateWeakHash32(hash); -} - -static IColumn::Selector fillSelector(const WeakHash32 & hash, size_t num_outputs) -{ - /// Row from interval [(2^32 / num_outputs) * i, (2^32 / num_outputs) * (i + 1)) goes to bucket with number i. - - const auto & hash_data = hash.getData(); - size_t num_rows = hash_data.size(); - IColumn::Selector selector(num_rows); - - for (size_t row = 0; row < num_rows; ++row) - { - selector[row] = hash_data[row]; /// [0, 2^32) - selector[row] *= num_outputs; /// [0, num_outputs * 2^32), selector stores 64 bit values. - selector[row] >>= 32u; /// [0, num_outputs) - } - - return selector; -} - -void AddingSelectorTransform::transform(Chunk & input_chunk, Chunk & output_chunk) -{ - auto chunk_info = std::make_shared(); - - calculateWeakHash32(input_chunk, key_columns, hash); - chunk_info->selector = fillSelector(hash, num_outputs); - - input_chunk.swap(output_chunk); - output_chunk.setChunkInfo(std::move(chunk_info)); -} - -} diff --git a/src/Processors/Transforms/AddingSelectorTransform.h b/src/Processors/Transforms/AddingSelectorTransform.h deleted file mode 100644 index bad97adfa76..00000000000 --- a/src/Processors/Transforms/AddingSelectorTransform.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include -#include -#include -#include - -namespace DB -{ - -/// Add IColumn::Selector to chunk (see SelectorInfo.h). -/// Selector is filled by formula (WeakHash(key_columns) * num_outputs / MAX_INT). -class AddingSelectorTransform : public ISimpleTransform -{ -public: - AddingSelectorTransform(const Block & header, size_t num_outputs_, ColumnNumbers key_columns_); - String getName() const override { return "AddingSelector"; } - void transform(Chunk & input_chunk, Chunk & output_chunk) override; - -private: - size_t num_outputs; - ColumnNumbers key_columns; - - WeakHash32 hash; -}; - -} diff --git a/src/Processors/Transforms/FilterSortedStreamByRange.h b/src/Processors/Transforms/FilterSortedStreamByRange.h new file mode 100644 index 00000000000..adbc0626abb --- /dev/null +++ b/src/Processors/Transforms/FilterSortedStreamByRange.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include + +namespace DB +{ + +/// Could be used when the predicate given by expression_ is true only on one continuous range of values and input is monotonous by that value. +/// The following optimization applies: when a new chunk of data comes in we firstly execute the expression_ only on the first and the last row. +/// If it evaluates to true on both rows then the whole chunk is immediately passed to further steps. +/// Otherwise, we apply the expression_ to all rows. +class FilterSortedStreamByRange : public ISimpleTransform +{ +public: + FilterSortedStreamByRange( + const Block & header_, + ExpressionActionsPtr expression_, + String filter_column_name_, + bool remove_filter_column_, + bool on_totals_ = false) + : ISimpleTransform( + header_, + FilterTransform::transformHeader(header_, expression_->getActionsDAG(), filter_column_name_, remove_filter_column_), + true) + , filter_transform(header_, expression_, filter_column_name_, remove_filter_column_, on_totals_) + { + } + + String getName() const override { return "FilterSortedStreamByRange"; } + + void transform(Chunk & chunk) override + { + int rows_before_filtration = chunk.getNumRows(); + if (rows_before_filtration < 2) + { + filter_transform.transform(chunk); + return; + } + + // Evaluate expression on just the first and the last row. + // If both of them satisfies conditions, than skip calculation for all the rows in between. + auto quick_check_columns = chunk.cloneEmptyColumns(); + auto src_columns = chunk.detachColumns(); + for (auto row : {0, rows_before_filtration - 1}) + for (size_t col = 0; col < quick_check_columns.size(); ++col) + quick_check_columns[col]->insertFrom(*src_columns[col].get(), row); + chunk.setColumns(std::move(quick_check_columns), 2); + filter_transform.transform(chunk); + const bool all_rows_will_pass_filter = chunk.getNumRows() == 2; + + chunk.setColumns(std::move(src_columns), rows_before_filtration); + + // Not all rows satisfy conditions. + if (!all_rows_will_pass_filter) + filter_transform.transform(chunk); + } + +private: + FilterTransform filter_transform; +}; + + +} diff --git a/src/Processors/Transforms/FilterTransform.h b/src/Processors/Transforms/FilterTransform.h index 39f1f1c42db..3340fe230b7 100644 --- a/src/Processors/Transforms/FilterTransform.h +++ b/src/Processors/Transforms/FilterTransform.h @@ -32,7 +32,6 @@ public: Status prepare() override; -protected: void transform(Chunk & chunk) override; private: diff --git a/src/Processors/Transforms/SelectorInfo.h b/src/Processors/Transforms/SelectorInfo.h deleted file mode 100644 index 2876d64ed28..00000000000 --- a/src/Processors/Transforms/SelectorInfo.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include -#include - -namespace DB -{ - -/// ChunkInfo with IColumn::Selector. It is added by AddingSelectorTransform. -struct SelectorInfo : public ChunkInfo -{ - IColumn::Selector selector; -}; - -} diff --git a/src/QueryPipeline/printPipeline.h b/src/QueryPipeline/printPipeline.h index 6ff5fb24c37..4b947149c7c 100644 --- a/src/QueryPipeline/printPipeline.h +++ b/src/QueryPipeline/printPipeline.h @@ -28,7 +28,10 @@ void printPipeline(const Processors & processors, const Statuses & statuses, Wri /// Nodes // TODO quoting and escaping for (const auto & processor : processors) { - out << " n" << get_proc_id(*processor) << "[label=\"" << processor->getName() << processor->getDescription(); + auto description = processor->getDescription(); + if (!description.empty()) + description = ": " + description; + out << " n" << get_proc_id(*processor) << "[label=\"" << processor->getName() << description; if (statuses_iter != statuses.end()) { diff --git a/tests/performance/parallel_final.xml b/tests/performance/parallel_final.xml index 775926d1ee8..ca84ed52a04 100644 --- a/tests/performance/parallel_final.xml +++ b/tests/performance/parallel_final.xml @@ -18,6 +18,7 @@ collapsing_final_16p_str_keys_rnd collapsing_final_1024p_ord collapsing_final_1024p_rnd + collapsing_final_1p_ord
@@ -30,6 +31,7 @@ create table collapsing_final_16p_str_keys_rnd (key1 UInt32, key2 String, key3 String, key4 String, key5 String, key6 String, key7 String, key8 String, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1, key2, key3, key4, key5, key6, key7, key8) partition by key1 % 16 create table collapsing_final_1024p_ord (key1 UInt32, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1) partition by intDiv(key1, 8192 * 2) create table collapsing_final_1024p_rnd (key1 UInt32, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1) partition by key1 % 1024 + create table collapsing_final_1p_ord (key1 UInt64, key2 UInt64, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1, key2) insert into collapsing_final_16p_ord select number, number, 1, number from numbers_mt(8388608) @@ -43,6 +45,9 @@ insert into collapsing_final_1024p_ord select number, 1, number from numbers_mt(16777216) insert into collapsing_final_1024p_rnd select number, 1, number from numbers_mt(16777216) + + insert into collapsing_final_1p_ord select number, number + 1, 1, number from numbers_mt(5e7) + optimize table {collapsing} final SELECT count() FROM {collapsing} final diff --git a/tests/queries/0_stateless/01861_explain_pipeline.reference b/tests/queries/0_stateless/01861_explain_pipeline.reference index 2ba294d7e4d..aec3ae06dce 100644 --- a/tests/queries/0_stateless/01861_explain_pipeline.reference +++ b/tests/queries/0_stateless/01861_explain_pipeline.reference @@ -16,8 +16,15 @@ ExpressionTransform ExpressionTransform × 2 (ReadFromMergeTree) ExpressionTransform × 2 - ReplacingSorted × 2 2 → 1 - Copy × 2 1 → 2 - AddingSelector × 2 - ExpressionTransform × 2 - MergeTreeInOrder × 2 0 → 1 + ReplacingSorted + ExpressionTransform + FilterSortedStreamByRange + Description: filter values in [(5), +inf) + ExpressionTransform + MergeTreeInOrder 0 → 1 + ReplacingSorted 2 → 1 + ExpressionTransform × 2 + FilterSortedStreamByRange × 2 + Description: filter values in [-inf, (5)) + ExpressionTransform × 2 + MergeTreeInOrder × 2 0 → 1 diff --git a/tests/queries/0_stateless/02286_parallel_final.reference b/tests/queries/0_stateless/02286_parallel_final.reference new file mode 100644 index 00000000000..f6573cb9042 --- /dev/null +++ b/tests/queries/0_stateless/02286_parallel_final.reference @@ -0,0 +1,9 @@ +2 +2 +3 +5 +8 +8 +8 +8 +8 diff --git a/tests/queries/0_stateless/02286_parallel_final.sh b/tests/queries/0_stateless/02286_parallel_final.sh new file mode 100755 index 00000000000..6686a5d3e33 --- /dev/null +++ b/tests/queries/0_stateless/02286_parallel_final.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +test_random_values() { + layers=$1 + $CLICKHOUSE_CLIENT -n -q " + create table tbl_8parts_${layers}granules_rnd (key1 UInt32, sign Int8) engine = CollapsingMergeTree(sign) order by (key1) partition by (key1 % 8); + insert into tbl_8parts_${layers}granules_rnd select number, 1 from numbers_mt($((layers * 8 * 8192))); + explain pipeline select * from tbl_8parts_${layers}granules_rnd final settings max_threads = 16;" 2>&1 | + grep -c "CollapsingSortedTransform" +} + +for layers in 2 3 5 8; do + test_random_values $layers +done; + +test_sequential_values() { + layers=$1 + $CLICKHOUSE_CLIENT -n -q " + create table tbl_8parts_${layers}granules_seq (key1 UInt32, sign Int8) engine = CollapsingMergeTree(sign) order by (key1) partition by (key1 / $((layers * 8192)))::UInt64; + insert into tbl_8parts_${layers}granules_seq select number, 1 from numbers_mt($((layers * 8 * 8192))); + explain pipeline select * from tbl_8parts_${layers}granules_seq final settings max_threads = 8;" 2>&1 | + grep -c "CollapsingSortedTransform" +} + +for layers in 2 3 5 8 16; do + test_sequential_values $layers +done; From 5d4e96cc30477e7f2da75c142af430c4d689e433 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 15 Jun 2022 13:17:45 +0200 Subject: [PATCH 105/204] Change order of uploaded artifacts to rebuild failed binaries --- tests/ci/build_check.py | 131 +++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/tests/ci/build_check.py b/tests/ci/build_check.py index 9730ac2cc46..3976e2ba916 100644 --- a/tests/ci/build_check.py +++ b/tests/ci/build_check.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# + import subprocess import logging import json @@ -7,9 +7,16 @@ import os import sys import time from shutil import rmtree -from typing import List, Optional, Tuple +from typing import List, Tuple -from env_helper import GITHUB_JOB, REPO_COPY, TEMP_PATH, CACHES_PATH, IMAGES_PATH +from env_helper import ( + CACHES_PATH, + GITHUB_JOB, + IMAGES_PATH, + REPO_COPY, + S3_BUILDS_BUCKET, + TEMP_PATH, +) from s3_helper import S3Helper from pr_info import PRInfo from version_helper import ( @@ -24,6 +31,7 @@ from docker_pull_helper import get_image_with_version from tee_popen import TeePopen IMAGE_NAME = "clickhouse/binary-builder" +BUILD_LOG_NAME = "build_log.log" def _can_export_binaries(build_config: BuildConfig) -> bool: @@ -86,7 +94,7 @@ def get_packager_cmd( def build_clickhouse( packager_cmd: str, logs_path: str, build_output_path: str ) -> Tuple[str, bool]: - build_log_path = os.path.join(logs_path, "build_log.log") + build_log_path = os.path.join(logs_path, BUILD_LOG_NAME) success = False with TeePopen(packager_cmd, build_log_path) as process: retcode = process.wait() @@ -108,15 +116,56 @@ def build_clickhouse( return build_log_path, success -def get_build_results_if_exists( - s3_helper: S3Helper, s3_prefix: str -) -> Optional[List[str]]: +def check_for_success_run( + s3_helper: S3Helper, + s3_prefix: str, + build_name: str, + build_config: BuildConfig, +): + logged_prefix = os.path.join(S3_BUILDS_BUCKET, s3_prefix) + logging.info("Checking for artifacts in %s", logged_prefix) try: - content = s3_helper.list_prefix(s3_prefix) - return content + # TODO: theoretically, it would miss performance artifact for pr==0, + # but luckily we rerun only really failed tasks now, so we're safe + build_results = s3_helper.list_prefix(s3_prefix) except Exception as ex: - logging.info("Got exception %s listing %s", ex, s3_prefix) - return None + logging.info("Got exception while listing %s: %s\nRerun", logged_prefix, ex) + return + + if build_results is None or len(build_results) == 0: + logging.info("Nothing found in %s, rerun", logged_prefix) + return + + logging.info("Some build results found:\n%s", build_results) + build_urls = [] + log_url = "" + for url in build_results: + url_escaped = url.replace("+", "%2B").replace(" ", "%20") + if BUILD_LOG_NAME in url: + log_url = f"https://s3.amazonaws.com/{S3_BUILDS_BUCKET}/{url_escaped}" + else: + build_urls.append( + f"https://s3.amazonaws.com/{S3_BUILDS_BUCKET}/{url_escaped}" + ) + if not log_url: + # log is uploaded the last, so if there's no log we need to rerun the build + return + + success = len(build_urls) > 0 + create_json_artifact( + TEMP_PATH, + build_name, + log_url, + build_urls, + build_config, + 0, + success, + ) + # Fail build job if not successeded + if not success: + sys.exit(1) + else: + sys.exit(0) def create_json_artifact( @@ -213,37 +262,8 @@ def main(): ) # If this is rerun, then we try to find already created artifacts and just - # put them as github actions artifcat (result) - build_results = get_build_results_if_exists(s3_helper, s3_path_prefix) - if build_results is not None and len(build_results) > 0: - logging.info("Some build results found %s", build_results) - build_urls = [] - log_url = "" - for url in build_results: - if "build_log.log" in url: - log_url = "https://s3.amazonaws.com/clickhouse-builds/" + url.replace( - "+", "%2B" - ).replace(" ", "%20") - else: - build_urls.append( - "https://s3.amazonaws.com/clickhouse-builds/" - + url.replace("+", "%2B").replace(" ", "%20") - ) - success = len(build_urls) > 0 - create_json_artifact( - TEMP_PATH, - build_name, - log_url, - build_urls, - build_config, - 0, - success, - ) - # Fail build job if not successeded - if not success: - sys.exit(1) - else: - sys.exit(0) + # put them as github actions artifact (result) + check_for_success_run(s3_helper, s3_path_prefix, build_name, build_config) docker_image = get_image_with_version(IMAGES_PATH, IMAGE_NAME) image_version = docker_image.version @@ -295,14 +315,12 @@ def main(): logging.info("Going to run packager with %s", packager_cmd) - build_clickhouse_log = os.path.join(TEMP_PATH, "build_log") - if not os.path.exists(build_clickhouse_log): - os.makedirs(build_clickhouse_log) + logs_path = os.path.join(TEMP_PATH, "build_log") + if not os.path.exists(logs_path): + os.makedirs(logs_path) start = time.time() - log_path, success = build_clickhouse( - packager_cmd, build_clickhouse_log, build_output_path - ) + log_path, success = build_clickhouse(packager_cmd, logs_path, build_output_path) elapsed = int(time.time() - start) subprocess.check_call( f"sudo chown -R ubuntu:ubuntu {build_output_path}", shell=True @@ -310,17 +328,10 @@ def main(): subprocess.check_call(f"sudo chown -R ubuntu:ubuntu {ccache_path}", shell=True) logging.info("Build finished with %s, log path %s", success, log_path) + # Upload the ccache first to have the least build time in case of problems logging.info("Will upload cache") upload_ccache(ccache_path, s3_helper, pr_info.number, TEMP_PATH) - if os.path.exists(log_path): - log_url = s3_helper.upload_build_file_to_s3( - log_path, s3_path_prefix + "/" + os.path.basename(log_path) - ) - logging.info("Log url %s", log_url) - else: - logging.info("Build log doesn't exist") - # FIXME performance performance_urls = [] performance_path = os.path.join(build_output_path, "performance.tgz") @@ -347,6 +358,14 @@ def main(): print("::notice ::Build URLs: {}".format("\n".join(build_urls))) + if os.path.exists(log_path): + log_url = s3_helper.upload_build_file_to_s3( + log_path, s3_path_prefix + "/" + os.path.basename(log_path) + ) + logging.info("Log url %s", log_url) + else: + logging.info("Build log doesn't exist") + print(f"::notice ::Log URL: {log_url}") create_json_artifact( From fe2231dbdb025970018991eb0ec706a1d5067798 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 15 Jun 2022 13:18:26 +0200 Subject: [PATCH 106/204] Upload static binaries from deb builds with lto=thin --- tests/ci/ci_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index 3b61e2077cf..a530b395130 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -12,6 +12,7 @@ CI_CONFIG = { "build_type": "", "sanitizer": "", "package_type": "deb", + "static_binary_name": "amd64", "bundled": "bundled", "splitted": "unsplitted", "additional_pkgs": True, @@ -34,6 +35,7 @@ CI_CONFIG = { "build_type": "", "sanitizer": "", "package_type": "deb", + "static_binary_name": "aarch64", "bundled": "bundled", "splitted": "unsplitted", "additional_pkgs": True, @@ -95,7 +97,6 @@ CI_CONFIG = { "build_type": "", "sanitizer": "", "package_type": "binary", - "static_binary_name": "amd64", "bundled": "bundled", "splitted": "unsplitted", "tidy": "disable", @@ -138,7 +139,6 @@ CI_CONFIG = { "build_type": "", "sanitizer": "", "package_type": "binary", - "static_binary_name": "aarch64", "bundled": "bundled", "splitted": "unsplitted", "tidy": "disable", From 50748a94bdc508590c9222831486f63d5fef7907 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 15 Jun 2022 13:36:45 +0200 Subject: [PATCH 107/204] Fix a compile errors in self-extracting-executable (de)compressor E.g. utils/self-extracting-executable/compressor.cpp:257:31: format specifies type 'ptrdiff_t' (aka 'long') but the argument has type 'off_t' (aka 'long long') [-Werror,-Wformat] printf("Size: %td\n", info_in.st_size); ~~~ ^~~~~~~~~~~~~~~ %lld Not sure though if it's a hard requirement to use only C. Avoided usage of fmt::format() to keep link dependencies to a minimum. Also not using C++20 std::format() as it's only available in Clang >=14. --- utils/self-extracting-executable/compressor.cpp | 17 +++++++++-------- .../self-extracting-executable/decompressor.cpp | 12 ++++++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/utils/self-extracting-executable/compressor.cpp b/utils/self-extracting-executable/compressor.cpp index 5478dfcb419..7a4ee46d5cc 100644 --- a/utils/self-extracting-executable/compressor.cpp +++ b/utils/self-extracting-executable/compressor.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "types.h" @@ -54,7 +55,7 @@ int doCompress(char * input, char * output, off_t & in_offset, off_t & out_offse size_t compressed_size = ZSTD_compress2(cctx, output + out_offset, output_size, input + in_offset, input_size); if (ZSTD_isError(compressed_size)) { - fprintf(stderr, "Error (ZSTD): %zu %s\n", compressed_size, ZSTD_getErrorName(compressed_size)); + std::cerr << "Error (ZSTD): " << compressed_size << " " << ZSTD_getErrorName(compressed_size) << std::endl; return 1; } in_offset += input_size; @@ -79,7 +80,7 @@ int compress(int in_fd, int out_fd, int level, off_t & pointer, const struct sta ZSTD_CCtx * cctx = ZSTD_createCCtx(); if (cctx == nullptr) { - fprintf(stderr, "Error (ZSTD): failed to create compression context\n"); + std::cerr << "Error (ZSTD): failed to create compression context" << std::endl; return 1; } @@ -89,13 +90,13 @@ int compress(int in_fd, int out_fd, int level, off_t & pointer, const struct sta check_result = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, level); if (ZSTD_isError(check_result)) { - fprintf(stderr, "Error (ZSTD): %zu %s\n", check_result, ZSTD_getErrorName(check_result)); + std::cerr << "Error (ZSTD): " << check_result << " " << ZSTD_getErrorName(check_result) << std::endl; return 1; } check_result = ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1); if (ZSTD_isError(check_result)) { - fprintf(stderr, "Error (ZSTD): %zu %s\n", check_result, ZSTD_getErrorName(check_result)); + std::cerr << "Error (ZSTD): " << check_result << " " << ZSTD_getErrorName(check_result) << std::endl; return 1; } @@ -254,7 +255,7 @@ int compressFiles(char* filenames[], int count, int output_fd, int level, const continue; } - printf("Size: %td\n", info_in.st_size); + std::cout << "Size: " << info_in.st_size << std::endl; /// Save umask files_data[i].umask = info_in.st_mode; @@ -317,7 +318,7 @@ int copy_decompressor(const char *self, int output_fd) if (sz < 0) perror(nullptr); else - fprintf(stderr, "Error: unable to extract decompressor.\n"); + std::cerr << "Error unable to extract decompressor" << std::endl; close(input_fd); return 1; } @@ -398,7 +399,7 @@ int main(int argc, char* argv[]) struct stat info_out; if (stat(argv[start_of_files], &info_out) != -1 || errno != ENOENT) { - fprintf(stderr, "Error: output file [%s] already exists.\n", argv[start_of_files]); + std::cerr << "Error: output file [" << argv[start_of_files] << "] already exists" << std::endl; return 1; } @@ -419,7 +420,7 @@ int main(int argc, char* argv[]) return 1; } - printf("Compression with level %d\n", level); + std::cout << "Compression with level: " << level << std::endl; if (0 != compressFiles(&argv[start_of_files], argc - start_of_files, output_fd, level, info_out)) { printf("Compression failed.\n"); diff --git a/utils/self-extracting-executable/decompressor.cpp b/utils/self-extracting-executable/decompressor.cpp index e10d1413cd0..d5c9cf278de 100644 --- a/utils/self-extracting-executable/decompressor.cpp +++ b/utils/self-extracting-executable/decompressor.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "types.h" @@ -21,7 +22,7 @@ int doDecompress(char * input, char * output, off_t & in_offset, off_t & out_off size_t decompressed_size = ZSTD_decompressDCtx(dctx, output + out_offset, output_size, input + in_offset, input_size); if (ZSTD_isError(decompressed_size)) { - fprintf(stderr, "Error (ZSTD): %zu %s\n", decompressed_size, ZSTD_getErrorName(decompressed_size)); + std::cerr << "Error (ZSTD):" << decompressed_size << " " << ZSTD_getErrorName(decompressed_size) << std::endl; return 1; } return 0; @@ -40,7 +41,7 @@ int decompress(char * input, char * output, off_t start, off_t end, size_t max_n ZSTD_DCtx * dctx = ZSTD_createDCtx(); if (dctx == nullptr) { - fprintf(stderr, "Error (ZSTD): failed to create decompression context\n"); + std::cerr << "Error (ZSTD): failed to create decompression context" << std::endl; return 1; } pid_t pid; @@ -52,7 +53,7 @@ int decompress(char * input, char * output, off_t start, off_t end, size_t max_n size = ZSTD_findFrameCompressedSize(input + in_pointer, max_block_size); if (ZSTD_isError(size)) { - fprintf(stderr, "Error (ZSTD): %td %s\n", size, ZSTD_getErrorName(size)); + std::cerr << "Error (ZSTD): " << size << " " << ZSTD_getErrorName(size) << std::endl; error_happened = true; break; } @@ -60,7 +61,7 @@ int decompress(char * input, char * output, off_t start, off_t end, size_t max_n decompressed_size = ZSTD_getFrameContentSize(input + in_pointer, max_block_size); if (ZSTD_isError(decompressed_size)) { - fprintf(stderr, "Error (ZSTD): %td %s\n", decompressed_size, ZSTD_getErrorName(decompressed_size)); + std::cerr << "Error (ZSTD): " << decompressed_size << " " << ZSTD_getErrorName(decompressed_size) << std::endl; error_happened = true; break; } @@ -171,8 +172,7 @@ int decompressFiles(int input_fd, char * path, char * name, bool & have_compress } if (fs_info.f_blocks * info_in.st_blksize < decompressed_full_size) { - fprintf(stderr, "Not enough space for decompression. Have %tu, need %zu.", - fs_info.f_blocks * info_in.st_blksize, decompressed_full_size); + std::cerr << "Not enough space for decompression. Have " << fs_info.f_blocks * info_in.st_blksize << ", need " << decompressed_full_size << std::endl; return 1; } From bf6d1551702d0e9ea08ed61e0f5b61d24672816d Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Wed, 15 Jun 2022 14:11:10 +0200 Subject: [PATCH 108/204] Aggregate functions added restrict into batch methods --- .../AggregateFunctionAggThrow.cpp | 10 ++++----- src/AggregateFunctions/AggregateFunctionAvg.h | 4 ++-- ...egateFunctionCategoricalInformationValue.h | 10 ++++----- .../AggregateFunctionCount.h | 6 ++--- .../AggregateFunctionIf.cpp | 2 +- src/AggregateFunctions/AggregateFunctionIf.h | 6 ++--- .../AggregateFunctionMLMethod.h | 2 +- src/AggregateFunctions/AggregateFunctionMap.h | 10 ++++----- .../AggregateFunctionNothing.h | 12 +++++----- .../AggregateFunctionNull.h | 2 +- .../AggregateFunctionOrFill.h | 14 ++++++------ .../AggregateFunctionResample.h | 10 ++++----- .../AggregateFunctionSequenceNextNode.h | 6 ++--- .../AggregateFunctionSimpleLinearRegression.h | 10 ++++----- .../AggregateFunctionSparkbar.h | 2 +- src/AggregateFunctions/AggregateFunctionSum.h | 4 ++-- src/AggregateFunctions/IAggregateFunction.h | 22 +++++++++---------- 17 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/AggregateFunctions/AggregateFunctionAggThrow.cpp b/src/AggregateFunctions/AggregateFunctionAggThrow.cpp index 8eb25953dbd..e74f93cd9b0 100644 --- a/src/AggregateFunctions/AggregateFunctionAggThrow.cpp +++ b/src/AggregateFunctions/AggregateFunctionAggThrow.cpp @@ -76,27 +76,27 @@ public: data(place).~Data(); } - void add(AggregateDataPtr, const IColumn **, size_t, Arena *) const override + void add(AggregateDataPtr __restrict, const IColumn **, size_t, Arena *) const override { } - void merge(AggregateDataPtr, ConstAggregateDataPtr, Arena *) const override + void merge(AggregateDataPtr __restrict, ConstAggregateDataPtr, Arena *) const override { } - void serialize(ConstAggregateDataPtr, WriteBuffer & buf, std::optional /* version */) const override + void serialize(ConstAggregateDataPtr __restrict, WriteBuffer & buf, std::optional /* version */) const override { char c = 0; buf.write(c); } - void deserialize(AggregateDataPtr /* place */, ReadBuffer & buf, std::optional /* version */, Arena *) const override + void deserialize(AggregateDataPtr __restrict /* place */, ReadBuffer & buf, std::optional /* version */, Arena *) const override { char c = 0; buf.read(c); } - void insertResultInto(AggregateDataPtr, IColumn & to, Arena *) const override + void insertResultInto(AggregateDataPtr __restrict, IColumn & to, Arena *) const override { to.insertDefault(); } diff --git a/src/AggregateFunctions/AggregateFunctionAvg.h b/src/AggregateFunctions/AggregateFunctionAvg.h index cb2eaa12310..c41a51997df 100644 --- a/src/AggregateFunctions/AggregateFunctionAvg.h +++ b/src/AggregateFunctions/AggregateFunctionAvg.h @@ -236,7 +236,7 @@ public: void addBatchSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena *, ssize_t if_argument_pos) const final @@ -260,7 +260,7 @@ public: void addBatchSinglePlaceNotNull( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, const UInt8 * null_map, Arena *, diff --git a/src/AggregateFunctions/AggregateFunctionCategoricalInformationValue.h b/src/AggregateFunctions/AggregateFunctionCategoricalInformationValue.h index cb945cc6ed9..0e0db27cf22 100644 --- a/src/AggregateFunctions/AggregateFunctionCategoricalInformationValue.h +++ b/src/AggregateFunctions/AggregateFunctionCategoricalInformationValue.h @@ -41,7 +41,7 @@ public: memset(place, 0, sizeOfData()); } - void destroy(AggregateDataPtr) const noexcept override + void destroy(AggregateDataPtr __restrict) const noexcept override { // nothing } @@ -61,7 +61,7 @@ public: return alignof(T); } - void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena *) const override + void add(AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena *) const override { const auto * y_col = static_cast(columns[category_count]); bool y = y_col->getData()[row_num]; @@ -78,7 +78,7 @@ public: reinterpret_cast(place)[category_count * 2 + size_t(y)] += 1; } - void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena *) const override + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena *) const override { for (size_t i : collections::range(0, category_count + 1)) { @@ -87,12 +87,12 @@ public: } } - void serialize(ConstAggregateDataPtr place, WriteBuffer & buf, std::optional /* version */) const override + void serialize(ConstAggregateDataPtr __restrict place, WriteBuffer & buf, std::optional /* version */) const override { buf.write(place, sizeOfData()); } - void deserialize(AggregateDataPtr place, ReadBuffer & buf, std::optional /* version */, Arena *) const override + void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional /* version */, Arena *) const override { buf.read(place, sizeOfData()); } diff --git a/src/AggregateFunctions/AggregateFunctionCount.h b/src/AggregateFunctions/AggregateFunctionCount.h index 446179d1957..4199cb55409 100644 --- a/src/AggregateFunctions/AggregateFunctionCount.h +++ b/src/AggregateFunctions/AggregateFunctionCount.h @@ -65,7 +65,7 @@ public: void addBatchSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena *, ssize_t if_argument_pos) const override @@ -84,7 +84,7 @@ public: void addBatchSinglePlaceNotNull( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, const UInt8 * null_map, Arena *, @@ -222,7 +222,7 @@ public: void addBatchSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena *, ssize_t if_argument_pos) const override diff --git a/src/AggregateFunctions/AggregateFunctionIf.cpp b/src/AggregateFunctions/AggregateFunctionIf.cpp index 8663df1e902..fa5e6b85a1e 100644 --- a/src/AggregateFunctions/AggregateFunctionIf.cpp +++ b/src/AggregateFunctions/AggregateFunctionIf.cpp @@ -122,7 +122,7 @@ public: void addBatchSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t) const override diff --git a/src/AggregateFunctions/AggregateFunctionIf.h b/src/AggregateFunctions/AggregateFunctionIf.h index 9a17c567db6..f3221ae66e9 100644 --- a/src/AggregateFunctions/AggregateFunctionIf.h +++ b/src/AggregateFunctions/AggregateFunctionIf.h @@ -100,7 +100,7 @@ public: void addBatch( size_t row_begin, size_t row_end, - AggregateDataPtr * places, + AggregateDataPtr * __restrict places, size_t place_offset, const IColumn ** columns, Arena * arena, @@ -112,7 +112,7 @@ public: void addBatchSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t) const override @@ -123,7 +123,7 @@ public: void addBatchSinglePlaceNotNull( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, const UInt8 * null_map, Arena * arena, diff --git a/src/AggregateFunctions/AggregateFunctionMLMethod.h b/src/AggregateFunctions/AggregateFunctionMLMethod.h index 705e689d347..b9d5d835f57 100644 --- a/src/AggregateFunctions/AggregateFunctionMLMethod.h +++ b/src/AggregateFunctions/AggregateFunctionMLMethod.h @@ -362,7 +362,7 @@ public: void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional /* version */, Arena *) const override { this->data(place).read(buf); } void predictValues( - ConstAggregateDataPtr place, + ConstAggregateDataPtr __restrict place, IColumn & to, const ColumnsWithTypeAndName & arguments, size_t offset, diff --git a/src/AggregateFunctions/AggregateFunctionMap.h b/src/AggregateFunctions/AggregateFunctionMap.h index 3aa0b26cae9..8d77e22300b 100644 --- a/src/AggregateFunctions/AggregateFunctionMap.h +++ b/src/AggregateFunctions/AggregateFunctionMap.h @@ -105,7 +105,7 @@ public: DataTypePtr getReturnType() const override { return std::make_shared(DataTypes{key_type, nested_func->getReturnType()}); } - void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena) const override + void add(AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * arena) const override { const auto & map_column = assert_cast(*columns[0]); const auto & map_nested_tuple = map_column.getNestedData(); @@ -160,7 +160,7 @@ public: } } - void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena * arena) const override + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena * arena) const override { auto & merged_maps = this->data(place).merged_maps; const auto & rhs_maps = this->data(rhs).merged_maps; @@ -178,7 +178,7 @@ public: } } - void serialize(ConstAggregateDataPtr place, WriteBuffer & buf, std::optional /* version */) const override + void serialize(ConstAggregateDataPtr __restrict place, WriteBuffer & buf, std::optional /* version */) const override { auto & merged_maps = this->data(place).merged_maps; writeVarUInt(merged_maps.size(), buf); @@ -190,7 +190,7 @@ public: } } - void deserialize(AggregateDataPtr place, ReadBuffer & buf, std::optional /* version */, Arena * arena) const override + void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional /* version */, Arena * arena) const override { auto & merged_maps = this->data(place).merged_maps; UInt64 size; @@ -209,7 +209,7 @@ public: } } - void insertResultInto(AggregateDataPtr place, IColumn & to, Arena * arena) const override + void insertResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena * arena) const override { auto & map_column = assert_cast(to); auto & nested_column = map_column.getNestedColumn(); diff --git a/src/AggregateFunctions/AggregateFunctionNothing.h b/src/AggregateFunctions/AggregateFunctionNothing.h index 645ea7c3f8a..13ef407be8b 100644 --- a/src/AggregateFunctions/AggregateFunctionNothing.h +++ b/src/AggregateFunctions/AggregateFunctionNothing.h @@ -33,11 +33,11 @@ public: bool allocatesMemoryInArena() const override { return false; } - void create(AggregateDataPtr) const override + void create(AggregateDataPtr __restrict) const override { } - void destroy(AggregateDataPtr) const noexcept override + void destroy(AggregateDataPtr __restrict) const noexcept override { } @@ -56,11 +56,11 @@ public: return 1; } - void add(AggregateDataPtr, const IColumn **, size_t, Arena *) const override + void add(AggregateDataPtr __restrict, const IColumn **, size_t, Arena *) const override { } - void merge(AggregateDataPtr, ConstAggregateDataPtr, Arena *) const override + void merge(AggregateDataPtr __restrict, ConstAggregateDataPtr, Arena *) const override { } @@ -69,14 +69,14 @@ public: writeChar('\0', buf); } - void deserialize(AggregateDataPtr, ReadBuffer & buf, std::optional, Arena *) const override + void deserialize(AggregateDataPtr __restrict, ReadBuffer & buf, std::optional, Arena *) const override { [[maybe_unused]] char symbol; readChar(symbol, buf); assert(symbol == '\0'); } - void insertResultInto(AggregateDataPtr, IColumn & to, Arena *) const override + void insertResultInto(AggregateDataPtr __restrict, IColumn & to, Arena *) const override { to.insertDefault(); } diff --git a/src/AggregateFunctions/AggregateFunctionNull.h b/src/AggregateFunctions/AggregateFunctionNull.h index 3a9330edce1..ca284680800 100644 --- a/src/AggregateFunctions/AggregateFunctionNull.h +++ b/src/AggregateFunctions/AggregateFunctionNull.h @@ -309,7 +309,7 @@ public: void addBatchSinglePlace( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t if_argument_pos = -1) const override diff --git a/src/AggregateFunctions/AggregateFunctionOrFill.h b/src/AggregateFunctions/AggregateFunctionOrFill.h index 73f70074e53..4eca1b4df92 100644 --- a/src/AggregateFunctions/AggregateFunctionOrFill.h +++ b/src/AggregateFunctions/AggregateFunctionOrFill.h @@ -99,7 +99,7 @@ public: } void add( - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * arena) const override @@ -138,7 +138,7 @@ public: void addBatchSinglePlace( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t if_argument_pos = -1) const override @@ -169,7 +169,7 @@ public: void addBatchSinglePlaceNotNull( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, const UInt8 * null_map, Arena * arena, @@ -206,7 +206,7 @@ public: } void merge( - AggregateDataPtr place, + AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena * arena) const override { @@ -227,14 +227,14 @@ public: (places[i] + place_offset)[size_of_data] |= rhs[i][size_of_data]; } - void serialize(ConstAggregateDataPtr place, WriteBuffer & buf, std::optional version) const override + void serialize(ConstAggregateDataPtr __restrict place, WriteBuffer & buf, std::optional version) const override { nested_function->serialize(place, buf, version); writeChar(place[size_of_data], buf); } - void deserialize(AggregateDataPtr place, ReadBuffer & buf, std::optional version, Arena * arena) const override + void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional version, Arena * arena) const override { nested_function->deserialize(place, buf, version, arena); @@ -261,7 +261,7 @@ public: } void insertResultInto( - AggregateDataPtr place, + AggregateDataPtr __restrict place, IColumn & to, Arena * arena) const override { diff --git a/src/AggregateFunctions/AggregateFunctionResample.h b/src/AggregateFunctions/AggregateFunctionResample.h index 8f2138eb3a5..5d7bf95ceee 100644 --- a/src/AggregateFunctions/AggregateFunctionResample.h +++ b/src/AggregateFunctions/AggregateFunctionResample.h @@ -134,7 +134,7 @@ public: nested_function->destroy(place + i * size_of_data); } - void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena) const override + void add(AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * arena) const override { Key key; @@ -151,19 +151,19 @@ public: nested_function->add(place + pos * size_of_data, columns, row_num, arena); } - void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena * arena) const override + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena * arena) const override { for (size_t i = 0; i < total; ++i) nested_function->merge(place + i * size_of_data, rhs + i * size_of_data, arena); } - void serialize(ConstAggregateDataPtr place, WriteBuffer & buf, std::optional version) const override + void serialize(ConstAggregateDataPtr __restrict place, WriteBuffer & buf, std::optional version) const override { for (size_t i = 0; i < total; ++i) nested_function->serialize(place + i * size_of_data, buf, version); } - void deserialize(AggregateDataPtr place, ReadBuffer & buf, std::optional version, Arena * arena) const override + void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional version, Arena * arena) const override { for (size_t i = 0; i < total; ++i) nested_function->deserialize(place + i * size_of_data, buf, version, arena); @@ -174,7 +174,7 @@ public: return std::make_shared(nested_function->getReturnType()); } - void insertResultInto(AggregateDataPtr place, IColumn & to, Arena * arena) const override + void insertResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena * arena) const override { auto & col = assert_cast(to); auto & col_offsets = assert_cast(col.getOffsetsColumn()); diff --git a/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h b/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h index f6c921aeef9..a4222b5471f 100644 --- a/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h +++ b/src/AggregateFunctions/AggregateFunctionSequenceNextNode.h @@ -158,8 +158,8 @@ class SequenceNextNodeImpl final using Self = SequenceNextNodeImpl; using Data = SequenceNextNodeGeneralData; - static Data & data(AggregateDataPtr place) { return *reinterpret_cast(place); } - static const Data & data(ConstAggregateDataPtr place) { return *reinterpret_cast(place); } + static Data & data(AggregateDataPtr __restrict place) { return *reinterpret_cast(place); } + static const Data & data(ConstAggregateDataPtr __restrict place) { return *reinterpret_cast(place); } static constexpr size_t base_cond_column_idx = 2; static constexpr size_t event_column_idx = 1; @@ -216,7 +216,7 @@ public: a.value.push_back(v->clone(arena), arena); } - void create(AggregateDataPtr place) const override /// NOLINT + void create(AggregateDataPtr __restrict place) const override /// NOLINT { new (place) Data; } diff --git a/src/AggregateFunctions/AggregateFunctionSimpleLinearRegression.h b/src/AggregateFunctions/AggregateFunctionSimpleLinearRegression.h index 333b847ea67..06cdfc5e582 100644 --- a/src/AggregateFunctions/AggregateFunctionSimpleLinearRegression.h +++ b/src/AggregateFunctions/AggregateFunctionSimpleLinearRegression.h @@ -110,7 +110,7 @@ public: } void add( - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * @@ -125,17 +125,17 @@ public: this->data(place).add(x, y); } - void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs, Arena *) const override + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena *) const override { this->data(place).merge(this->data(rhs)); } - void serialize(ConstAggregateDataPtr place, WriteBuffer & buf, std::optional /* version */) const override + void serialize(ConstAggregateDataPtr __restrict place, WriteBuffer & buf, std::optional /* version */) const override { this->data(place).serialize(buf); } - void deserialize(AggregateDataPtr place, ReadBuffer & buf, std::optional /* version */, Arena *) const override + void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional /* version */, Arena *) const override { this->data(place).deserialize(buf); } @@ -163,7 +163,7 @@ public: bool allocatesMemoryInArena() const override { return false; } void insertResultInto( - AggregateDataPtr place, + AggregateDataPtr __restrict place, IColumn & to, Arena *) const override { diff --git a/src/AggregateFunctions/AggregateFunctionSparkbar.h b/src/AggregateFunctions/AggregateFunctionSparkbar.h index de271181f2c..70066012c00 100644 --- a/src/AggregateFunctions/AggregateFunctionSparkbar.h +++ b/src/AggregateFunctions/AggregateFunctionSparkbar.h @@ -298,7 +298,7 @@ public: } } - void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena * /*arena*/) const override + void merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr __restrict rhs, Arena * /*arena*/) const override { this->data(place).merge(this->data(rhs)); } diff --git a/src/AggregateFunctions/AggregateFunctionSum.h b/src/AggregateFunctions/AggregateFunctionSum.h index 672e9dad9e5..0f2357d64a8 100644 --- a/src/AggregateFunctions/AggregateFunctionSum.h +++ b/src/AggregateFunctions/AggregateFunctionSum.h @@ -445,7 +445,7 @@ public: void addBatchSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena *, ssize_t if_argument_pos) const override @@ -465,7 +465,7 @@ public: void addBatchSinglePlaceNotNull( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, const UInt8 * null_map, Arena *, diff --git a/src/AggregateFunctions/IAggregateFunction.h b/src/AggregateFunctions/IAggregateFunction.h index 73484efc080..cdb3b2f32b7 100644 --- a/src/AggregateFunctions/IAggregateFunction.h +++ b/src/AggregateFunctions/IAggregateFunction.h @@ -150,7 +150,7 @@ public: /// Used for machine learning methods. Predict result from trained model. /// Will insert result into `to` column for rows in range [offset, offset + limit). virtual void predictValues( - ConstAggregateDataPtr /* place */, + ConstAggregateDataPtr __restrict /* place */, IColumn & /*to*/, const ColumnsWithTypeAndName & /*arguments*/, size_t /*offset*/, @@ -209,7 +209,7 @@ public: virtual void addBatchSinglePlace( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t if_argument_pos = -1) const = 0; @@ -218,7 +218,7 @@ public: virtual void addBatchSparseSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena) const = 0; @@ -228,7 +228,7 @@ public: virtual void addBatchSinglePlaceNotNull( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, const UInt8 * null_map, Arena * arena, @@ -237,7 +237,7 @@ public: virtual void addBatchSinglePlaceFromInterval( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t if_argument_pos = -1) @@ -370,7 +370,7 @@ template class IAggregateFunctionHelper : public IAggregateFunction { private: - static void addFree(const IAggregateFunction * that, AggregateDataPtr place, const IColumn ** columns, size_t row_num, Arena * arena) + static void addFree(const IAggregateFunction * that, AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * arena) { static_cast(*that).add(place, columns, row_num, arena); } @@ -450,7 +450,7 @@ public: void addBatchSinglePlace( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t if_argument_pos = -1) const override @@ -474,7 +474,7 @@ public: void addBatchSparseSinglePlace( size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena) const override { @@ -493,7 +493,7 @@ public: void addBatchSinglePlaceNotNull( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, const UInt8 * null_map, Arena * arena, @@ -517,7 +517,7 @@ public: void addBatchSinglePlaceFromInterval( /// NOLINT size_t row_begin, size_t row_end, - AggregateDataPtr place, + AggregateDataPtr __restrict place, const IColumn ** columns, Arena * arena, ssize_t if_argument_pos = -1) @@ -661,7 +661,7 @@ public: IAggregateFunctionDataHelper(const DataTypes & argument_types_, const Array & parameters_) : IAggregateFunctionHelper(argument_types_, parameters_) {} - void create(AggregateDataPtr place) const override /// NOLINT + void create(AggregateDataPtr __restrict place) const override /// NOLINT { new (place) Data; } From a0d936cc9ff1c8748ca5b57ce8b8b289310ecc93 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 15 Jun 2022 12:41:20 +0200 Subject: [PATCH 109/204] Small follow-up for FPC codec - add paper reference + doxygen - remove endianness handling (ClickHouse assumes little endian) - documentation - other minor stuff --- .../sql-reference/statements/create/table.md | 1 + src/Compression/CompressionCodecFPC.cpp | 106 +++++++----------- tests/performance/codecs_float_select.xml | 6 +- 3 files changed, 49 insertions(+), 64 deletions(-) diff --git a/docs/en/sql-reference/statements/create/table.md b/docs/en/sql-reference/statements/create/table.md index d8bd741c2cf..bdf6c02c737 100644 --- a/docs/en/sql-reference/statements/create/table.md +++ b/docs/en/sql-reference/statements/create/table.md @@ -248,6 +248,7 @@ Specialized codecs: - `Delta(delta_bytes)` — Compression approach in which raw values are replaced by the difference of two neighboring values, except for the first value that stays unchanged. Up to `delta_bytes` are used for storing delta values, so `delta_bytes` is the maximum size of raw values. Possible `delta_bytes` values: 1, 2, 4, 8. The default value for `delta_bytes` is `sizeof(type)` if equal to 1, 2, 4, or 8. In all other cases, it’s 1. - `DoubleDelta` — Calculates delta of deltas and writes it in compact binary form. Optimal compression rates are achieved for monotonic sequences with a constant stride, such as time series data. Can be used with any fixed-width type. Implements the algorithm used in Gorilla TSDB, extending it to support 64-bit types. Uses 1 extra bit for 32-byte deltas: 5-bit prefixes instead of 4-bit prefixes. For additional information, see Compressing Time Stamps in [Gorilla: A Fast, Scalable, In-Memory Time Series Database](http://www.vldb.org/pvldb/vol8/p1816-teller.pdf). - `Gorilla` — Calculates XOR between current and previous value and writes it in compact binary form. Efficient when storing a series of floating point values that change slowly, because the best compression rate is achieved when neighboring values are binary equal. Implements the algorithm used in Gorilla TSDB, extending it to support 64-bit types. For additional information, see Compressing Values in [Gorilla: A Fast, Scalable, In-Memory Time Series Database](http://www.vldb.org/pvldb/vol8/p1816-teller.pdf). +- `FPC` - Repeatedly predicts the next floating point value in the sequence using the better of two predictors, then XORs the actual with the predicted value, and leading-zero compresses the result. Similar to Gorilla, this is efficient when storing a series of floating point values that change slowly. For 64-bit values (double), FPC is faster than Gorilla, for 32-bit values your mileage may vary. For a detailed description of the algorithm see [High Throughput Compression of Double-Precision Floating-Point Data](https://userweb.cs.txstate.edu/~burtscher/papers/dcc07a.pdf). - `T64` — Compression approach that crops unused high bits of values in integer data types (including `Enum`, `Date` and `DateTime`). At each step of its algorithm, codec takes a block of 64 values, puts them into 64x64 bit matrix, transposes it, crops the unused bits of values and returns the rest as a sequence. Unused bits are the bits, that do not differ between maximum and minimum values in the whole data part for which the compression is used. `DoubleDelta` and `Gorilla` codecs are used in Gorilla TSDB as the components of its compressing algorithm. Gorilla approach is effective in scenarios when there is a sequence of slowly changing values with their timestamps. Timestamps are effectively compressed by the `DoubleDelta` codec, and values are effectively compressed by the `Gorilla` codec. For example, to get an effectively stored table, you can create it in the following configuration: diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index 3b66060b6fc..28a0b1b0299 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -14,6 +14,10 @@ namespace DB { +/// An implementation of the FPC codec for floating-point values described in the paper +/// M. Burtscher, P. Ratanaworabhan: "FPC: A high-speed compressor for double-precision floating-point data" (2008). +/// Note: The paper only describes compression of 64-bit doubles and leaves 32-bit floats to future work. The code +/// implements them anyways. Your mileage with respect to performance and compression may vary. class CompressionCodecFPC : public ICompressionCodec { public: @@ -23,8 +27,8 @@ public: void updateHash(SipHash & hash) const override; - static constexpr UInt8 MAX_COMPRESSION_LEVEL{28}; - static constexpr UInt8 DEFAULT_COMPRESSION_LEVEL{12}; + static constexpr UInt8 MAX_COMPRESSION_LEVEL = 28; + static constexpr UInt8 DEFAULT_COMPRESSION_LEVEL = 12; protected: UInt32 doCompressData(const char * source, UInt32 source_size, char * dest) const override; @@ -37,10 +41,11 @@ protected: bool isGenericCompression() const override { return false; } private: - static constexpr UInt32 HEADER_SIZE{3}; + static constexpr UInt32 HEADER_SIZE = 3; - UInt8 float_width; // size of uncompressed float in bytes - UInt8 level; // compression level, 2^level * float_width is the size of predictors table in bytes + // below members are used by compression, decompression ignores them: + const UInt8 float_width; // size of uncompressed float in bytes + const UInt8 level; // compression level, 2^level * float_width is the size of predictors table in bytes }; @@ -96,30 +101,6 @@ UInt8 getFloatBytesSize(const IDataType & column_type) column_type.getName()); } -std::byte encodeEndianness(std::endian endian) -{ - switch (endian) - { - case std::endian::little: - return std::byte{0}; - case std::endian::big: - return std::byte{1}; - } - throw Exception("Unsupported endianness", ErrorCodes::BAD_ARGUMENTS); -} - -std::endian decodeEndianness(std::byte endian) -{ - switch (std::to_integer(endian)) - { - case 0: - return std::endian::little; - case 1: - return std::endian::big; - } - throw Exception("Unsupported endianness", ErrorCodes::BAD_ARGUMENTS); -} - } void registerCodecFPC(CompressionCodecFactory & factory) @@ -127,7 +108,7 @@ void registerCodecFPC(CompressionCodecFactory & factory) auto method_code = static_cast(CompressionMethodByte::FPC); auto codec_builder = [&](const ASTPtr & arguments, const IDataType * column_type) -> CompressionCodecPtr { - UInt8 float_width{0}; + UInt8 float_width = 0; if (column_type != nullptr) float_width = getFloatBytesSize(*column_type); @@ -145,10 +126,8 @@ void registerCodecFPC(CompressionCodecFactory & factory) throw Exception("FPC codec argument must be integer", ErrorCodes::ILLEGAL_CODEC_PARAMETER); level = literal->value.safeGet(); - if (level == 0) - throw Exception("FPC codec level must be at least 1", ErrorCodes::ILLEGAL_CODEC_PARAMETER); - if (level > CompressionCodecFPC::MAX_COMPRESSION_LEVEL) - throw Exception("FPC codec level must be at most 28", ErrorCodes::ILLEGAL_CODEC_PARAMETER); + if (level < 1 || level > CompressionCodecFPC::MAX_COMPRESSION_LEVEL) + throw Exception(ErrorCodes::ILLEGAL_CODEC_PARAMETER, "FPC codec level must be between {} and {}", 1, static_cast(CompressionCodecFPC::MAX_COMPRESSION_LEVEL)); } return std::make_shared(float_width, level); }; @@ -159,11 +138,12 @@ namespace { template - requires (sizeof(TUint) >= 4) +requires (sizeof(TUint) >= 4) class DfcmPredictor { public: - explicit DfcmPredictor(std::size_t table_size): table(table_size, 0), prev_value{0}, hash{0} + explicit DfcmPredictor(std::size_t table_size) + : table(table_size, 0), prev_value{0}, hash{0} { } @@ -200,11 +180,12 @@ private: }; template - requires (sizeof(TUint) >= 4) +requires (sizeof(TUint) >= 4) class FcmPredictor { public: - explicit FcmPredictor(std::size_t table_size): table(table_size, 0), hash{0} + explicit FcmPredictor(std::size_t table_size) + : table(table_size, 0), hash{0} { } @@ -238,18 +219,17 @@ private: std::size_t hash; }; -template - requires (Endian == std::endian::little || Endian == std::endian::big) +template class FPCOperation { - static constexpr std::size_t CHUNK_SIZE{64}; - static constexpr auto VALUE_SIZE = sizeof(TUint); static constexpr std::byte FCM_BIT{0}; static constexpr std::byte DFCM_BIT{1u << 3}; static constexpr auto DFCM_BIT_1 = DFCM_BIT << 4; static constexpr auto DFCM_BIT_2 = DFCM_BIT; - static constexpr unsigned MAX_ZERO_BYTE_COUNT{0b111u}; + static constexpr unsigned MAX_ZERO_BYTE_COUNT = 0b111u; + static constexpr std::endian ENDIAN = std::endian::little; + static constexpr std::size_t CHUNK_SIZE = 64; public: FPCOperation(std::span destination, UInt8 compression_level) @@ -264,8 +244,8 @@ public: std::span chunk_view(chunk); for (std::size_t i = 0; i < data.size(); i += chunk_view.size_bytes()) { - auto written_values = importChunk(data.subspan(i), chunk_view); - encodeChunk(chunk_view.subspan(0, written_values)); + auto written_values_count = importChunk(data.subspan(i), chunk_view); + encodeChunk(chunk_view.subspan(0, written_values_count)); } return initial_size - result.size(); @@ -273,7 +253,7 @@ public: void decode(std::span values, std::size_t decoded_size) && { - std::size_t read_bytes{0}; + std::size_t read_bytes = 0; std::span chunk_view(chunk); for (std::size_t i = 0; i < decoded_size; i += chunk_view.size_bytes()) @@ -329,14 +309,14 @@ private: std::byte predictor; }; - unsigned encodeCompressedZeroByteCount(int compressed) + unsigned encodeCompressedZeroByteCount(unsigned compressed) { if constexpr (VALUE_SIZE == MAX_ZERO_BYTE_COUNT + 1) { if (compressed >= 4) --compressed; } - return std::min(static_cast(compressed), MAX_ZERO_BYTE_COUNT); + return std::min(compressed, MAX_ZERO_BYTE_COUNT); } unsigned decodeCompressedZeroByteCount(unsigned encoded_size) @@ -360,14 +340,14 @@ private: auto zeroes_dfcm = std::countl_zero(compressed_dfcm); auto zeroes_fcm = std::countl_zero(compressed_fcm); if (zeroes_dfcm > zeroes_fcm) - return {compressed_dfcm, encodeCompressedZeroByteCount(zeroes_dfcm / BITS_PER_BYTE), DFCM_BIT}; - return {compressed_fcm, encodeCompressedZeroByteCount(zeroes_fcm / BITS_PER_BYTE), FCM_BIT}; + return {compressed_dfcm, encodeCompressedZeroByteCount(static_cast(zeroes_dfcm) / BITS_PER_BYTE), DFCM_BIT}; + return {compressed_fcm, encodeCompressedZeroByteCount(static_cast(zeroes_fcm) / BITS_PER_BYTE), FCM_BIT}; } void encodePair(TUint first, TUint second) { - auto [value1, zero_byte_count1, predictor1] = compressValue(first); - auto [value2, zero_byte_count2, predictor2] = compressValue(second); + auto [compressed_value1, zero_byte_count1, predictor1] = compressValue(first); + auto [compressed_value2, zero_byte_count2, predictor2] = compressValue(second); std::byte header{0x0}; header |= (predictor1 << 4) | predictor2; header |= static_cast((zero_byte_count1 << 4) | zero_byte_count2); @@ -378,14 +358,14 @@ private: auto tail_size1 = VALUE_SIZE - zero_byte_count1; auto tail_size2 = VALUE_SIZE - zero_byte_count2; - std::memcpy(result.data() + 1, valueTail(value1, zero_byte_count1), tail_size1); - std::memcpy(result.data() + 1 + tail_size1, valueTail(value2, zero_byte_count2), tail_size2); + std::memcpy(result.data() + 1, valueTail(compressed_value1, zero_byte_count1), tail_size1); + std::memcpy(result.data() + 1 + tail_size1, valueTail(compressed_value2, zero_byte_count2), tail_size2); result = result.subspan(1 + tail_size1 + tail_size2); } std::size_t decodeChunk(std::span values, std::span seq) { - std::size_t read_bytes{0}; + std::size_t read_bytes = 0; for (std::size_t i = 0; i < seq.size(); i += 2) { read_bytes += decodePair(values.subspan(read_bytes), seq[i], seq[i + 1]); @@ -411,7 +391,7 @@ private: std::size_t decodePair(std::span bytes, TUint& first, TUint& second) { - if (bytes.empty()) + if (bytes.empty()) [[unlikely]] throw Exception(ErrorCodes::CANNOT_DECOMPRESS, "Unexpected end of encoded sequence"); auto zero_byte_count1 = decodeCompressedZeroByteCount( @@ -422,11 +402,11 @@ private: auto tail_size1 = VALUE_SIZE - zero_byte_count1; auto tail_size2 = VALUE_SIZE - zero_byte_count2; - if (bytes.size() < 1 + tail_size1 + tail_size2) + if (bytes.size() < 1 + tail_size1 + tail_size2) [[unlikely]] throw Exception(ErrorCodes::CANNOT_DECOMPRESS, "Unexpected end of encoded sequence"); - TUint value1{0}; - TUint value2{0}; + TUint value1 = 0; + TUint value2 = 0; std::memcpy(valueTail(value1, zero_byte_count1), bytes.data() + 1, tail_size1); std::memcpy(valueTail(value2, zero_byte_count2), bytes.data() + 1 + tail_size1, tail_size2); @@ -441,7 +421,7 @@ private: static void* valueTail(TUint& value, unsigned compressed_size) { - if constexpr (Endian == std::endian::little) + if constexpr (ENDIAN == std::endian::little) { return &value; } @@ -453,7 +433,10 @@ private: DfcmPredictor dfcm_predictor; FcmPredictor fcm_predictor; + + // memcpy the input into this buffer to align reads, this improves performance compared to unaligned reads (bit_cast) by ~10% std::array chunk{}; + std::span result{}; }; @@ -463,7 +446,6 @@ UInt32 CompressionCodecFPC::doCompressData(const char * source, UInt32 source_si { dest[0] = static_cast(float_width); dest[1] = static_cast(level); - dest[2] = std::to_integer(encodeEndianness(std::endian::native)); auto dest_size = getMaxCompressedDataSize(source_size); auto destination = std::as_writable_bytes(std::span(dest, dest_size).subspan(HEADER_SIZE)); @@ -490,8 +472,6 @@ void CompressionCodecFPC::doDecompressData(const char * source, UInt32 source_si auto compressed_level = std::to_integer(compressed_data[1]); if (compressed_level == 0 || compressed_level > MAX_COMPRESSION_LEVEL) throw Exception("Cannot decompress. File has incorrect level", ErrorCodes::CANNOT_DECOMPRESS); - if (decodeEndianness(compressed_data[2]) != std::endian::native) - throw Exception("Cannot decompress. File has incorrect endianness", ErrorCodes::CANNOT_DECOMPRESS); auto destination = std::as_writable_bytes(std::span(dest, uncompressed_size)); auto src = compressed_data.subspan(HEADER_SIZE); diff --git a/tests/performance/codecs_float_select.xml b/tests/performance/codecs_float_select.xml index 4743a756ac3..325c140d9a0 100644 --- a/tests/performance/codecs_float_select.xml +++ b/tests/performance/codecs_float_select.xml @@ -37,7 +37,11 @@ - CREATE TABLE IF NOT EXISTS codec_{seq_type}_{type}_{codec} (n {type} CODEC({codec})) ENGINE = MergeTree PARTITION BY tuple() ORDER BY tuple(); + + CREATE TABLE IF NOT EXISTS codec_{seq_type}_{type}_{codec} (n {type} CODEC({codec})) + ENGINE = MergeTree PARTITION BY tuple() + ORDER BY tuple(); + INSERT INTO codec_seq_{type}_{codec} (n) SELECT number/pi() FROM system.numbers LIMIT {num_rows} SETTINGS max_threads=1 INSERT INTO codec_mon_{type}_{codec} (n) SELECT number+sin(number) FROM system.numbers LIMIT {num_rows} SETTINGS max_threads=1 From 8fef6e7fa341d87be1c35e4e8b8537877441a88f Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 15 Jun 2022 14:37:58 +0200 Subject: [PATCH 110/204] Add missing ":" to error msg --- utils/self-extracting-executable/compressor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/self-extracting-executable/compressor.cpp b/utils/self-extracting-executable/compressor.cpp index 7a4ee46d5cc..7108e34e220 100644 --- a/utils/self-extracting-executable/compressor.cpp +++ b/utils/self-extracting-executable/compressor.cpp @@ -318,7 +318,7 @@ int copy_decompressor(const char *self, int output_fd) if (sz < 0) perror(nullptr); else - std::cerr << "Error unable to extract decompressor" << std::endl; + std::cerr << "Error: unable to extract decompressor" << std::endl; close(input_fd); return 1; } From 7e1f64002da6e7d33a814c06b4ab201b4ec3f3c6 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 15 Jun 2022 12:48:30 +0000 Subject: [PATCH 111/204] Address review comments --- src/Coordination/KeeperDispatcher.cpp | 10 ++-- src/Coordination/KeeperServer.cpp | 4 ++ src/Coordination/KeeperStateMachine.cpp | 4 ++ src/Coordination/KeeperStorage.cpp | 78 ++++++++++++------------- src/Coordination/KeeperStorage.h | 8 ++- 5 files changed, 59 insertions(+), 45 deletions(-) diff --git a/src/Coordination/KeeperDispatcher.cpp b/src/Coordination/KeeperDispatcher.cpp index c9ff30d7318..9ad5fe9e8ed 100644 --- a/src/Coordination/KeeperDispatcher.cpp +++ b/src/Coordination/KeeperDispatcher.cpp @@ -442,14 +442,14 @@ void KeeperDispatcher::finishSession(int64_t session_id) void KeeperDispatcher::addErrorResponses(const KeeperStorage::RequestsForSessions & requests_for_sessions, Coordination::Error error) { - for (const auto & [session_id, time, request, zxid, nodes_hash] : requests_for_sessions) + for (const auto & request_for_session : requests_for_sessions) { KeeperStorage::ResponsesForSessions responses; - auto response = request->makeResponse(); - response->xid = request->xid; - response->zxid = zxid; + auto response = request_for_session.request->makeResponse(); + response->xid = request_for_session.request->xid; + response->zxid = 0; response->error = error; - if (!responses_queue.push(DB::KeeperStorage::ResponseForSession{session_id, response})) + if (!responses_queue.push(DB::KeeperStorage::ResponseForSession{request_for_session.session_id, response})) throw Exception(ErrorCodes::SYSTEM_ERROR, "Could not push error response xid {} zxid {} error message {} to responses queue", response->xid, diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index c04f3f0e3ef..d4c188fe8d9 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -387,6 +387,8 @@ void KeeperServer::shutdown() namespace { +// Serialize the request with all the necessary information for the leader +// we don't know ZXID and digest yet so we don't serialize it nuraft::ptr getZooKeeperRequestMessage(const KeeperStorage::RequestForSession & request_for_session) { DB::WriteBufferFromNuraftBuffer write_buf; @@ -396,6 +398,7 @@ nuraft::ptr getZooKeeperRequestMessage(const KeeperStorage::Requ return write_buf.getBuffer(); } +// Serialize the request for the log entry nuraft::ptr getZooKeeperLogEntry(const KeeperStorage::RequestForSession & request_for_session) { DB::WriteBufferFromNuraftBuffer write_buf; @@ -540,6 +543,7 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ { switch (type) { + // This event is called before a single log is appended to the entry on the leader node case nuraft::cb_func::PreAppendLog: { // we are relying on the fact that request are being processed under a mutex diff --git a/src/Coordination/KeeperStateMachine.cpp b/src/Coordination/KeeperStateMachine.cpp index 53258f09805..368b23f34d2 100644 --- a/src/Coordination/KeeperStateMachine.cpp +++ b/src/Coordination/KeeperStateMachine.cpp @@ -190,6 +190,7 @@ void KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & req request_for_session.zxid, true /* check_acl */, request_for_session.digest); + if (digest_enabled && request_for_session.digest) assertDigest(*request_for_session.digest, storage->getNodesDigest(false), *request_for_session.request, false); } @@ -282,6 +283,9 @@ void KeeperStateMachine::commit_config(const uint64_t /* log_idx */, nuraft::ptr void KeeperStateMachine::rollback(uint64_t log_idx, nuraft::buffer & data) { auto request_for_session = parseRequest(data); + // If we received a log from an older node, use the log_idx as the zxid + // log_idx will always be larger or equal to the zxid so we can safely do this + // (log_idx is increased for all logs, while zxid is only increased for requests) if (!request_for_session.zxid) request_for_session.zxid = log_idx; diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index a688c4d0ce1..15ad2d4aff6 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -159,7 +159,7 @@ KeeperStorage::ResponsesForSessions processWatchesImpl( } // When this function is updated, update CURRENT_DIGEST_VERSION!! -UInt64 calculateDigest(std::string_view path, std::string_view data, const Coordination::Stat & stat) +uint64_t calculateDigest(std::string_view path, std::string_view data, const Coordination::Stat & stat) { SipHash hash; @@ -243,7 +243,7 @@ struct Overloaded : Ts... template Overloaded(Ts...) -> Overloaded; -std::shared_ptr KeeperStorage::UncommittedState::getNodeFromStorage(StringRef path) const +std::shared_ptr KeeperStorage::UncommittedState::tryGetNodeFromStorage(StringRef path) const { if (auto node_it = storage.container.find(path); node_it != storage.container.end()) { @@ -261,7 +261,7 @@ void KeeperStorage::UncommittedState::applyDelta(const Delta & delta) assert(!delta.path.empty()); if (!nodes.contains(delta.path)) { - if (auto storage_node = getNodeFromStorage(delta.path)) + if (auto storage_node = tryGetNodeFromStorage(delta.path)) nodes.emplace(delta.path, UncommittedNode{.node = std::move(storage_node)}); else nodes.emplace(delta.path, UncommittedNode{.node = nullptr}); @@ -403,7 +403,7 @@ std::shared_ptr KeeperStorage::UncommittedState::getNode(St if (auto node_it = nodes.find(std::string{path}); node_it != nodes.end()) return node_it->second.node; - return getNodeFromStorage(path); + return tryGetNodeFromStorage(path); } Coordination::ACLs KeeperStorage::UncommittedState::getACLs(StringRef path) const @@ -733,10 +733,10 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr auto parent_path = parentPath(request.path); auto parent_node = storage.uncommitted_state.getNode(parent_path); if (parent_node == nullptr) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; else if (parent_node->stat.ephemeralOwner != 0) - return {{zxid, Coordination::Error::ZNOCHILDRENFOREPHEMERALS}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNOCHILDRENFOREPHEMERALS}}; std::string path_created = request.path; if (request.is_sequential) @@ -751,35 +751,35 @@ struct KeeperStorageCreateRequestProcessor final : public KeeperStorageRequestPr } if (storage.uncommitted_state.getNode(path_created)) - return {{zxid, Coordination::Error::ZNODEEXISTS}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNODEEXISTS}}; if (getBaseName(path_created).size == 0) - return {{zxid, Coordination::Error::ZBADARGUMENTS}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZBADARGUMENTS}}; Coordination::ACLs node_acls; if (!fixupACL(request.acls, storage.session_and_auth[session_id], node_acls)) - return {{zxid, Coordination::Error::ZINVALIDACL}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZINVALIDACL}}; if (request.is_ephemeral) storage.ephemerals[session_id].emplace(path_created); int32_t parent_cversion = request.parent_cversion; - new_deltas.emplace_back( - std::string{parent_path}, - zxid, - KeeperStorage::UpdateNodeDelta{[parent_cversion, zxid](KeeperStorage::Node & node) - { - ++node.seq_num; - if (parent_cversion == -1) - ++node.stat.cversion; - else if (parent_cversion > node.stat.cversion) - node.stat.cversion = parent_cversion; + auto parent_update = [parent_cversion, zxid](KeeperStorage::Node & node) + { + /// Increment sequential number even if node is not sequential + ++node.seq_num; + if (parent_cversion == -1) + ++node.stat.cversion; + else if (parent_cversion > node.stat.cversion) + node.stat.cversion = parent_cversion; - if (zxid > node.stat.pzxid) - node.stat.pzxid = zxid; - ++node.stat.numChildren; - }}); + if (zxid > node.stat.pzxid) + node.stat.pzxid = zxid; + ++node.stat.numChildren; + }; + + new_deltas.emplace_back(std::string{parent_path}, zxid, KeeperStorage::UpdateNodeDelta{std::move(parent_update)}); Coordination::Stat stat; stat.czxid = zxid; @@ -842,7 +842,7 @@ struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProce Coordination::ZooKeeperGetRequest & request = dynamic_cast(*zk_request); if (!storage.uncommitted_state.getNode(request.path)) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; return {}; } @@ -935,12 +935,12 @@ struct KeeperStorageRemoveRequestProcessor final : public KeeperStorageRequestPr { if (request.restored_from_zookeeper_log) update_parent_pzxid(); - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; } else if (request.version != -1 && request.version != node->stat.version) - return {{zxid, Coordination::Error::ZBADVERSION}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZBADVERSION}}; else if (node->stat.numChildren != 0) - return {{zxid, Coordination::Error::ZNOTEMPTY}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNOTEMPTY}}; if (request.restored_from_zookeeper_log) update_parent_pzxid(); @@ -990,7 +990,7 @@ struct KeeperStorageExistsRequestProcessor final : public KeeperStorageRequestPr Coordination::ZooKeeperExistsRequest & request = dynamic_cast(*zk_request); if (!storage.uncommitted_state.getNode(request.path)) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; return {}; } @@ -1056,12 +1056,12 @@ struct KeeperStorageSetRequestProcessor final : public KeeperStorageRequestProce std::vector new_deltas; if (!storage.uncommitted_state.getNode(request.path)) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; auto node = storage.uncommitted_state.getNode(request.path); if (request.version != -1 && request.version != node->stat.version) - return {{zxid, Coordination::Error::ZBADVERSION}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZBADVERSION}}; new_deltas.emplace_back( request.path, @@ -1138,7 +1138,7 @@ struct KeeperStorageListRequestProcessor final : public KeeperStorageRequestProc Coordination::ZooKeeperListRequest & request = dynamic_cast(*zk_request); if (!storage.uncommitted_state.getNode(request.path)) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; return {}; } @@ -1213,11 +1213,11 @@ struct KeeperStorageCheckRequestProcessor final : public KeeperStorageRequestPro Coordination::ZooKeeperCheckRequest & request = dynamic_cast(*zk_request); if (!storage.uncommitted_state.getNode(request.path)) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; auto node = storage.uncommitted_state.getNode(request.path); if (request.version != -1 && request.version != node->stat.version) - return {{zxid, Coordination::Error::ZBADVERSION}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZBADVERSION}}; return {}; } @@ -1292,19 +1292,19 @@ struct KeeperStorageSetACLRequestProcessor final : public KeeperStorageRequestPr auto & uncommitted_state = storage.uncommitted_state; if (!uncommitted_state.getNode(request.path)) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; auto node = uncommitted_state.getNode(request.path); if (request.version != -1 && request.version != node->stat.aversion) - return {{zxid, Coordination::Error::ZBADVERSION}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZBADVERSION}}; auto & session_auth_ids = storage.session_and_auth[session_id]; Coordination::ACLs node_acls; if (!fixupACL(request.acls, session_auth_ids, node_acls)) - return {{zxid, Coordination::Error::ZINVALIDACL}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZINVALIDACL}}; std::vector new_deltas { @@ -1365,7 +1365,7 @@ struct KeeperStorageGetACLRequestProcessor final : public KeeperStorageRequestPr Coordination::ZooKeeperGetACLRequest & request = dynamic_cast(*zk_request); if (!storage.uncommitted_state.getNode(request.path)) - return {{zxid, Coordination::Error::ZNONODE}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZNONODE}}; return {}; } @@ -1478,7 +1478,7 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro response_errors.push_back(Coordination::Error::ZRUNTIMEINCONSISTENCY); } - return {{zxid, KeeperStorage::FailedMultiDelta{std::move(response_errors)}}}; + return {KeeperStorage::Delta{zxid, KeeperStorage::FailedMultiDelta{std::move(response_errors)}}}; } } new_deltas.emplace_back(zxid, KeeperStorage::SubDeltaEnd{}); @@ -1589,7 +1589,7 @@ struct KeeperStorageAuthRequestProcessor final : public KeeperStorageRequestProc Coordination::ZooKeeperResponsePtr response_ptr = zk_request->makeResponse(); if (auth_request.scheme != "digest" || std::count(auth_request.data.begin(), auth_request.data.end(), ':') != 1) - return {{zxid, Coordination::Error::ZAUTHFAILED}}; + return {KeeperStorage::Delta{zxid, Coordination::Error::ZAUTHFAILED}}; std::vector new_deltas; auto auth_digest = generateDigest(auth_request.data); diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index 3b9465edba6..c8bf3841207 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -52,9 +52,15 @@ public: const auto & getChildren() const noexcept { return children; } + // Invalidate the calculated digest so it's recalculated again on the next + // getDigest call void invalidateDigestCache() const; + + // get the calculated digest of the node UInt64 getDigest(std::string_view path) const; + // copy only necessary information for preprocessing and digest calculation + // (e.g. we don't need to copy list of children) void shallowCopy(const Node & other); private: @@ -240,7 +246,7 @@ public: return false; } - std::shared_ptr getNodeFromStorage(StringRef path) const; + std::shared_ptr tryGetNodeFromStorage(StringRef path) const; struct UncommittedNode { From 1ff6218996834a2c371af34e76e715d9b5b5dad2 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 15 Jun 2022 15:01:47 +0200 Subject: [PATCH 112/204] Revert "(Temporary / to be reverted) Force an official build." This reverts commit 45d309108781842876baa8e4bd51827eb242bcaf. --- tests/ci/build_check.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/ci/build_check.py b/tests/ci/build_check.py index eb03152c1a9..9730ac2cc46 100644 --- a/tests/ci/build_check.py +++ b/tests/ci/build_check.py @@ -250,17 +250,14 @@ def main(): logging.info("Got version from repo %s", version.string) - # official_flag = pr_info.number == 0 - # if "official" in build_config: - # official_flag = build_config["official"] - # - # version_type = "testing" - # if "release" in pr_info.labels or "release-lts" in pr_info.labels: - # version_type = "stable" - # official_flag = True + official_flag = pr_info.number == 0 + if "official" in build_config: + official_flag = build_config["official"] version_type = "testing" - official_flag = True + if "release" in pr_info.labels or "release-lts" in pr_info.labels: + version_type = "stable" + official_flag = True update_version_local(version, version_type) From 08e3f77a9cce0087ae15ca06193a0eaf28d9081f Mon Sep 17 00:00:00 2001 From: Danila Kutenin Date: Wed, 15 Jun 2022 13:19:29 +0000 Subject: [PATCH 113/204] Optimize most important parts with NEON SIMD First part, updated most UTF8, hashing, memory and codecs. Except utf8lower and upper, maybe a little later. That includes huge amount of research with movemask dealing. Exact details and blog post TBD. --- cmake/cpu_features.cmake | 2 +- contrib/simdjson | 2 +- contrib/zstd | 2 +- contrib/zstd-cmake/CMakeLists.txt | 6 +- src/Columns/ColumnsCommon.h | 19 ++ src/Common/HashTable/Hash.h | 28 ++ src/Common/UTF8Helpers.h | 16 ++ src/Common/memcmpSmall.h | 262 ++++++++++++++++-- src/Common/memcpySmall.h | 51 +++- src/Functions/FunctionsStringSimilarity.cpp | 6 + src/Functions/modulo.cpp | 8 +- src/Functions/toValidUTF8.cpp | 36 +++ src/IO/ReadHelpers.cpp | 24 ++ src/IO/WriteBufferValidUTF8.cpp | 36 ++- src/Interpreters/createBlockSelector.cpp | 8 +- .../MergeTree/MergeTreeRangeReader.cpp | 34 +++ 16 files changed, 492 insertions(+), 48 deletions(-) diff --git a/cmake/cpu_features.cmake b/cmake/cpu_features.cmake index fd4cc51b6f9..7b966e1acac 100644 --- a/cmake/cpu_features.cmake +++ b/cmake/cpu_features.cmake @@ -29,7 +29,7 @@ if (ARCH_NATIVE) set (COMPILER_FLAGS "${COMPILER_FLAGS} -march=native") elseif (ARCH_AARCH64) - set (COMPILER_FLAGS "${COMPILER_FLAGS} -march=armv8-a+crc") + set (COMPILER_FLAGS "${COMPILER_FLAGS} -march=armv8-a+crc+simd+crypto+dotprod+ssbs") elseif (ARCH_PPC64LE) # Note that gcc and clang have support for x86 SSE2 intrinsics when building for PowerPC diff --git a/contrib/simdjson b/contrib/simdjson index 8df32cea335..de196dd7a3a 160000 --- a/contrib/simdjson +++ b/contrib/simdjson @@ -1 +1 @@ -Subproject commit 8df32cea3359cb30120795da6020b3b73da01d38 +Subproject commit de196dd7a3a16e4056b0551ffa3b85c2f52581e1 diff --git a/contrib/zstd b/contrib/zstd index a488ba114ec..b944db0c451 160000 --- a/contrib/zstd +++ b/contrib/zstd @@ -1 +1 @@ -Subproject commit a488ba114ec17ea1054b9057c26a046fc122b3b6 +Subproject commit b944db0c451ba1bc6bbd8e201d5f88f9041bf1f9 diff --git a/contrib/zstd-cmake/CMakeLists.txt b/contrib/zstd-cmake/CMakeLists.txt index 4949c3f30d5..f44d5db12c4 100644 --- a/contrib/zstd-cmake/CMakeLists.txt +++ b/contrib/zstd-cmake/CMakeLists.txt @@ -50,7 +50,7 @@ GetLibraryVersion("${HEADER_CONTENT}" LIBVER_MAJOR LIBVER_MINOR LIBVER_RELEASE) MESSAGE(STATUS "ZSTD VERSION ${LIBVER_MAJOR}.${LIBVER_MINOR}.${LIBVER_RELEASE}") # cd contrib/zstd/lib -# find . -name '*.c' | grep -vP 'deprecated|legacy' | sort | sed 's/^\./ "${LIBRARY_DIR}/"' +# find . -name '*.c' -or -name '*.S' | grep -vP 'deprecated|legacy' | sort | sed 's/^\./ "${LIBRARY_DIR}/"' SET(Sources "${LIBRARY_DIR}/common/debug.c" "${LIBRARY_DIR}/common/entropy_common.c" @@ -73,6 +73,7 @@ SET(Sources "${LIBRARY_DIR}/compress/zstd_ldm.c" "${LIBRARY_DIR}/compress/zstdmt_compress.c" "${LIBRARY_DIR}/compress/zstd_opt.c" + "${LIBRARY_DIR}/decompress/huf_decompress_amd64.S" "${LIBRARY_DIR}/decompress/huf_decompress.c" "${LIBRARY_DIR}/decompress/zstd_ddict.c" "${LIBRARY_DIR}/decompress/zstd_decompress_block.c" @@ -85,6 +86,7 @@ SET(Sources # cd contrib/zstd/lib # find . -name '*.h' | grep -vP 'deprecated|legacy' | sort | sed 's/^\./ "${LIBRARY_DIR}/"' SET(Headers + "${LIBRARY_DIR}/common/bits.h" "${LIBRARY_DIR}/common/bitstream.h" "${LIBRARY_DIR}/common/compiler.h" "${LIBRARY_DIR}/common/cpu.h" @@ -94,11 +96,13 @@ SET(Headers "${LIBRARY_DIR}/common/huf.h" "${LIBRARY_DIR}/common/mem.h" "${LIBRARY_DIR}/common/pool.h" + "${LIBRARY_DIR}/common/portability_macros.h" "${LIBRARY_DIR}/common/threading.h" "${LIBRARY_DIR}/common/xxhash.h" "${LIBRARY_DIR}/common/zstd_deps.h" "${LIBRARY_DIR}/common/zstd_internal.h" "${LIBRARY_DIR}/common/zstd_trace.h" + "${LIBRARY_DIR}/compress/clevels.h" "${LIBRARY_DIR}/compress/hist.h" "${LIBRARY_DIR}/compress/zstd_compress_internal.h" "${LIBRARY_DIR}/compress/zstd_compress_literals.h" diff --git a/src/Columns/ColumnsCommon.h b/src/Columns/ColumnsCommon.h index 99489e7e4cc..1e5849e2b88 100644 --- a/src/Columns/ColumnsCommon.h +++ b/src/Columns/ColumnsCommon.h @@ -8,6 +8,9 @@ #if defined(__AVX512F__) || defined(__AVX512BW__) || defined(__AVX__) || defined(__AVX2__) #include #endif +#if defined(__aarch64__) && defined(__ARM_NEON) +# include +#endif /// Common helper methods for implementation of different columns. @@ -44,6 +47,22 @@ inline UInt64 bytes64MaskToBits64Mask(const UInt8 * bytes64) _mm_loadu_si128(reinterpret_cast(bytes64 + 32)), zero16))) << 32) & 0xffff00000000) | ((static_cast(_mm_movemask_epi8(_mm_cmpeq_epi8( _mm_loadu_si128(reinterpret_cast(bytes64 + 48)), zero16))) << 48) & 0xffff000000000000); +#elif defined(__aarch64__) && defined(__ARM_NEON) + const uint8x16_t bitmask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; + const auto * src = reinterpret_cast(bytes64); + const uint8x16_t p0 = vceqzq_u8(vld1q_u8(src)); + const uint8x16_t p1 = vceqzq_u8(vld1q_u8(src + 16)); + const uint8x16_t p2 = vceqzq_u8(vld1q_u8(src + 32)); + const uint8x16_t p3 = vceqzq_u8(vld1q_u8(src + 48)); + uint8x16_t t0 = vandq_u8(p0, bitmask); + uint8x16_t t1 = vandq_u8(p1, bitmask); + uint8x16_t t2 = vandq_u8(p2, bitmask); + uint8x16_t t3 = vandq_u8(p3, bitmask); + uint8x16_t sum0 = vpaddq_u8(t0, t1); + uint8x16_t sum1 = vpaddq_u8(t2, t3); + sum0 = vpaddq_u8(sum0, sum1); + sum0 = vpaddq_u8(sum0, sum0); + UInt64 res = vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0); #else UInt64 res = 0; for (size_t i = 0; i < 64; ++i) diff --git a/src/Common/HashTable/Hash.h b/src/Common/HashTable/Hash.h index 3cf8978f418..bf691458d27 100644 --- a/src/Common/HashTable/Hash.h +++ b/src/Common/HashTable/Hash.h @@ -296,6 +296,19 @@ struct UInt128HashCRC32 } }; +#elif defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + +struct UInt128HashCRC32 +{ + size_t operator()(UInt128 x) const + { + UInt64 crc = -1ULL; + crc = __crc32cd(crc, x.items[0]); + crc = __crc32cd(crc, x.items[1]); + return crc; + } +}; + #else /// On other platforms we do not use CRC32. NOTE This can be confusing. @@ -339,6 +352,21 @@ struct UInt256HashCRC32 } }; +#elif defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + +struct UInt256HashCRC32 +{ + size_t operator()(UInt256 x) const + { + UInt64 crc = -1ULL; + crc = __crc32cd(crc, x.items[0]); + crc = __crc32cd(crc, x.items[1]); + crc = __crc32cd(crc, x.items[2]); + crc = __crc32cd(crc, x.items[3]); + return crc; + } +}; + #else /// We do not need to use CRC32 on other platforms. NOTE This can be confusing. diff --git a/src/Common/UTF8Helpers.h b/src/Common/UTF8Helpers.h index a940ddc0234..04326f6910c 100644 --- a/src/Common/UTF8Helpers.h +++ b/src/Common/UTF8Helpers.h @@ -9,6 +9,13 @@ #include #endif +#if defined(__aarch64__) && defined(__ARM_NEON) +# include +# ifdef HAS_RESERVED_IDENTIFIER +# pragma clang diagnostic ignored "-Wreserved-identifier" +# endif +#endif + namespace DB { @@ -66,6 +73,15 @@ inline size_t countCodePoints(const UInt8 * data, size_t size) for (; data < src_end_sse; data += bytes_sse) res += __builtin_popcount(_mm_movemask_epi8( _mm_cmpgt_epi8(_mm_loadu_si128(reinterpret_cast(data)), threshold))); +#elif defined(__aarch64__) && defined(__ARM_NEON) + auto get_nibble_mask + = [](uint8x16_t input) -> uint64_t { return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(input), 4)), 0); }; + constexpr auto bytes_sse = 16; + const auto * src_end_sse = data + size / bytes_sse * bytes_sse; + + for (; data < src_end_sse; data += bytes_sse) + res += __builtin_popcountll(get_nibble_mask(vcgtq_s8(vld1q_s8(reinterpret_cast(data)), vdupq_n_s8(0xBF)))); + res >>= 2; #endif for (; data < end; ++data) /// Skip UTF-8 continuation bytes. diff --git a/src/Common/memcmpSmall.h b/src/Common/memcmpSmall.h index 57b9c731897..9a47f46639e 100644 --- a/src/Common/memcmpSmall.h +++ b/src/Common/memcmpSmall.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include @@ -26,7 +26,7 @@ inline int cmp(T a, T b) /// Results don't depend on the values inside uninitialized memory but Memory Sanitizer cannot see it. /// Disable optimized functions if compile with Memory Sanitizer. #if defined(__AVX512BW__) && defined(__AVX512VL__) && !defined(MEMORY_SANITIZER) -#include +# include /** All functions works under the following assumptions: @@ -45,7 +45,8 @@ inline int memcmpSmallAllowOverflow15(const Char * a, size_t a_size, const Char { uint16_t mask = _mm_cmp_epi8_mask( _mm_loadu_si128(reinterpret_cast(a + offset)), - _mm_loadu_si128(reinterpret_cast(b + offset)), _MM_CMPINT_NE); + _mm_loadu_si128(reinterpret_cast(b + offset)), + _MM_CMPINT_NE); if (mask) { @@ -76,7 +77,8 @@ inline int memcmpSmallLikeZeroPaddedAllowOverflow15(const Char * a, size_t a_siz { uint16_t mask = _mm_cmp_epi8_mask( _mm_loadu_si128(reinterpret_cast(a + offset)), - _mm_loadu_si128(reinterpret_cast(b + offset)), _MM_CMPINT_NE); + _mm_loadu_si128(reinterpret_cast(b + offset)), + _MM_CMPINT_NE); if (mask) { @@ -117,9 +119,7 @@ inline int memcmpSmallLikeZeroPaddedAllowOverflow15(const Char * a, size_t a_siz for (size_t offset = min_size; offset < max_size; offset += 16) { - uint16_t mask = _mm_cmpneq_epi8_mask( - _mm_loadu_si128(reinterpret_cast(longest + offset)), - zero16); + uint16_t mask = _mm_cmpneq_epi8_mask(_mm_loadu_si128(reinterpret_cast(longest + offset)), zero16); if (mask) { @@ -145,7 +145,8 @@ inline int memcmpSmallAllowOverflow15(const Char * a, const Char * b, size_t siz { uint16_t mask = _mm_cmp_epi8_mask( _mm_loadu_si128(reinterpret_cast(a + offset)), - _mm_loadu_si128(reinterpret_cast(b + offset)), _MM_CMPINT_NE); + _mm_loadu_si128(reinterpret_cast(b + offset)), + _MM_CMPINT_NE); if (mask) { @@ -174,7 +175,8 @@ inline bool memequalSmallAllowOverflow15(const Char * a, size_t a_size, const Ch { uint16_t mask = _mm_cmp_epi8_mask( _mm_loadu_si128(reinterpret_cast(a + offset)), - _mm_loadu_si128(reinterpret_cast(b + offset)), _MM_CMPINT_NE); + _mm_loadu_si128(reinterpret_cast(b + offset)), + _MM_CMPINT_NE); if (mask) { @@ -196,7 +198,8 @@ inline int memcmpSmallMultipleOf16(const Char * a, const Char * b, size_t size) { uint16_t mask = _mm_cmp_epi8_mask( _mm_loadu_si128(reinterpret_cast(a + offset)), - _mm_loadu_si128(reinterpret_cast(b + offset)), _MM_CMPINT_NE); + _mm_loadu_si128(reinterpret_cast(b + offset)), + _MM_CMPINT_NE); if (mask) { @@ -215,8 +218,7 @@ template inline int memcmp16(const Char * a, const Char * b) { uint16_t mask = _mm_cmp_epi8_mask( - _mm_loadu_si128(reinterpret_cast(a)), - _mm_loadu_si128(reinterpret_cast(b)), _MM_CMPINT_NE); + _mm_loadu_si128(reinterpret_cast(a)), _mm_loadu_si128(reinterpret_cast(b)), _MM_CMPINT_NE); if (mask) { @@ -232,9 +234,9 @@ inline int memcmp16(const Char * a, const Char * b) */ inline bool memequal16(const void * a, const void * b) { - return 0xFFFF == _mm_cmp_epi8_mask( - _mm_loadu_si128(reinterpret_cast(a)), - _mm_loadu_si128(reinterpret_cast(b)), _MM_CMPINT_EQ); + return 0xFFFF + == _mm_cmp_epi8_mask( + _mm_loadu_si128(reinterpret_cast(a)), _mm_loadu_si128(reinterpret_cast(b)), _MM_CMPINT_EQ); } @@ -245,8 +247,8 @@ inline bool memoryIsZeroSmallAllowOverflow15(const void * data, size_t size) for (size_t offset = 0; offset < size; offset += 16) { - uint16_t mask = _mm_cmp_epi8_mask(zero16, - _mm_loadu_si128(reinterpret_cast(reinterpret_cast(data) + offset)), _MM_CMPINT_NE); + uint16_t mask = _mm_cmp_epi8_mask( + zero16, _mm_loadu_si128(reinterpret_cast(reinterpret_cast(data) + offset)), _MM_CMPINT_NE); if (mask) { @@ -259,7 +261,7 @@ inline bool memoryIsZeroSmallAllowOverflow15(const void * data, size_t size) } #elif defined(__SSE2__) && !defined(MEMORY_SANITIZER) -#include +# include /** All functions works under the following assumptions: @@ -352,9 +354,7 @@ inline int memcmpSmallLikeZeroPaddedAllowOverflow15(const Char * a, size_t a_siz for (size_t offset = min_size; offset < max_size; offset += 16) { - uint16_t mask = _mm_movemask_epi8(_mm_cmpeq_epi8( - _mm_loadu_si128(reinterpret_cast(longest + offset)), - zero16)); + uint16_t mask = _mm_movemask_epi8(_mm_cmpeq_epi8(_mm_loadu_si128(reinterpret_cast(longest + offset)), zero16)); mask = ~mask; if (mask) @@ -453,9 +453,8 @@ inline int memcmpSmallMultipleOf16(const Char * a, const Char * b, size_t size) template inline int memcmp16(const Char * a, const Char * b) { - uint16_t mask = _mm_movemask_epi8(_mm_cmpeq_epi8( - _mm_loadu_si128(reinterpret_cast(a)), - _mm_loadu_si128(reinterpret_cast(b)))); + uint16_t mask = _mm_movemask_epi8( + _mm_cmpeq_epi8(_mm_loadu_si128(reinterpret_cast(a)), _mm_loadu_si128(reinterpret_cast(b)))); mask = ~mask; if (mask) @@ -472,9 +471,9 @@ inline int memcmp16(const Char * a, const Char * b) */ inline bool memequal16(const void * a, const void * b) { - return 0xFFFF == _mm_movemask_epi8(_mm_cmpeq_epi8( - _mm_loadu_si128(reinterpret_cast(a)), - _mm_loadu_si128(reinterpret_cast(b)))); + return 0xFFFF + == _mm_movemask_epi8(_mm_cmpeq_epi8( + _mm_loadu_si128(reinterpret_cast(a)), _mm_loadu_si128(reinterpret_cast(b)))); } @@ -485,8 +484,8 @@ inline bool memoryIsZeroSmallAllowOverflow15(const void * data, size_t size) for (size_t offset = 0; offset < size; offset += 16) { - uint16_t mask = _mm_movemask_epi8(_mm_cmpeq_epi8(zero16, - _mm_loadu_si128(reinterpret_cast(reinterpret_cast(data) + offset)))); + uint16_t mask = _mm_movemask_epi8( + _mm_cmpeq_epi8(zero16, _mm_loadu_si128(reinterpret_cast(reinterpret_cast(data) + offset)))); mask = ~mask; if (mask) @@ -499,10 +498,213 @@ inline bool memoryIsZeroSmallAllowOverflow15(const void * data, size_t size) return true; } +#elif defined(__aarch64__) && defined(__ARM_NEON) + +# include +# ifdef HAS_RESERVED_IDENTIFIER +# pragma clang diagnostic ignored "-Wreserved-identifier" +# endif + +inline uint64_t getNibbleMask(uint8x16_t res) +{ + return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(res), 4)), 0); +} + +template +inline int memcmpSmallAllowOverflow15(const Char * a, size_t a_size, const Char * b, size_t b_size) +{ + size_t min_size = std::min(a_size, b_size); + + for (size_t offset = 0; offset < min_size; offset += 16) + { + uint64_t mask = getNibbleMask(vceqq_u8( + vld1q_u8(reinterpret_cast(a + offset)), vld1q_u8(reinterpret_cast(b + offset)))); + mask = ~mask; + + if (mask) + { + offset += __builtin_ctzll(mask) >> 2; + + if (offset >= min_size) + break; + + return detail::cmp(a[offset], b[offset]); + } + } + + return detail::cmp(a_size, b_size); +} + +template +inline int memcmpSmallLikeZeroPaddedAllowOverflow15(const Char * a, size_t a_size, const Char * b, size_t b_size) +{ + size_t min_size = std::min(a_size, b_size); + + for (size_t offset = 0; offset < min_size; offset += 16) + { + uint64_t mask = getNibbleMask(vceqq_u8( + vld1q_u8(reinterpret_cast(a + offset)), vld1q_u8(reinterpret_cast(b + offset)))); + mask = ~mask; + + if (mask) + { + offset += __builtin_ctzll(mask) >> 2; + + if (offset >= min_size) + break; + + return detail::cmp(a[offset], b[offset]); + } + } + + /// The strings are equal up to min_size. + /// If the rest of the larger string is zero bytes then the strings are + /// considered equal. + + size_t max_size; + const Char * longest; + int cmp; + + if (a_size == b_size) + { + return 0; + } + else if (a_size > b_size) + { + max_size = a_size; + longest = a; + cmp = 1; + } + else + { + max_size = b_size; + longest = b; + cmp = -1; + } + + for (size_t offset = min_size; offset < max_size; offset += 16) + { + uint64_t mask = getNibbleMask(vceqzq_u8(vld1q_u8(reinterpret_cast(longest + offset)))); + mask = ~mask; + + if (mask) + { + offset += __builtin_ctzll(mask) >> 2; + + if (offset >= max_size) + return 0; + return cmp; + } + } + + return 0; +} + +template +inline int memcmpSmallAllowOverflow15(const Char * a, const Char * b, size_t size) +{ + for (size_t offset = 0; offset < size; offset += 16) + { + uint64_t mask = getNibbleMask(vceqq_u8( + vld1q_u8(reinterpret_cast(a + offset)), vld1q_u8(reinterpret_cast(b + offset)))); + mask = ~mask; + + if (mask) + { + offset += __builtin_ctzll(mask) >> 2; + + if (offset >= size) + return 0; + + return detail::cmp(a[offset], b[offset]); + } + } + + return 0; +} + +template +inline bool memequalSmallAllowOverflow15(const Char * a, size_t a_size, const Char * b, size_t b_size) +{ + if (a_size != b_size) + return false; + + for (size_t offset = 0; offset < a_size; offset += 16) + { + uint64_t mask = getNibbleMask(vceqq_u8( + vld1q_u8(reinterpret_cast(a + offset)), vld1q_u8(reinterpret_cast(b + offset)))); + mask = ~mask; + + if (mask) + { + offset += __builtin_ctzll(mask) >> 2; + return offset >= a_size; + } + } + + return true; +} + +template +inline int memcmpSmallMultipleOf16(const Char * a, const Char * b, size_t size) +{ + for (size_t offset = 0; offset < size; offset += 16) + { + uint64_t mask = getNibbleMask(vceqq_u8( + vld1q_u8(reinterpret_cast(a + offset)), vld1q_u8(reinterpret_cast(b + offset)))); + mask = ~mask; + + if (mask) + { + offset += __builtin_ctzll(mask) >> 2; + return detail::cmp(a[offset], b[offset]); + } + } + + return 0; +} + +template +inline int memcmp16(const Char * a, const Char * b) +{ + uint64_t mask = getNibbleMask( + vceqq_u8(vld1q_u8(reinterpret_cast(a)), vld1q_u8(reinterpret_cast(b)))); + mask = ~mask; + if (mask) + { + auto offset = __builtin_ctzll(mask) >> 2; + return detail::cmp(a[offset], b[offset]); + } + return 0; +} + +inline bool memequal16(const void * a, const void * b) +{ + return 0xFFFFFFFFFFFFFFFFull + == getNibbleMask( + vceqq_u8(vld1q_u8(reinterpret_cast(a)), vld1q_u8(reinterpret_cast(b)))); +} + +inline bool memoryIsZeroSmallAllowOverflow15(const void * data, size_t size) +{ + for (size_t offset = 0; offset < size; offset += 16) + { + uint64_t mask = getNibbleMask(vceqzq_u8(vld1q_u8(reinterpret_cast(data) + offset))); + mask = ~mask; + + if (mask) + { + offset += __builtin_ctzll(mask) >> 2; + return offset >= size; + } + } + + return true; +} #else -#include +# include template inline int memcmpSmallAllowOverflow15(const Char * a, size_t a_size, const Char * b, size_t b_size) diff --git a/src/Common/memcpySmall.h b/src/Common/memcpySmall.h index aaedfb81fe5..16be0906299 100644 --- a/src/Common/memcpySmall.h +++ b/src/Common/memcpySmall.h @@ -3,8 +3,15 @@ #include #ifdef __SSE2__ -#include +# include +#endif +#if defined(__aarch64__) && defined(__ARM_NEON) +# include +# ifdef HAS_RESERVED_IDENTIFIER +# pragma clang diagnostic ignored "-Wreserved-identifier" +# endif +#endif /** memcpy function could work suboptimal if all the following conditions are met: * 1. Size of memory region is relatively small (approximately, under 50 bytes). @@ -27,21 +34,21 @@ * Use with caution. */ +#ifdef __SSE2__ /// Implementation for x86 platform namespace detail { - inline void memcpySmallAllowReadWriteOverflow15Impl(char * __restrict dst, const char * __restrict src, ssize_t n) +inline void memcpySmallAllowReadWriteOverflow15Impl(char * __restrict dst, const char * __restrict src, ssize_t n) +{ + while (n > 0) { - while (n > 0) - { - _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), - _mm_loadu_si128(reinterpret_cast(src))); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), _mm_loadu_si128(reinterpret_cast(src))); - dst += 16; - src += 16; - n -= 16; - } + dst += 16; + src += 16; + n -= 16; } } +} /** Works under assumption, that it's possible to read up to 15 excessive bytes after end of 'src' region * and to write any garbage into up to 15 bytes after end of 'dst' region. @@ -51,11 +58,33 @@ inline void memcpySmallAllowReadWriteOverflow15(void * __restrict dst, const voi detail::memcpySmallAllowReadWriteOverflow15Impl(reinterpret_cast(dst), reinterpret_cast(src), n); } +#elif defined(__aarch64__) && defined(__ARM_NEON) /// Implementation for arm platform, similar to x86 + +namespace detail +{ +inline void memcpySmallAllowReadWriteOverflow15Impl(char * __restrict dst, const char * __restrict src, ssize_t n) +{ + while (n > 0) + { + vst1q_s8(reinterpret_cast(dst), vld1q_s8(reinterpret_cast(src))); + + dst += 16; + src += 16; + n -= 16; + } +} +} + +inline void memcpySmallAllowReadWriteOverflow15(void * __restrict dst, const void * __restrict src, size_t n) +{ + detail::memcpySmallAllowReadWriteOverflow15Impl(reinterpret_cast(dst), reinterpret_cast(src), n); +} + /** NOTE There was also a function, that assumes, that you could read any bytes inside same memory page of src. * This function was unused, and also it requires special handling for Valgrind and ASan. */ -#else /// Implementation for other platforms. +#else /// Implementation for other platforms. inline void memcpySmallAllowReadWriteOverflow15(void * __restrict dst, const void * __restrict src, size_t n) { diff --git a/src/Functions/FunctionsStringSimilarity.cpp b/src/Functions/FunctionsStringSimilarity.cpp index 6b2d34a182c..ee3bce4c0db 100644 --- a/src/Functions/FunctionsStringSimilarity.cpp +++ b/src/Functions/FunctionsStringSimilarity.cpp @@ -20,6 +20,10 @@ # include #endif +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) +# include +#endif + namespace DB { /** Distance function implementation. @@ -64,6 +68,8 @@ struct NgramDistanceImpl UInt64 combined = (static_cast(code_points[0]) << 32) | code_points[1]; #ifdef __SSE4_2__ return _mm_crc32_u64(code_points[2], combined) & 0xFFFFu; +#elif defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) + return __crc32cd(code_points[2], combined) & 0xFFFFu; #else return (intHashCRC32(combined) ^ intHashCRC32(code_points[2])) & 0xFFFFu; #endif diff --git a/src/Functions/modulo.cpp b/src/Functions/modulo.cpp index d494dfca74d..175f74bab8f 100644 --- a/src/Functions/modulo.cpp +++ b/src/Functions/modulo.cpp @@ -2,7 +2,13 @@ #include #if defined(__SSE2__) -# define LIBDIVIDE_SSE2 1 +# define LIBDIVIDE_SSE2 +#elif defined(__AVX512F__) || defined(__AVX512BW__) || defined(__AVX512VL__) +# define LIBDIVIDE_AVX512 +#elif defined(__AVX2__) +# define LIBDIVIDE_AVX2 +#elif defined(__aarch64__) && defined(__ARM_NEON) +# define LIBDIVIDE_NEON #endif #include diff --git a/src/Functions/toValidUTF8.cpp b/src/Functions/toValidUTF8.cpp index 189556f48ea..6334cd2516e 100644 --- a/src/Functions/toValidUTF8.cpp +++ b/src/Functions/toValidUTF8.cpp @@ -11,8 +11,28 @@ # include #endif +#if defined(__aarch64__) && defined(__ARM_NEON) +# include +# ifdef HAS_RESERVED_IDENTIFIER +# pragma clang diagnostic ignored "-Wreserved-identifier" +# endif +#endif + namespace DB { + +#if defined(__aarch64__) && defined(__ARM_NEON) +inline uint64_t getNibbleMask(uint8x16_t res) +{ + return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(res), 4)), 0); +} + +inline bool onlyASCII(uint8x16_t input) +{ + return getNibbleMask(vcgeq_u8(input, vdupq_n_u8(0x80))) == 0; +} +#endif + namespace ErrorCodes { extern const int ILLEGAL_COLUMN; @@ -61,6 +81,22 @@ struct ToValidUTF8Impl while (p < simd_end && !_mm_movemask_epi8(_mm_loadu_si128(reinterpret_cast(p)))) p += SIMD_BYTES; + if (!(p < end)) + break; +#elif defined(__aarch64__) && defined(__ARM_NEON) + /// Fast skip of ASCII for aarch64. + static constexpr size_t SIMD_BYTES = 16; + const char * simd_end = p + (end - p) / SIMD_BYTES * SIMD_BYTES; + + /// Other options include + /// vmaxvq_u8(input) < 0b10000000; + /// Used by SIMDJSON, has latency 3 for M1, 6 for everything else + /// SIMDJSON uses it for 64 byte masks, so it's a little different. + /// vmaxvq_u32(vandq_u32(input, vdupq_n_u32(0x80808080))) // u32 version has latency 3 + /// shrn version has universally <=3 cycles, on servers 2 cycles. + while (p < simd_end && onlyASCII(vld1q_u8(reinterpret_cast(p)))) + p += SIMD_BYTES; + if (!(p < end)) break; #endif diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index 21e943b36ef..0972e97e39a 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -15,6 +15,13 @@ #include #endif +#if defined(__aarch64__) && defined(__ARM_NEON) +# include +# ifdef HAS_RESERVED_IDENTIFIER +# pragma clang diagnostic ignored "-Wreserved-identifier" +# endif +#endif + namespace DB { @@ -695,6 +702,23 @@ void readCSVStringInto(Vector & s, ReadBuffer & buf, const FormatSettings::CSV & return; } } +#elif defined(__aarch64__) && defined(__ARM_NEON) + auto rc = vdupq_n_u8('\r'); + auto nc = vdupq_n_u8('\n'); + auto dc = vdupq_n_u8(delimiter); + auto get_nibble_mask = [](uint8x16_t input) -> uint64_t + { return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(input), 4)), 0); }; + for (; next_pos + 15 < buf.buffer().end(); next_pos += 16) + { + uint8x16_t bytes = vld1q_u8(reinterpret_cast(next_pos)); + auto eq = vorrq_u8(vorrq_u8(vceqq_u8(bytes, rc), vceqq_u8(bytes, nc)), vceqq_u8(bytes, dc)); + uint64_t bit_mask = get_nibble_mask(eq); + if (bit_mask) + { + next_pos += __builtin_ctzll(bit_mask) >> 2; + return; + } + } #endif while (next_pos < buf.buffer().end() && *next_pos != delimiter && *next_pos != '\r' && *next_pos != '\n') diff --git a/src/IO/WriteBufferValidUTF8.cpp b/src/IO/WriteBufferValidUTF8.cpp index f711db0f1e7..a8fac26603f 100644 --- a/src/IO/WriteBufferValidUTF8.cpp +++ b/src/IO/WriteBufferValidUTF8.cpp @@ -6,10 +6,28 @@ #include #endif +#if defined(__aarch64__) && defined(__ARM_NEON) +# include +# ifdef HAS_RESERVED_IDENTIFIER +# pragma clang diagnostic ignored "-Wreserved-identifier" +# endif +#endif namespace DB { +#if defined(__aarch64__) && defined(__ARM_NEON) +inline uint64_t getNibbleMask(uint8x16_t res) +{ + return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(res), 4)), 0); +} + +inline bool onlyASCII(uint8x16_t input) +{ + return getNibbleMask(vcgeq_u8(input, vdupq_n_u8(0x80))) == 0; +} +#endif + const size_t WriteBufferValidUTF8::DEFAULT_SIZE = 4096; /** Index into the table below with the first byte of a UTF-8 sequence to @@ -67,13 +85,29 @@ void WriteBufferValidUTF8::nextImpl() while (p < pos) { #ifdef __SSE2__ - /// Fast skip of ASCII + /// Fast skip of ASCII for x86. static constexpr size_t SIMD_BYTES = 16; const char * simd_end = p + (pos - p) / SIMD_BYTES * SIMD_BYTES; while (p < simd_end && !_mm_movemask_epi8(_mm_loadu_si128(reinterpret_cast(p)))) p += SIMD_BYTES; + if (!(p < pos)) + break; +#elif defined(__aarch64__) && defined(__ARM_NEON) + /// Fast skip of ASCII for aarch64. + static constexpr size_t SIMD_BYTES = 16; + const char * simd_end = p + (pos - p) / SIMD_BYTES * SIMD_BYTES; + + /// Other options include + /// vmaxvq_u8(input) < 0b10000000; + /// Used by SIMDJSON, has latency 3 for M1, 6 for everything else + /// SIMDJSON uses it for 64 byte masks, so it's a little different. + /// vmaxvq_u32(vandq_u32(input, vdupq_n_u32(0x80808080))) // u32 version has latency 3 + /// shrn version has universally <=3 cycles, on servers 2 cycles. + while (p < simd_end && onlyASCII(vld1q_u8(reinterpret_cast(p)))) + p += SIMD_BYTES; + if (!(p < pos)) break; #endif diff --git a/src/Interpreters/createBlockSelector.cpp b/src/Interpreters/createBlockSelector.cpp index d7a09743b46..b1a9a4e9e35 100644 --- a/src/Interpreters/createBlockSelector.cpp +++ b/src/Interpreters/createBlockSelector.cpp @@ -6,7 +6,13 @@ #include #if defined(__SSE2__) -# define LIBDIVIDE_SSE2 1 +# define LIBDIVIDE_SSE2 +#elif defined(__AVX512F__) || defined(__AVX512BW__) || defined(__AVX512VL__) +# define LIBDIVIDE_AVX512 +#elif defined(__AVX2__) +# define LIBDIVIDE_AVX2 +#elif defined(__aarch64__) && defined(__ARM_NEON) +# define LIBDIVIDE_NEON #endif #include diff --git a/src/Storages/MergeTree/MergeTreeRangeReader.cpp b/src/Storages/MergeTree/MergeTreeRangeReader.cpp index c142530de58..d71dddc938c 100644 --- a/src/Storages/MergeTree/MergeTreeRangeReader.cpp +++ b/src/Storages/MergeTree/MergeTreeRangeReader.cpp @@ -11,6 +11,12 @@ #include #endif +#if defined(__aarch64__) && defined(__ARM_NEON) +# include +# ifdef HAS_RESERVED_IDENTIFIER +# pragma clang diagnostic ignored "-Wreserved-identifier" +# endif +#endif namespace DB { @@ -551,6 +557,34 @@ size_t MergeTreeRangeReader::ReadResult::numZerosInTail(const UInt8 * begin, con return count; } } +#elif defined(__aarch64__) && defined(__ARM_NEON) + const uint8x16_t bitmask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; + while (end - begin >= 64) + { + end -= 64; + const auto * src = reinterpret_cast(end); + const uint8x16_t p0 = vceqzq_u8(vld1q_u8(src)); + const uint8x16_t p1 = vceqzq_u8(vld1q_u8(src + 16)); + const uint8x16_t p2 = vceqzq_u8(vld1q_u8(src + 32)); + const uint8x16_t p3 = vceqzq_u8(vld1q_u8(src + 48)); + uint8x16_t t0 = vandq_u8(p0, bitmask); + uint8x16_t t1 = vandq_u8(p1, bitmask); + uint8x16_t t2 = vandq_u8(p2, bitmask); + uint8x16_t t3 = vandq_u8(p3, bitmask); + uint8x16_t sum0 = vpaddq_u8(t0, t1); + uint8x16_t sum1 = vpaddq_u8(t2, t3); + sum0 = vpaddq_u8(sum0, sum1); + sum0 = vpaddq_u8(sum0, sum0); + UInt64 val = vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0); + val = ~val; + if (val == 0) + count += 64; + else + { + count += __builtin_clzll(val); + return count; + } + } #endif while (end > begin && *(--end) == 0) From 82ebf869a7c95a0d41ace2256800ba5547a4a5eb Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 15 Jun 2022 13:40:30 +0000 Subject: [PATCH 114/204] Fixing style --- src/Client/ConnectionPoolWithFailover.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Client/ConnectionPoolWithFailover.cpp b/src/Client/ConnectionPoolWithFailover.cpp index a2bc4ca5059..f2a07b64432 100644 --- a/src/Client/ConnectionPoolWithFailover.cpp +++ b/src/Client/ConnectionPoolWithFailover.cpp @@ -20,6 +20,7 @@ namespace DB namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int ALL_CONNECTION_TRIES_FAILED; } From ac0b7ab20b5e257db16b8da8b3c0467a40381a80 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 15 Jun 2022 13:46:27 +0000 Subject: [PATCH 115/204] Fix backwards compatibility with older snapshots --- src/Coordination/KeeperSnapshotManager.cpp | 5 +++++ src/Coordination/KeeperStorage.cpp | 7 ++++--- src/Coordination/KeeperStorage.h | 7 +++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Coordination/KeeperSnapshotManager.cpp b/src/Coordination/KeeperSnapshotManager.cpp index 7df5e430f17..07d351cb73c 100644 --- a/src/Coordination/KeeperSnapshotManager.cpp +++ b/src/Coordination/KeeperSnapshotManager.cpp @@ -268,9 +268,14 @@ void KeeperStorageSnapshot::deserialize(SnapshotDeserializationResult & deserial recalculate_digest = false; } } + + storage.old_snapshot_zxid = 0; } else + { storage.zxid = deserialization_result.snapshot_meta->get_last_log_idx(); + storage.old_snapshot_zxid = storage.zxid; + } int64_t session_id; readBinary(session_id, in); diff --git a/src/Coordination/KeeperStorage.cpp b/src/Coordination/KeeperStorage.cpp index 15ad2d4aff6..21265f0bd61 100644 --- a/src/Coordination/KeeperStorage.cpp +++ b/src/Coordination/KeeperStorage.cpp @@ -1769,9 +1769,10 @@ void KeeperStorage::preprocessRequest( if (uncommitted_transactions.empty()) { - if (new_last_zxid <= last_zxid) + // if we have no uncommitted transactions it means the last zxid is possibly loaded from snapshot + if (last_zxid != old_snapshot_zxid && new_last_zxid <= last_zxid) throw Exception( - ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, last_zxid); + ErrorCodes::LOGICAL_ERROR, "Got new ZXID ({}) smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, last_zxid); } else { @@ -1781,7 +1782,7 @@ void KeeperStorage::preprocessRequest( if (new_last_zxid <= last_zxid) throw Exception( - ErrorCodes::LOGICAL_ERROR, "Got new ZXID {} smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, last_zxid); + ErrorCodes::LOGICAL_ERROR, "Got new ZXID ({}) smaller or equal to current ZXID ({}). It's a bug", new_last_zxid, last_zxid); } std::vector new_deltas; diff --git a/src/Coordination/KeeperStorage.h b/src/Coordination/KeeperStorage.h index c8bf3841207..f4fccd95adb 100644 --- a/src/Coordination/KeeperStorage.h +++ b/src/Coordination/KeeperStorage.h @@ -298,6 +298,13 @@ public: /// Global id of all requests applied to storage int64_t zxid{0}; + // older Keeper node (pre V5 snapshots) can create snapshots and receive logs from newer Keeper nodes + // this can lead to some inconsistencies, e.g. from snapshot it will use log_idx as zxid + // while the log will have a smaller zxid because it's generated by the newer nodes + // we save the value loaded from snapshot to know when is it okay to have + // smaller zxid in newer requests + int64_t old_snapshot_zxid{0}; + struct TransactionInfo { int64_t zxid; From 5bfb15262c1ba2ffb68ec7003edc9a4fa6356b9d Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Jun 2022 17:25:38 +0300 Subject: [PATCH 116/204] Revert "More parallel execution for queries with `FINAL` (#36396)" This reverts commit c8afeafe0e974a0070f04a056705fabc75cc998e. --- src/Processors/Merges/IMergingTransform.cpp | 65 +++++ src/Processors/Merges/IMergingTransform.h | 10 + src/Processors/QueryPlan/IQueryPlanStep.cpp | 3 - src/Processors/QueryPlan/PartsSplitter.cpp | 274 ------------------ src/Processors/QueryPlan/PartsSplitter.h | 25 -- .../QueryPlan/ReadFromMergeTree.cpp | 138 +++++---- .../Transforms/AddingSelectorTransform.cpp | 76 +++++ .../Transforms/AddingSelectorTransform.h | 26 ++ .../Transforms/FilterSortedStreamByRange.h | 66 ----- src/Processors/Transforms/FilterTransform.h | 1 + src/Processors/Transforms/SelectorInfo.h | 14 + src/QueryPipeline/printPipeline.h | 5 +- tests/performance/parallel_final.xml | 5 - .../01861_explain_pipeline.reference | 17 +- .../02286_parallel_final.reference | 9 - .../0_stateless/02286_parallel_final.sh | 31 -- 16 files changed, 280 insertions(+), 485 deletions(-) delete mode 100644 src/Processors/QueryPlan/PartsSplitter.cpp delete mode 100644 src/Processors/QueryPlan/PartsSplitter.h create mode 100644 src/Processors/Transforms/AddingSelectorTransform.cpp create mode 100644 src/Processors/Transforms/AddingSelectorTransform.h delete mode 100644 src/Processors/Transforms/FilterSortedStreamByRange.h create mode 100644 src/Processors/Transforms/SelectorInfo.h delete mode 100644 tests/queries/0_stateless/02286_parallel_final.reference delete mode 100755 tests/queries/0_stateless/02286_parallel_final.sh diff --git a/src/Processors/Merges/IMergingTransform.cpp b/src/Processors/Merges/IMergingTransform.cpp index 226f55b3e92..f09c7c5339f 100644 --- a/src/Processors/Merges/IMergingTransform.cpp +++ b/src/Processors/Merges/IMergingTransform.cpp @@ -1,4 +1,5 @@ #include +#include namespace DB { @@ -180,4 +181,68 @@ IProcessor::Status IMergingTransformBase::prepare() return Status::Ready; } +static void filterChunk(IMergingAlgorithm::Input & input, size_t selector_position) +{ + if (!input.chunk.getChunkInfo()) + throw Exception("IMergingTransformBase expected ChunkInfo for input chunk", ErrorCodes::LOGICAL_ERROR); + + const auto * chunk_info = typeid_cast(input.chunk.getChunkInfo().get()); + if (!chunk_info) + throw Exception("IMergingTransformBase expected SelectorInfo for input chunk", ErrorCodes::LOGICAL_ERROR); + + const auto & selector = chunk_info->selector; + + IColumn::Filter filter; + filter.resize_fill(selector.size()); + + size_t num_rows = input.chunk.getNumRows(); + auto columns = input.chunk.detachColumns(); + + size_t num_result_rows = 0; + + for (size_t row = 0; row < num_rows; ++row) + { + if (selector[row] == selector_position) + { + ++num_result_rows; + filter[row] = 1; + } + } + + if (!filter.empty() && filter.back() == 0) + { + filter.back() = 1; + ++num_result_rows; + input.skip_last_row = true; + } + + for (auto & column : columns) + column = column->filter(filter, num_result_rows); + + input.chunk.clear(); + input.chunk.setColumns(std::move(columns), num_result_rows); +} + +void IMergingTransformBase::filterChunks() +{ + if (state.selector_position < 0) + return; + + if (!state.init_chunks.empty()) + { + for (size_t i = 0; i < input_states.size(); ++i) + { + auto & input = state.init_chunks[i]; + if (!input.chunk) + continue; + + filterChunk(input, state.selector_position); + } + } + + if (state.has_input) + filterChunk(state.input_chunk, state.selector_position); +} + + } diff --git a/src/Processors/Merges/IMergingTransform.h b/src/Processors/Merges/IMergingTransform.h index 144c47c96f5..ea6f6aed37f 100644 --- a/src/Processors/Merges/IMergingTransform.h +++ b/src/Processors/Merges/IMergingTransform.h @@ -28,10 +28,17 @@ public: Status prepare() override; + /// Set position which will be used in selector if input chunk has attached SelectorInfo (see SelectorInfo.h). + /// Columns will be filtered, keep only rows labeled with this position. + /// It is used in parallel final. + void setSelectorPosition(size_t position) { state.selector_position = position; } + protected: virtual void onNewInput(); /// Is called when new input is added. Only if have_all_inputs = false. virtual void onFinish() {} /// Is called when all data is processed. + void filterChunks(); /// Filter chunks if selector position was set. For parallel final. + /// Processor state. struct State { @@ -43,6 +50,7 @@ protected: size_t next_input_to_read = 0; IMergingAlgorithm::Inputs init_chunks; + ssize_t selector_position = -1; }; State state; @@ -84,6 +92,8 @@ public: void work() override { + filterChunks(); + if (!state.init_chunks.empty()) algorithm.initialize(std::move(state.init_chunks)); diff --git a/src/Processors/QueryPlan/IQueryPlanStep.cpp b/src/Processors/QueryPlan/IQueryPlanStep.cpp index b36d1f0e12f..f06897e8488 100644 --- a/src/Processors/QueryPlan/IQueryPlanStep.cpp +++ b/src/Processors/QueryPlan/IQueryPlanStep.cpp @@ -86,9 +86,6 @@ static void doDescribeProcessor(const IProcessor & processor, size_t count, IQue doDescribeHeader(*last_header, num_equal_headers, settings); } - if (!processor.getDescription().empty()) - settings.out << String(settings.offset, settings.indent_char) << "Description: " << processor.getDescription() << '\n'; - settings.offset += settings.indent; } diff --git a/src/Processors/QueryPlan/PartsSplitter.cpp b/src/Processors/QueryPlan/PartsSplitter.cpp deleted file mode 100644 index 25574c6dcc5..00000000000 --- a/src/Processors/QueryPlan/PartsSplitter.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace DB; - -namespace -{ - -using Value = std::vector; - -std::string toString(const Value & value) -{ - return fmt::format("({})", fmt::join(value, ", ")); -} - -/// Adaptor to access PK values from index. -class IndexAccess -{ -public: - explicit IndexAccess(const RangesInDataParts & parts_) : parts(parts_) { } - - Value getValue(size_t part_idx, size_t mark) const - { - const auto & index = parts[part_idx].data_part->index; - Value value(index.size()); - for (size_t i = 0; i < value.size(); ++i) - index[i]->get(mark, value[i]); - return value; - } - - size_t getMarkRows(size_t part_idx, size_t mark) const { return parts[part_idx].data_part->index_granularity.getMarkRows(mark); } - - size_t getTotalRowCount() const - { - size_t total = 0; - for (const auto & part : parts) - total += part.getRowsCount(); - return total; - } - -private: - const RangesInDataParts & parts; -}; - - -/// Splits parts into layers, each layer will contain parts subranges with PK values from its own range. -/// Will try to produce exactly max_layer layers but may return less if data is distributed in not a very parallelizable way. -std::pair, std::vector> split(RangesInDataParts parts, size_t max_layers) -{ - // We will advance the iterator pointing to the mark with the smallest PK value until there will be not less than rows_per_layer rows in the current layer (roughly speaking). - // Then we choose the last observed value as the new border, so the current layer will consists of granules with values greater than the previous mark and less or equal - // than the new border. - - struct PartsRangesIterator - { - struct RangeInDataPart : MarkRange - { - size_t part_idx; - }; - - enum class EventType - { - RangeBeginning, - RangeEnding, - }; - - bool operator<(const PartsRangesIterator & other) const { return std::tie(value, event) > std::tie(other.value, other.event); } - - Value value; - RangeInDataPart range; - EventType event; - }; - - const auto index_access = std::make_unique(parts); - std::priority_queue parts_ranges_queue; - for (size_t part_idx = 0; part_idx < parts.size(); ++part_idx) - { - for (const auto & range : parts[part_idx].ranges) - { - parts_ranges_queue.push( - {index_access->getValue(part_idx, range.begin), {range, part_idx}, PartsRangesIterator::EventType::RangeBeginning}); - const auto & index_granularity = parts[part_idx].data_part->index_granularity; - if (index_granularity.hasFinalMark() && range.end + 1 == index_granularity.getMarksCount()) - parts_ranges_queue.push( - {index_access->getValue(part_idx, range.end), {range, part_idx}, PartsRangesIterator::EventType::RangeEnding}); - } - } - - /// The beginning of currently started (but not yet finished) range of marks of a part in the current layer. - std::unordered_map current_part_range_begin; - /// The current ending of a range of marks of a part in the current layer. - std::unordered_map current_part_range_end; - - /// Determine borders between layers. - std::vector borders; - std::vector result_layers; - - const size_t rows_per_layer = std::max(index_access->getTotalRowCount() / max_layers, 1); - - while (!parts_ranges_queue.empty()) - { - // New layer should include last granules of still open ranges from the previous layer, - // because they may already contain values greater than the last border. - size_t rows_in_current_layer = 0; - size_t marks_in_current_layer = 0; - - // Intersection between the current and next layers is just the last observed marks of each still open part range. Ratio is empirical. - auto layers_intersection_is_too_big = [&]() - { - const auto intersected_parts = current_part_range_end.size(); - return marks_in_current_layer < intersected_parts * 2; - }; - - result_layers.emplace_back(); - - while (rows_in_current_layer < rows_per_layer || layers_intersection_is_too_big() || result_layers.size() == max_layers) - { - // We're advancing iterators until a new value showed up. - Value last_value; - while (!parts_ranges_queue.empty() && (last_value.empty() || last_value == parts_ranges_queue.top().value)) - { - auto current = parts_ranges_queue.top(); - parts_ranges_queue.pop(); - const auto part_idx = current.range.part_idx; - - if (current.event == PartsRangesIterator::EventType::RangeEnding) - { - result_layers.back().emplace_back( - parts[part_idx].data_part, - parts[part_idx].part_index_in_query, - MarkRanges{{current_part_range_begin[part_idx], current.range.end}}); - current_part_range_begin.erase(part_idx); - current_part_range_end.erase(part_idx); - continue; - } - - last_value = std::move(current.value); - rows_in_current_layer += index_access->getMarkRows(part_idx, current.range.begin); - marks_in_current_layer++; - current_part_range_begin.try_emplace(part_idx, current.range.begin); - current_part_range_end[part_idx] = current.range.begin; - if (current.range.begin + 1 < current.range.end) - { - current.range.begin++; - current.value = index_access->getValue(part_idx, current.range.begin); - parts_ranges_queue.push(std::move(current)); - } - } - if (parts_ranges_queue.empty()) - break; - if (rows_in_current_layer >= rows_per_layer && !layers_intersection_is_too_big() && result_layers.size() < max_layers) - borders.push_back(last_value); - } - for (const auto & [part_idx, last_mark] : current_part_range_end) - { - result_layers.back().emplace_back( - parts[part_idx].data_part, - parts[part_idx].part_index_in_query, - MarkRanges{{current_part_range_begin[part_idx], last_mark + 1}}); - current_part_range_begin[part_idx] = current_part_range_end[part_idx]; - } - } - for (auto & layer : result_layers) - { - std::stable_sort( - layer.begin(), - layer.end(), - [](const auto & lhs, const auto & rhs) { return lhs.part_index_in_query < rhs.part_index_in_query; }); - } - - return std::make_pair(std::move(borders), std::move(result_layers)); -} - - -/// Will return borders.size()+1 filters in total, i-th filter will accept rows with PK values within the range [borders[i-1], borders[i]). -std::vector buildFilters(const KeyDescription & primary_key, const std::vector & borders) -{ - auto add_and_condition = [&](ASTPtr & result, const ASTPtr & foo) { result = !result ? foo : makeASTFunction("and", result, foo); }; - - /// Produces ASTPtr to predicate (pk_col0, pk_col1, ... , pk_colN) > (value[0], value[1], ... , value[N]) - auto lexicographically_greater = [&](const Value & value) - { - // PK may contain functions of the table columns, so we need the actual PK AST with all expressions it contains. - ASTPtr pk_columns_as_tuple = makeASTFunction("tuple", primary_key.expression_list_ast->children); - - ASTPtr value_ast = std::make_shared(); - for (size_t i = 0; i < value.size(); ++i) - { - const auto & types = primary_key.data_types; - ASTPtr component_ast = std::make_shared(value[i]); - // Values of some types (e.g. Date, DateTime) are stored in columns as numbers and we get them as just numbers from the index. - // So we need an explicit Cast for them. - if (isColumnedAsNumber(types.at(i)->getTypeId()) && !isNumber(types.at(i)->getTypeId())) - component_ast = makeASTFunction("cast", std::move(component_ast), std::make_shared(types.at(i)->getName())); - value_ast->children.push_back(std::move(component_ast)); - } - ASTPtr value_as_tuple = makeASTFunction("tuple", value_ast->children); - - return makeASTFunction("greater", pk_columns_as_tuple, value_as_tuple); - }; - - std::vector filters(borders.size() + 1); - for (size_t layer = 0; layer <= borders.size(); ++layer) - { - if (layer > 0) - add_and_condition(filters[layer], lexicographically_greater(borders[layer - 1])); - if (layer < borders.size()) - add_and_condition(filters[layer], makeASTFunction("not", lexicographically_greater(borders[layer]))); - } - return filters; -} -} - - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - -Pipes buildPipesForReadingByPKRanges( - const KeyDescription & primary_key, - RangesInDataParts parts, - size_t max_layers, - ContextPtr context, - ReadingInOrderStepGetter && reading_step_getter) -{ - if (max_layers <= 1) - throw Exception(ErrorCodes::LOGICAL_ERROR, "max_layer should be greater than 1."); - - auto && [borders, result_layers] = split(std::move(parts), max_layers); - auto filters = buildFilters(primary_key, borders); - Pipes pipes(result_layers.size()); - for (size_t i = 0; i < result_layers.size(); ++i) - { - pipes[i] = reading_step_getter(std::move(result_layers[i])); - auto & filter_function = filters[i]; - if (!filter_function) - continue; - auto syntax_result = TreeRewriter(context).analyze(filter_function, primary_key.expression->getRequiredColumnsWithTypes()); - auto actions = ExpressionAnalyzer(filter_function, syntax_result, context).getActionsDAG(false); - ExpressionActionsPtr expression_actions = std::make_shared(std::move(actions)); - auto description = fmt::format( - "filter values in [{}, {})", i ? ::toString(borders[i - 1]) : "-inf", i < borders.size() ? ::toString(borders[i]) : "+inf"); - auto pk_expression = std::make_shared(primary_key.expression->getActionsDAG().clone()); - pipes[i].addSimpleTransform([pk_expression](const Block & header) - { return std::make_shared(header, pk_expression); }); - pipes[i].addSimpleTransform( - [&](const Block & header) - { - auto step = std::make_shared(header, expression_actions, filter_function->getColumnName(), true); - step->setDescription(description); - return step; - }); - } - return pipes; -} - -} diff --git a/src/Processors/QueryPlan/PartsSplitter.h b/src/Processors/QueryPlan/PartsSplitter.h deleted file mode 100644 index 56bca688c2d..00000000000 --- a/src/Processors/QueryPlan/PartsSplitter.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - - -namespace DB -{ - -using ReadingInOrderStepGetter = std::function; - -/// Splits parts into layers, each layer will contain parts subranges with PK values from its own range. -/// A separate pipe will be constructed for each layer with a reading step (provided by the reading_step_getter) and a filter for this layer's range of PK values. -/// Will try to produce exactly max_layer pipes but may return less if data is distributed in not a very parallelizable way. -Pipes buildPipesForReadingByPKRanges( - const KeyDescription & primary_key, - RangesInDataParts parts, - size_t max_layers, - ContextPtr context, - ReadingInOrderStepGetter && reading_step_getter); -} diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 1caee41160f..8adaf2f1027 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -1,16 +1,14 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -18,22 +16,17 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include #include +#include +#include +#include #include #include -#include #include namespace ProfileEvents @@ -567,6 +560,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsWithOrder( static void addMergingFinal( Pipe & pipe, + size_t num_output_streams, const SortDescription & sort_description, MergeTreeData::MergingParams merging_params, Names partition_key_columns, @@ -613,7 +607,56 @@ static void addMergingFinal( __builtin_unreachable(); }; - pipe.addTransform(get_merging_processor()); + if (num_output_streams <= 1 || sort_description.empty()) + { + pipe.addTransform(get_merging_processor()); + return; + } + + ColumnNumbers key_columns; + key_columns.reserve(sort_description.size()); + for (const auto & desc : sort_description) + key_columns.push_back(header.getPositionByName(desc.column_name)); + + pipe.addSimpleTransform([&](const Block & stream_header) + { + return std::make_shared(stream_header, num_output_streams, key_columns); + }); + + pipe.transform([&](OutputPortRawPtrs ports) + { + Processors transforms; + std::vector output_ports; + transforms.reserve(ports.size() + num_output_streams); + output_ports.reserve(ports.size()); + + for (auto & port : ports) + { + auto copier = std::make_shared(header, num_output_streams); + connect(*port, copier->getInputPort()); + output_ports.emplace_back(copier->getOutputs().begin()); + transforms.emplace_back(std::move(copier)); + } + + for (size_t i = 0; i < num_output_streams; ++i) + { + auto merge = get_merging_processor(); + merge->setSelectorPosition(i); + auto input = merge->getInputs().begin(); + + /// Connect i-th merge with i-th input port of every copier. + for (size_t j = 0; j < ports.size(); ++j) + { + connect(*output_ports[j], *input); + ++output_ports[j]; + ++input; + } + + transforms.emplace_back(std::move(merge)); + } + + return transforms; + }); } @@ -667,7 +710,8 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( for (size_t range_index = 0; range_index < parts_to_merge_ranges.size() - 1; ++range_index) { - Pipes pipes; + Pipe pipe; + { RangesInDataParts new_parts; @@ -694,39 +738,21 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( if (new_parts.empty()) continue; - if (num_streams > 1 && metadata_for_reading->hasPrimaryKey()) - { - // Let's split parts into layers to ensure data parallelism of final. - auto reading_step_getter = [this, &column_names, &info](auto parts) - { - return read( - std::move(parts), - column_names, - ReadFromMergeTree::ReadType::InOrder, - 1 /* num_streams */, - 0 /* min_marks_for_concurrent_read */, - info.use_uncompressed_cache); - }; - pipes = buildPipesForReadingByPKRanges( - metadata_for_reading->getPrimaryKey(), std::move(new_parts), num_streams, context, std::move(reading_step_getter)); - } - else - { - pipes.emplace_back(read( - std::move(new_parts), column_names, ReadFromMergeTree::ReadType::InOrder, num_streams, 0, info.use_uncompressed_cache)); - } + pipe = read(std::move(new_parts), column_names, ReadFromMergeTree::ReadType::InOrder, + num_streams, 0, info.use_uncompressed_cache); /// Drop temporary columns, added by 'sorting_key_expr' if (!out_projection) - out_projection = createProjection(pipes.front().getHeader()); + out_projection = createProjection(pipe.getHeader()); } auto sorting_expr = std::make_shared( metadata_for_reading->getSortingKey().expression->getActionsDAG().clone()); - for (auto & pipe : pipes) - pipe.addSimpleTransform([sorting_expr](const Block & header) - { return std::make_shared(header, sorting_expr); }); + pipe.addSimpleTransform([sorting_expr](const Block & header) + { + return std::make_shared(header, sorting_expr); + }); /// If do_not_merge_across_partitions_select_final is true and there is only one part in partition /// with level > 0 then we won't postprocess this part @@ -734,7 +760,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( std::distance(parts_to_merge_ranges[range_index], parts_to_merge_ranges[range_index + 1]) == 1 && parts_to_merge_ranges[range_index]->data_part->info.level > 0) { - partition_pipes.emplace_back(Pipe::unitePipes(std::move(pipes))); + partition_pipes.emplace_back(std::move(pipe)); continue; } @@ -751,21 +777,21 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( for (size_t i = 0; i < sort_columns_size; ++i) sort_description.emplace_back(sort_columns[i], 1, 1); - for (auto & pipe : pipes) - addMergingFinal( - pipe, - sort_description, - data.merging_params, - partition_key_columns, - max_block_size); + addMergingFinal( + pipe, + std::min(num_streams, settings.max_final_threads), + sort_description, data.merging_params, partition_key_columns, max_block_size); - partition_pipes.emplace_back(Pipe::unitePipes(std::move(pipes))); + partition_pipes.emplace_back(std::move(pipe)); } if (!lonely_parts.empty()) { + RangesInDataParts new_parts; + size_t num_streams_for_lonely_parts = num_streams * lonely_parts.size(); + const size_t min_marks_for_concurrent_read = MergeTreeDataSelectExecutor::minMarksForConcurrentRead( settings.merge_tree_min_rows_for_concurrent_read, settings.merge_tree_min_bytes_for_concurrent_read, diff --git a/src/Processors/Transforms/AddingSelectorTransform.cpp b/src/Processors/Transforms/AddingSelectorTransform.cpp new file mode 100644 index 00000000000..f75a5920072 --- /dev/null +++ b/src/Processors/Transforms/AddingSelectorTransform.cpp @@ -0,0 +1,76 @@ +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +AddingSelectorTransform::AddingSelectorTransform( + const Block & header, size_t num_outputs_, ColumnNumbers key_columns_) + : ISimpleTransform(header, header, false) + , num_outputs(num_outputs_) + , key_columns(std::move(key_columns_)) + , hash(0) +{ + setInputNotNeededAfterRead(false); + + if (num_outputs <= 1) + throw Exception("SplittingByHashTransform expects more than 1 outputs, got " + std::to_string(num_outputs), + ErrorCodes::LOGICAL_ERROR); + + if (key_columns.empty()) + throw Exception("SplittingByHashTransform cannot split by empty set of key columns", + ErrorCodes::LOGICAL_ERROR); + + for (auto & column : key_columns) + if (column >= header.columns()) + throw Exception("Invalid column number: " + std::to_string(column) + + ". There is only " + std::to_string(header.columns()) + " columns in header", + ErrorCodes::LOGICAL_ERROR); +} + +static void calculateWeakHash32(const Chunk & chunk, const ColumnNumbers & key_columns, WeakHash32 & hash) +{ + auto num_rows = chunk.getNumRows(); + const auto & columns = chunk.getColumns(); + + hash.reset(num_rows); + + for (const auto & column_number : key_columns) + columns[column_number]->updateWeakHash32(hash); +} + +static IColumn::Selector fillSelector(const WeakHash32 & hash, size_t num_outputs) +{ + /// Row from interval [(2^32 / num_outputs) * i, (2^32 / num_outputs) * (i + 1)) goes to bucket with number i. + + const auto & hash_data = hash.getData(); + size_t num_rows = hash_data.size(); + IColumn::Selector selector(num_rows); + + for (size_t row = 0; row < num_rows; ++row) + { + selector[row] = hash_data[row]; /// [0, 2^32) + selector[row] *= num_outputs; /// [0, num_outputs * 2^32), selector stores 64 bit values. + selector[row] >>= 32u; /// [0, num_outputs) + } + + return selector; +} + +void AddingSelectorTransform::transform(Chunk & input_chunk, Chunk & output_chunk) +{ + auto chunk_info = std::make_shared(); + + calculateWeakHash32(input_chunk, key_columns, hash); + chunk_info->selector = fillSelector(hash, num_outputs); + + input_chunk.swap(output_chunk); + output_chunk.setChunkInfo(std::move(chunk_info)); +} + +} diff --git a/src/Processors/Transforms/AddingSelectorTransform.h b/src/Processors/Transforms/AddingSelectorTransform.h new file mode 100644 index 00000000000..bad97adfa76 --- /dev/null +++ b/src/Processors/Transforms/AddingSelectorTransform.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include +#include + +namespace DB +{ + +/// Add IColumn::Selector to chunk (see SelectorInfo.h). +/// Selector is filled by formula (WeakHash(key_columns) * num_outputs / MAX_INT). +class AddingSelectorTransform : public ISimpleTransform +{ +public: + AddingSelectorTransform(const Block & header, size_t num_outputs_, ColumnNumbers key_columns_); + String getName() const override { return "AddingSelector"; } + void transform(Chunk & input_chunk, Chunk & output_chunk) override; + +private: + size_t num_outputs; + ColumnNumbers key_columns; + + WeakHash32 hash; +}; + +} diff --git a/src/Processors/Transforms/FilterSortedStreamByRange.h b/src/Processors/Transforms/FilterSortedStreamByRange.h deleted file mode 100644 index adbc0626abb..00000000000 --- a/src/Processors/Transforms/FilterSortedStreamByRange.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace DB -{ - -/// Could be used when the predicate given by expression_ is true only on one continuous range of values and input is monotonous by that value. -/// The following optimization applies: when a new chunk of data comes in we firstly execute the expression_ only on the first and the last row. -/// If it evaluates to true on both rows then the whole chunk is immediately passed to further steps. -/// Otherwise, we apply the expression_ to all rows. -class FilterSortedStreamByRange : public ISimpleTransform -{ -public: - FilterSortedStreamByRange( - const Block & header_, - ExpressionActionsPtr expression_, - String filter_column_name_, - bool remove_filter_column_, - bool on_totals_ = false) - : ISimpleTransform( - header_, - FilterTransform::transformHeader(header_, expression_->getActionsDAG(), filter_column_name_, remove_filter_column_), - true) - , filter_transform(header_, expression_, filter_column_name_, remove_filter_column_, on_totals_) - { - } - - String getName() const override { return "FilterSortedStreamByRange"; } - - void transform(Chunk & chunk) override - { - int rows_before_filtration = chunk.getNumRows(); - if (rows_before_filtration < 2) - { - filter_transform.transform(chunk); - return; - } - - // Evaluate expression on just the first and the last row. - // If both of them satisfies conditions, than skip calculation for all the rows in between. - auto quick_check_columns = chunk.cloneEmptyColumns(); - auto src_columns = chunk.detachColumns(); - for (auto row : {0, rows_before_filtration - 1}) - for (size_t col = 0; col < quick_check_columns.size(); ++col) - quick_check_columns[col]->insertFrom(*src_columns[col].get(), row); - chunk.setColumns(std::move(quick_check_columns), 2); - filter_transform.transform(chunk); - const bool all_rows_will_pass_filter = chunk.getNumRows() == 2; - - chunk.setColumns(std::move(src_columns), rows_before_filtration); - - // Not all rows satisfy conditions. - if (!all_rows_will_pass_filter) - filter_transform.transform(chunk); - } - -private: - FilterTransform filter_transform; -}; - - -} diff --git a/src/Processors/Transforms/FilterTransform.h b/src/Processors/Transforms/FilterTransform.h index 3340fe230b7..39f1f1c42db 100644 --- a/src/Processors/Transforms/FilterTransform.h +++ b/src/Processors/Transforms/FilterTransform.h @@ -32,6 +32,7 @@ public: Status prepare() override; +protected: void transform(Chunk & chunk) override; private: diff --git a/src/Processors/Transforms/SelectorInfo.h b/src/Processors/Transforms/SelectorInfo.h new file mode 100644 index 00000000000..2876d64ed28 --- /dev/null +++ b/src/Processors/Transforms/SelectorInfo.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +namespace DB +{ + +/// ChunkInfo with IColumn::Selector. It is added by AddingSelectorTransform. +struct SelectorInfo : public ChunkInfo +{ + IColumn::Selector selector; +}; + +} diff --git a/src/QueryPipeline/printPipeline.h b/src/QueryPipeline/printPipeline.h index 4b947149c7c..6ff5fb24c37 100644 --- a/src/QueryPipeline/printPipeline.h +++ b/src/QueryPipeline/printPipeline.h @@ -28,10 +28,7 @@ void printPipeline(const Processors & processors, const Statuses & statuses, Wri /// Nodes // TODO quoting and escaping for (const auto & processor : processors) { - auto description = processor->getDescription(); - if (!description.empty()) - description = ": " + description; - out << " n" << get_proc_id(*processor) << "[label=\"" << processor->getName() << description; + out << " n" << get_proc_id(*processor) << "[label=\"" << processor->getName() << processor->getDescription(); if (statuses_iter != statuses.end()) { diff --git a/tests/performance/parallel_final.xml b/tests/performance/parallel_final.xml index ca84ed52a04..775926d1ee8 100644 --- a/tests/performance/parallel_final.xml +++ b/tests/performance/parallel_final.xml @@ -18,7 +18,6 @@ collapsing_final_16p_str_keys_rnd collapsing_final_1024p_ord collapsing_final_1024p_rnd - collapsing_final_1p_ord @@ -31,7 +30,6 @@ create table collapsing_final_16p_str_keys_rnd (key1 UInt32, key2 String, key3 String, key4 String, key5 String, key6 String, key7 String, key8 String, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1, key2, key3, key4, key5, key6, key7, key8) partition by key1 % 16 create table collapsing_final_1024p_ord (key1 UInt32, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1) partition by intDiv(key1, 8192 * 2) create table collapsing_final_1024p_rnd (key1 UInt32, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1) partition by key1 % 1024 - create table collapsing_final_1p_ord (key1 UInt64, key2 UInt64, sign Int8, s UInt64) engine = CollapsingMergeTree(sign) order by (key1, key2) insert into collapsing_final_16p_ord select number, number, 1, number from numbers_mt(8388608) @@ -45,9 +43,6 @@ insert into collapsing_final_1024p_ord select number, 1, number from numbers_mt(16777216) insert into collapsing_final_1024p_rnd select number, 1, number from numbers_mt(16777216) - - insert into collapsing_final_1p_ord select number, number + 1, 1, number from numbers_mt(5e7) - optimize table {collapsing} final SELECT count() FROM {collapsing} final diff --git a/tests/queries/0_stateless/01861_explain_pipeline.reference b/tests/queries/0_stateless/01861_explain_pipeline.reference index aec3ae06dce..2ba294d7e4d 100644 --- a/tests/queries/0_stateless/01861_explain_pipeline.reference +++ b/tests/queries/0_stateless/01861_explain_pipeline.reference @@ -16,15 +16,8 @@ ExpressionTransform ExpressionTransform × 2 (ReadFromMergeTree) ExpressionTransform × 2 - ReplacingSorted - ExpressionTransform - FilterSortedStreamByRange - Description: filter values in [(5), +inf) - ExpressionTransform - MergeTreeInOrder 0 → 1 - ReplacingSorted 2 → 1 - ExpressionTransform × 2 - FilterSortedStreamByRange × 2 - Description: filter values in [-inf, (5)) - ExpressionTransform × 2 - MergeTreeInOrder × 2 0 → 1 + ReplacingSorted × 2 2 → 1 + Copy × 2 1 → 2 + AddingSelector × 2 + ExpressionTransform × 2 + MergeTreeInOrder × 2 0 → 1 diff --git a/tests/queries/0_stateless/02286_parallel_final.reference b/tests/queries/0_stateless/02286_parallel_final.reference deleted file mode 100644 index f6573cb9042..00000000000 --- a/tests/queries/0_stateless/02286_parallel_final.reference +++ /dev/null @@ -1,9 +0,0 @@ -2 -2 -3 -5 -8 -8 -8 -8 -8 diff --git a/tests/queries/0_stateless/02286_parallel_final.sh b/tests/queries/0_stateless/02286_parallel_final.sh deleted file mode 100755 index 6686a5d3e33..00000000000 --- a/tests/queries/0_stateless/02286_parallel_final.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -# shellcheck source=../shell_config.sh -. "$CURDIR"/../shell_config.sh - -test_random_values() { - layers=$1 - $CLICKHOUSE_CLIENT -n -q " - create table tbl_8parts_${layers}granules_rnd (key1 UInt32, sign Int8) engine = CollapsingMergeTree(sign) order by (key1) partition by (key1 % 8); - insert into tbl_8parts_${layers}granules_rnd select number, 1 from numbers_mt($((layers * 8 * 8192))); - explain pipeline select * from tbl_8parts_${layers}granules_rnd final settings max_threads = 16;" 2>&1 | - grep -c "CollapsingSortedTransform" -} - -for layers in 2 3 5 8; do - test_random_values $layers -done; - -test_sequential_values() { - layers=$1 - $CLICKHOUSE_CLIENT -n -q " - create table tbl_8parts_${layers}granules_seq (key1 UInt32, sign Int8) engine = CollapsingMergeTree(sign) order by (key1) partition by (key1 / $((layers * 8192)))::UInt64; - insert into tbl_8parts_${layers}granules_seq select number, 1 from numbers_mt($((layers * 8 * 8192))); - explain pipeline select * from tbl_8parts_${layers}granules_seq final settings max_threads = 8;" 2>&1 | - grep -c "CollapsingSortedTransform" -} - -for layers in 2 3 5 8 16; do - test_sequential_values $layers -done; From 14df2824067c34b2a7df2bf6cd7721087e80adda Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Wed, 15 Jun 2022 16:37:44 +0200 Subject: [PATCH 117/204] Add dispatch to CherryPick action --- .github/workflows/backport.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 620c698284e..66dddbee640 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -9,6 +9,8 @@ concurrency: on: # yamllint disable-line rule:truthy schedule: - cron: '0 */3 * * *' + workflow_dispatch: + jobs: CherryPick: runs-on: [self-hosted, style-checker] From 500f49972b787632e6c5a2e8489c614e6c731124 Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 15 Jun 2022 13:39:00 +0200 Subject: [PATCH 118/204] Extract diff from PR 36171 --- src/Common/FileCache.h | 430 ------------------ src/Common/FileCacheFactory.cpp | 3 +- src/Common/FileCacheSettings.cpp | 2 + src/Common/FileCacheSettings.h | 6 +- src/Common/FileSegment.cpp | 19 +- src/Common/FileSegment.h | 18 +- src/Common/IFileCache.cpp | 185 ++++++++ src/Common/IFileCache.h | 266 +++++++++++ .../{FileCache.cpp => LRUFileCache.cpp} | 248 ++++------ src/Common/LRUFileCache.h | 153 +++++++ src/Common/tests/gtest_lru_file_cache.cpp | 64 ++- src/Disks/IO/CachedReadBufferFromRemoteFS.cpp | 5 +- src/Disks/IO/CachedReadBufferFromRemoteFS.h | 5 +- .../ObjectStorages/DiskObjectStorage.cpp | 2 +- .../DiskObjectStorageCommon.cpp | 2 +- ...jectStorageRemoteMetadataRestoreHelper.cpp | 1 + src/Disks/ObjectStorages/IObjectStorage.cpp | 3 +- src/Disks/ObjectStorages/IObjectStorage.h | 2 +- .../ObjectStorages/S3/S3ObjectStorage.cpp | 2 +- src/IO/WriteBufferFromS3.cpp | 4 +- src/Interpreters/AsynchronousMetrics.cpp | 2 +- src/Interpreters/InterpreterSystemQuery.cpp | 6 +- .../MergeTree/MergeTreeDeduplicationLog.cpp | 1 + .../System/StorageSystemFilesystemCache.cpp | 6 +- .../System/StorageSystemRemoteDataPaths.cpp | 2 +- 25 files changed, 784 insertions(+), 653 deletions(-) delete mode 100644 src/Common/FileCache.h create mode 100644 src/Common/IFileCache.cpp create mode 100644 src/Common/IFileCache.h rename src/Common/{FileCache.cpp => LRUFileCache.cpp} (85%) create mode 100644 src/Common/LRUFileCache.h diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h deleted file mode 100644 index cefa193bacb..00000000000 --- a/src/Common/FileCache.h +++ /dev/null @@ -1,430 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "FileCache_fwd.h" -#include -#include -#include -#include - - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - -class IFileCache; -using FileCachePtr = std::shared_ptr; - -/** - * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - */ -class IFileCache : private boost::noncopyable -{ -friend class FileSegment; -friend struct FileSegmentsHolder; -friend class FileSegmentRangeWriter; - -public: - using Key = UInt128; - using Downloader = std::unique_ptr; - - IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); - - virtual ~IFileCache() = default; - - /// Restore cache from local filesystem. - virtual void initialize() = 0; - - virtual void remove(const Key & key) = 0; - - virtual void remove() = 0; - - static bool isReadOnly(); - - /// Cache capacity in bytes. - size_t capacity() const { return max_size; } - - static Key hash(const String & path); - - String getPathInLocalCache(const Key & key, size_t offset); - - String getPathInLocalCache(const Key & key); - - const String & getBasePath() const { return cache_base_path; } - - virtual std::vector tryGetCachePaths(const Key & key) = 0; - - /** - * Given an `offset` and `size` representing [offset, offset + size) bytes interval, - * return list of cached non-overlapping non-empty - * file segments `[segment1, ..., segmentN]` which intersect with given interval. - * - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * As long as pointers to returned file segments are hold - * it is guaranteed that these file segments are not removed from cache. - */ - virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) = 0; - - /** - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" - * from cache (not owned by cache), and as a result will never change it's state and will be destructed - * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change - * it's state (and become DOWNLOADED). - */ - virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegments getSnapshot() const = 0; - - /// For debug. - virtual String dumpStructure(const Key & key) = 0; - - virtual size_t getUsedCacheSize() const = 0; - - virtual size_t getFileSegmentsNum() const = 0; - -protected: - String cache_base_path; - size_t max_size; - size_t max_element_size; - size_t max_file_segment_size; - - bool is_initialized = false; - - mutable std::mutex mutex; - - class LRUQueue - { - public: - struct FileKeyAndOffset - { - Key key; - size_t offset; - size_t size; - size_t hits = 0; - - FileKeyAndOffset(const Key & key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) {} - }; - - using Iterator = typename std::list::iterator; - - size_t getTotalCacheSize(std::lock_guard & /* cache_lock */) const { return cache_size; } - - size_t getElementsNum(std::lock_guard & /* cache_lock */) const { return queue.size(); } - - Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); - - void remove(Iterator queue_it, std::lock_guard & cache_lock); - - void moveToEnd(Iterator queue_it, std::lock_guard & cache_lock); - - /// Space reservation for a file segment is incremental, so we need to be able to increment size of the queue entry. - void incrementSize(Iterator queue_it, size_t size_increment, std::lock_guard & cache_lock); - - String toString(std::lock_guard & cache_lock) const; - - bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) const; - - Iterator begin() { return queue.begin(); } - - Iterator end() { return queue.end(); } - - void removeAll(std::lock_guard & cache_lock); - - private: - std::list queue; - size_t cache_size = 0; - }; - - using AccessKeyAndOffset = std::pair; - - struct KeyAndOffsetHash - { - std::size_t operator()(const AccessKeyAndOffset & key) const - { - return std::hash()(key.first) ^ std::hash()(key.second); - } - }; - - using AccessRecord = std::unordered_map; - - /// Used to track and control the cache access of each query. - /// Through it, we can realize the processing of different queries by the cache layer. - struct QueryContext - { - LRUQueue lru_queue; - AccessRecord records; - - size_t cache_size = 0; - size_t max_cache_size; - - bool skip_download_if_exceeds_query_cache; - - QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) - : max_cache_size(max_cache_size_) - , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} - - void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) - { - if (cache_size < size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - { - lru_queue.remove(record->second, cache_lock); - records.erase({key, offset}); - } - } - cache_size -= size; - } - - void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) - { - if (cache_size + size > max_cache_size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Reserved cache size exceeds the remaining cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record == records.end()) - { - auto queue_iter = lru_queue.add(key, offset, 0, cache_lock); - record = records.insert({{key, offset}, queue_iter}).first; - } - record->second->size += size; - } - cache_size += size; - } - - void use(const Key & key, size_t offset, std::lock_guard & cache_lock) - { - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - lru_queue.moveToEnd(record->second, cache_lock); - } - } - - size_t getMaxCacheSize() { return max_cache_size; } - - size_t getCacheSize() { return cache_size; } - - LRUQueue & queue() { return lru_queue; } - - bool isSkipDownloadIfExceed() { return skip_download_if_exceeds_query_cache; } - }; - - using QueryContextPtr = std::shared_ptr; - using QueryContextMap = std::unordered_map; - - QueryContextMap query_map; - - bool enable_filesystem_query_cache_limit; - - QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); - - QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); - - void removeQueryContext(const String & query_id); - - QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); - - virtual bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) = 0; - - virtual void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - virtual bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - /// If file segment was partially downloaded and then space reservation fails (because of no - /// space left), then update corresponding cache cell metadata (file segment size). - virtual void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - void assertInitialized() const; - -public: - /// Save a query context information, and adopt different cache policies - /// for different queries through the context cache layer. - struct QueryContextHolder : private boost::noncopyable - { - explicit QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); - - QueryContextHolder() = default; - - ~QueryContextHolder(); - - String query_id {}; - IFileCache * cache = nullptr; - QueryContextPtr context = nullptr; - }; - - QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); -}; - -class LRUFileCache final : public IFileCache -{ -public: - LRUFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); - - FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) override; - - FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; - - FileSegments getSnapshot() const override; - - void initialize() override; - - void remove(const Key & key) override; - - void remove() override; - - std::vector tryGetCachePaths(const Key & key) override; - - size_t getUsedCacheSize() const override; - - size_t getFileSegmentsNum() const override; - -private: - struct FileSegmentCell : private boost::noncopyable - { - FileSegmentPtr file_segment; - - /// Iterator is put here on first reservation attempt, if successful. - std::optional queue_iterator; - - /// Pointer to file segment is always hold by the cache itself. - /// Apart from pointer in cache, it can be hold by cache users, when they call - /// getorSet(), but cache users always hold it via FileSegmentsHolder. - bool releasable() const { return file_segment.unique(); } - - size_t size() const { return file_segment->reserved_size; } - - FileSegmentCell(FileSegmentPtr file_segment_, LRUFileCache * cache, std::lock_guard & cache_lock); - - FileSegmentCell(FileSegmentCell && other) noexcept - : file_segment(std::move(other.file_segment)) - , queue_iterator(other.queue_iterator) {} - }; - - using FileSegmentsByOffset = std::map; - using CachedFiles = std::unordered_map; - - CachedFiles files; - LRUQueue queue; - - LRUQueue stash_queue; - AccessRecord records; - - size_t max_stash_element_size; - size_t enable_cache_hits_threshold; - - Poco::Logger * log; - - FileSegments getImpl( - const Key & key, const FileSegment::Range & range, - std::lock_guard & cache_lock); - - FileSegmentCell * getCell( - const Key & key, size_t offset, std::lock_guard & cache_lock); - - FileSegmentCell * addCell( - const Key & key, size_t offset, size_t size, - FileSegment::State state, std::lock_guard & cache_lock); - - void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); - - bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) override; - - bool tryReserveForMainList( - const Key & key, size_t offset, size_t size, - QueryContextPtr query_context, - std::lock_guard & cache_lock); - - void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - size_t getAvailableCacheSize() const; - - void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); - - FileSegments splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, std::lock_guard & cache_lock); - - String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); - - void fillHolesWithEmptyFileSegments( - FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, std::lock_guard & cache_lock); - - FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) override; - - size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; - - size_t getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const; - - size_t getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const; - - void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); - -public: - String dumpStructure(const Key & key_) override; - - void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); - - void assertCacheCorrectness(std::lock_guard & cache_lock); - - void assertQueueCorrectness(std::lock_guard & cache_lock); -}; - -} diff --git a/src/Common/FileCacheFactory.cpp b/src/Common/FileCacheFactory.cpp index 9eadea05547..e126ac014f2 100644 --- a/src/Common/FileCacheFactory.cpp +++ b/src/Common/FileCacheFactory.cpp @@ -1,5 +1,6 @@ #include "FileCacheFactory.h" -#include "FileCache.h" +#include "IFileCache.h" +#include "LRUFileCache.h" namespace DB { diff --git a/src/Common/FileCacheSettings.cpp b/src/Common/FileCacheSettings.cpp index 23cb418bc68..96ddfeebf95 100644 --- a/src/Common/FileCacheSettings.cpp +++ b/src/Common/FileCacheSettings.cpp @@ -13,6 +13,8 @@ void FileCacheSettings::loadFromConfig(const Poco::Util::AbstractConfiguration & cache_on_write_operations = config.getUInt64(config_prefix + ".cache_on_write_operations", false); enable_filesystem_query_cache_limit = config.getUInt64(config_prefix + ".enable_filesystem_query_cache_limit", false); enable_cache_hits_threshold = config.getUInt64(config_prefix + ".enable_cache_hits_threshold", REMOTE_FS_OBJECTS_CACHE_ENABLE_HITS_THRESHOLD); + do_not_evict_index_and_mark_files = config.getUInt64(config_prefix + ".do_not_evict_index_and_mark_files", true); + allow_remove_persistent_cache_by_default = config.getUInt64(config_prefix + ".allow_remove_persistent_cache_by_default", true); } } diff --git a/src/Common/FileCacheSettings.h b/src/Common/FileCacheSettings.h index 989db16bd7e..6e0326fd1af 100644 --- a/src/Common/FileCacheSettings.h +++ b/src/Common/FileCacheSettings.h @@ -12,10 +12,14 @@ struct FileCacheSettings size_t max_size = 0; size_t max_elements = REMOTE_FS_OBJECTS_CACHE_DEFAULT_MAX_ELEMENTS; size_t max_file_segment_size = REMOTE_FS_OBJECTS_CACHE_DEFAULT_MAX_FILE_SEGMENT_SIZE; + bool cache_on_write_operations = false; - bool enable_filesystem_query_cache_limit = false; size_t enable_cache_hits_threshold = REMOTE_FS_OBJECTS_CACHE_ENABLE_HITS_THRESHOLD; + bool enable_filesystem_query_cache_limit = false; + + bool do_not_evict_index_and_mark_files = true; + bool allow_remove_persistent_cache_by_default = true; void loadFromConfig(const Poco::Util::AbstractConfiguration & config, const std::string & config_prefix); }; diff --git a/src/Common/FileSegment.cpp b/src/Common/FileSegment.cpp index 27a111c1297..1183abc0e22 100644 --- a/src/Common/FileSegment.cpp +++ b/src/Common/FileSegment.cpp @@ -1,11 +1,12 @@ #include "FileSegment.h" #include -#include #include +#include #include #include #include + namespace CurrentMetrics { extern const Metric CacheDetachedFileSegments; @@ -25,7 +26,8 @@ FileSegment::FileSegment( size_t size_, const Key & key_, IFileCache * cache_, - State download_state_) + State download_state_, + bool is_persistent_) : segment_range(offset_, offset_ + size_ - 1) , download_state(download_state_) , file_key(key_) @@ -35,6 +37,7 @@ FileSegment::FileSegment( #else , log(&Poco::Logger::get("FileSegment")) #endif + , is_persistent(is_persistent_) /// Not really used for now, see PR 36171 { /// On creation, file segment state can be EMPTY, DOWNLOADED, DOWNLOADING. switch (download_state) @@ -241,7 +244,7 @@ void FileSegment::write(const char * from, size_t size, size_t offset_) "Cache writer was finalized (downloaded size: {}, state: {})", downloaded_size, stateToString(download_state)); - auto download_path = cache->getPathInLocalCache(key(), offset()); + auto download_path = getPathInLocalCache(); cache_writer = std::make_unique(download_path); } @@ -271,6 +274,11 @@ void FileSegment::write(const char * from, size_t size, size_t offset_) assert(getDownloadOffset() == offset_ + size); } +String FileSegment::getPathInLocalCache() const +{ + return cache->getPathInLocalCache(key(), offset(), isPersistent()); +} + void FileSegment::writeInMemory(const char * from, size_t size) { if (!size) @@ -287,7 +295,7 @@ void FileSegment::writeInMemory(const char * from, size_t size) if (cache_writer) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cache writer already initialized"); - auto download_path = cache->getPathInLocalCache(key(), offset()); + auto download_path = getPathInLocalCache(); cache_writer = std::make_unique(download_path, size + 1); try @@ -677,7 +685,7 @@ void FileSegment::assertCorrectnessImpl(std::lock_guard & /* segment { assert(downloader_id.empty() == (download_state != FileSegment::State::DOWNLOADING)); assert(!downloader_id.empty() == (download_state == FileSegment::State::DOWNLOADING)); - assert(download_state != FileSegment::State::DOWNLOADED || std::filesystem::file_size(cache->getPathInLocalCache(key(), offset())) > 0); + assert(download_state != FileSegment::State::DOWNLOADED || std::filesystem::file_size(getPathInLocalCache()) > 0); } void FileSegment::throwIfDetached() const @@ -729,6 +737,7 @@ FileSegmentPtr FileSegment::getSnapshot(const FileSegmentPtr & file_segment, std snapshot->ref_count = file_segment.use_count(); snapshot->downloaded_size = file_segment->getDownloadedSize(); snapshot->download_state = file_segment->state(); + snapshot->is_persistent = file_segment->isPersistent(); return snapshot; } diff --git a/src/Common/FileSegment.h b/src/Common/FileSegment.h index 307ff167942..2ab77634665 100644 --- a/src/Common/FileSegment.h +++ b/src/Common/FileSegment.h @@ -1,8 +1,9 @@ #pragma once #include -#include +#include #include +#include #include #include @@ -31,7 +32,7 @@ friend struct FileSegmentsHolder; friend class FileSegmentRangeWriter; public: - using Key = UInt128; + using Key = IFileCache::Key; using RemoteFileReaderPtr = std::shared_ptr; using LocalCacheWriterPtr = std::unique_ptr; @@ -70,8 +71,12 @@ public: }; FileSegment( - size_t offset_, size_t size_, const Key & key_, - IFileCache * cache_, State download_state_); + size_t offset_, + size_t size_, + const Key & key_, + IFileCache * cache_, + State download_state_, + bool is_persistent_ = false); ~FileSegment(); @@ -100,6 +105,8 @@ public: size_t offset() const { return range().left; } + bool isPersistent() const { return is_persistent; } + State wait(); bool reserve(size_t size); @@ -161,6 +168,8 @@ public: [[noreturn]] void throwIfDetached() const; + String getPathInLocalCache() const; + private: size_t availableSize() const { return reserved_size - downloaded_size; } @@ -237,6 +246,7 @@ private: std::atomic hits_count = 0; /// cache hits. std::atomic ref_count = 0; /// Used for getting snapshot state + bool is_persistent; CurrentMetrics::Increment metric_increment{CurrentMetrics::CacheFileSegments}; }; diff --git a/src/Common/IFileCache.cpp b/src/Common/IFileCache.cpp new file mode 100644 index 00000000000..ab949d4a536 --- /dev/null +++ b/src/Common/IFileCache.cpp @@ -0,0 +1,185 @@ +#include "IFileCache.h" + +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int REMOTE_FS_OBJECT_CACHE_ERROR; + extern const int LOGICAL_ERROR; +} + +IFileCache::IFileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_) + : cache_base_path(cache_base_path_) + , max_size(cache_settings_.max_size) + , max_element_size(cache_settings_.max_elements) + , max_file_segment_size(cache_settings_.max_file_segment_size) + , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) +{ +} + +String IFileCache::Key::toString() const +{ + return getHexUIntLowercase(key); +} + +IFileCache::Key IFileCache::hash(const String & path) +{ + return Key(sipHash128(path.data(), path.size())); +} + +String IFileCache::getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const +{ + auto key_str = key.toString(); + return fs::path(cache_base_path) + / key_str.substr(0, 3) + / key_str + / (std::to_string(offset) + (is_persistent ? "_persistent" : "")); +} + +String IFileCache::getPathInLocalCache(const Key & key) const +{ + auto key_str = key.toString(); + return fs::path(cache_base_path) / key_str.substr(0, 3) / key_str; +} + +static bool isQueryInitialized() +{ + return CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() && CurrentThread::getQueryId().size != 0; +} + +bool IFileCache::isReadOnly() +{ + return !isQueryInitialized(); +} + +void IFileCache::assertInitialized() const +{ + if (!is_initialized) + throw Exception(ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache not initialized"); +} + +IFileCache::QueryContextPtr IFileCache::getCurrentQueryContext(std::lock_guard & cache_lock) +{ + if (!isQueryInitialized()) + return nullptr; + + return getQueryContext(CurrentThread::getQueryId().toString(), cache_lock); +} + +IFileCache::QueryContextPtr IFileCache::getQueryContext(const String & query_id, std::lock_guard &) +{ + auto query_iter = query_map.find(query_id); + return (query_iter == query_map.end()) ? nullptr : query_iter->second; +} + +void IFileCache::removeQueryContext(const String & query_id) +{ + std::lock_guard cache_lock(mutex); + auto query_iter = query_map.find(query_id); + + if (query_iter == query_map.end()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Attempt to release query context that does not exist"); + + query_map.erase(query_iter); +} + +IFileCache::QueryContextPtr IFileCache::getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) +{ + if (query_id.empty()) + return nullptr; + + auto context = getQueryContext(query_id, cache_lock); + if (!context) + { + auto query_iter = query_map.insert({query_id, std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache)}).first; + context = query_iter->second; + } + return context; +} + +IFileCache::QueryContextHolder IFileCache::getQueryContextHolder(const String & query_id, const ReadSettings & settings) +{ + std::lock_guard cache_lock(mutex); + + /// if enable_filesystem_query_cache_limit is true, and max_query_cache_size large than zero, + /// we create context query for current query. + if (enable_filesystem_query_cache_limit && settings.max_query_cache_size) + { + auto context = getOrSetQueryContext(query_id, settings, cache_lock); + return QueryContextHolder(query_id, this, context); + } + else + return QueryContextHolder(); +} + +void IFileCache::QueryContext::remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +{ + if (cache_size < size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + { + lru_queue.remove(record->second, cache_lock); + records.erase({key, offset}); + } + } + cache_size -= size; +} + +void IFileCache::QueryContext::reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +{ + if (cache_size + size > max_cache_size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "reserved cache size exceeds the remaining cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record == records.end()) + { + auto queue_iter = lru_queue.add(key, offset, 0, cache_lock); + record = records.insert({{key, offset}, queue_iter}).first; + } + record->second->size += size; + } + cache_size += size; +} + +void IFileCache::QueryContext::use(const Key & key, size_t offset, std::lock_guard & cache_lock) +{ + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + lru_queue.moveToEnd(record->second, cache_lock); + } +} + +IFileCache::QueryContextHolder::QueryContextHolder(const String & query_id_, IFileCache * cache_, IFileCache::QueryContextPtr context_) + : query_id(query_id_), cache(cache_), context(context_) +{ +} + +IFileCache::QueryContextHolder::~QueryContextHolder() +{ + /// If only the query_map and the current holder hold the context_query, + /// the query has been completed and the query_context is released. + if (context && context.use_count() == 2) + cache->removeQueryContext(query_id); +} + +} diff --git a/src/Common/IFileCache.h b/src/Common/IFileCache.h new file mode 100644 index 00000000000..6a1806ccbff --- /dev/null +++ b/src/Common/IFileCache.h @@ -0,0 +1,266 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + + +namespace DB +{ + +class FileSegment; +using FileSegmentPtr = std::shared_ptr; +using FileSegments = std::list; +struct FileSegmentsHolder; +struct ReadSettings; + +/** + * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. + */ +class IFileCache : private boost::noncopyable +{ +friend class FileSegment; +friend struct FileSegmentsHolder; +friend class FileSegmentRangeWriter; + +public: + struct Key + { + UInt128 key; + String toString() const; + + Key() = default; + explicit Key(const UInt128 & key_) : key(key_) {} + + bool operator==(const Key & other) const { return key == other.key; } + }; + + IFileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_); + + virtual ~IFileCache() = default; + + /// Restore cache from local filesystem. + virtual void initialize() = 0; + + virtual void removeIfExists(const Key & key) = 0; + + virtual void removeIfReleasable(bool remove_persistent_files) = 0; + + static bool isReadOnly(); + + /// Cache capacity in bytes. + size_t capacity() const { return max_size; } + + static Key hash(const String & path); + + String getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const; + + String getPathInLocalCache(const Key & key) const; + + const String & getBasePath() const { return cache_base_path; } + + virtual std::vector tryGetCachePaths(const Key & key) = 0; + + /** + * Given an `offset` and `size` representing [offset, offset + size) bytes interval, + * return list of cached non-overlapping non-empty + * file segments `[segment1, ..., segmentN]` which intersect with given interval. + * + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * As long as pointers to returned file segments are hold + * it is guaranteed that these file segments are not removed from cache. + */ + virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) = 0; + + /** + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" + * from cache (not owned by cache), and as a result will never change it's state and will be destructed + * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change + * it's state (and become DOWNLOADED). + */ + virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; + + virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) = 0; + + virtual FileSegments getSnapshot() const = 0; + + /// For debug. + virtual String dumpStructure(const Key & key) = 0; + + virtual size_t getUsedCacheSize() const = 0; + + virtual size_t getFileSegmentsNum() const = 0; + +protected: + String cache_base_path; + size_t max_size; + size_t max_element_size; + size_t max_file_segment_size; + + bool is_initialized = false; + + mutable std::mutex mutex; + + virtual bool tryReserve( + const Key & key, size_t offset, size_t size, + std::lock_guard & cache_lock) = 0; + + virtual void remove( + Key key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + virtual bool isLastFileSegmentHolder( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + virtual void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) = 0; + + void assertInitialized() const; + + class LRUQueue + { + public: + struct FileKeyAndOffset + { + Key key; + size_t offset; + size_t size; + size_t hits = 0; + + FileKeyAndOffset(const Key & key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) {} + }; + + using Iterator = typename std::list::iterator; + + size_t getTotalCacheSize(std::lock_guard & /* cache_lock */) const { return cache_size; } + + size_t getElementsNum(std::lock_guard & /* cache_lock */) const { return queue.size(); } + + Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void remove(Iterator queue_it, std::lock_guard & cache_lock); + + void moveToEnd(Iterator queue_it, std::lock_guard & cache_lock); + + /// Space reservation for a file segment is incremental, so we need to be able to increment size of the queue entry. + void incrementSize(Iterator queue_it, size_t size_increment, std::lock_guard & cache_lock); + + String toString(std::lock_guard & cache_lock) const; + + bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) const; + + Iterator begin() { return queue.begin(); } + + Iterator end() { return queue.end(); } + + void removeAll(std::lock_guard & cache_lock); + + private: + std::list queue; + size_t cache_size = 0; + }; + + using AccessKeyAndOffset = std::pair; + struct KeyAndOffsetHash + { + std::size_t operator()(const AccessKeyAndOffset & key) const + { + return std::hash()(key.first.key) ^ std::hash()(key.second); + } + }; + + using AccessRecord = std::unordered_map; + + /// Used to track and control the cache access of each query. + /// Through it, we can realize the processing of different queries by the cache layer. + struct QueryContext + { + LRUQueue lru_queue; + AccessRecord records; + + size_t cache_size = 0; + size_t max_cache_size; + + bool skip_download_if_exceeds_query_cache; + + QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) + : max_cache_size(max_cache_size_) + , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} + + void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void use(const Key & key, size_t offset, std::lock_guard & cache_lock); + + size_t getMaxCacheSize() const { return max_cache_size; } + + size_t getCacheSize() const { return cache_size; } + + LRUQueue & queue() { return lru_queue; } + + bool isSkipDownloadIfExceed() const { return skip_download_if_exceeds_query_cache; } + }; + + using QueryContextPtr = std::shared_ptr; + using QueryContextMap = std::unordered_map; + + QueryContextMap query_map; + + bool enable_filesystem_query_cache_limit; + +public: + QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); + + QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); + + void removeQueryContext(const String & query_id); + + QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); + + /// Save a query context information, and adopt different cache policies + /// for different queries through the context cache layer. + struct QueryContextHolder : private boost::noncopyable + { + explicit QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); + + QueryContextHolder() = default; + + ~QueryContextHolder(); + + String query_id {}; + IFileCache * cache = nullptr; + QueryContextPtr context = nullptr; + }; + + QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); + +}; + +using FileCachePtr = std::shared_ptr; + +} + +namespace std +{ +template <> struct hash +{ + std::size_t operator()(const DB::IFileCache::Key & k) const { return hash()(k.key); } +}; + +} diff --git a/src/Common/FileCache.cpp b/src/Common/LRUFileCache.cpp similarity index 85% rename from src/Common/FileCache.cpp rename to src/Common/LRUFileCache.cpp index d8ffcaadd55..4c015e49354 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -1,4 +1,4 @@ -#include "FileCache.h" +#include "LRUFileCache.h" #include #include @@ -22,130 +22,12 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -namespace -{ - String keyToStr(const IFileCache::Key & key) - { - return getHexUIntLowercase(key); - } -} - -static bool isQueryInitialized() -{ - return CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() && CurrentThread::getQueryId().size != 0; -} - -IFileCache::IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_) - : cache_base_path(cache_base_path_) - , max_size(cache_settings_.max_size) - , max_element_size(cache_settings_.max_elements) - , max_file_segment_size(cache_settings_.max_file_segment_size) - , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) -{ -} - -IFileCache::Key IFileCache::hash(const String & path) -{ - return sipHash128(path.data(), path.size()); -} - -String IFileCache::getPathInLocalCache(const Key & key, size_t offset) -{ - auto key_str = keyToStr(key); - return fs::path(cache_base_path) / key_str.substr(0, 3) / key_str / std::to_string(offset); -} - -String IFileCache::getPathInLocalCache(const Key & key) -{ - auto key_str = keyToStr(key); - return fs::path(cache_base_path) / key_str.substr(0, 3) / key_str; -} - -bool IFileCache::isReadOnly() -{ - return (!isQueryInitialized()); -} - -void IFileCache::assertInitialized() const -{ - if (!is_initialized) - throw Exception(ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache not initialized"); -} - -IFileCache::QueryContextPtr IFileCache::getCurrentQueryContext(std::lock_guard & cache_lock) -{ - if (!isQueryInitialized()) - return nullptr; - - return getQueryContext(CurrentThread::getQueryId().toString(), cache_lock); -} - -IFileCache::QueryContextPtr IFileCache::getQueryContext(const String & query_id, std::lock_guard &) -{ - auto query_iter = query_map.find(query_id); - return (query_iter == query_map.end()) ? nullptr : query_iter->second; -} - -void IFileCache::removeQueryContext(const String & query_id) -{ - std::lock_guard cache_lock(mutex); - auto query_iter = query_map.find(query_id); - - if (query_iter == query_map.end()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Attempt to release query context that does not exist"); - - query_map.erase(query_iter); -} - -IFileCache::QueryContextPtr IFileCache::getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) -{ - if (query_id.empty()) - return nullptr; - - auto context = getQueryContext(query_id, cache_lock); - if (!context) - { - auto query_iter = query_map.insert({query_id, std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache)}).first; - context = query_iter->second; - } - return context; -} - -IFileCache::QueryContextHolder IFileCache::getQueryContextHolder(const String & query_id, const ReadSettings & settings) -{ - std::lock_guard cache_lock(mutex); - - /// if enable_filesystem_query_cache_limit is true, and max_query_cache_size large than zero, - /// we create context query for current query. - if (enable_filesystem_query_cache_limit && settings.max_query_cache_size) - { - auto context = getOrSetQueryContext(query_id, settings, cache_lock); - return QueryContextHolder(query_id, this, context); - } - else - return QueryContextHolder(); -} - -IFileCache::QueryContextHolder::QueryContextHolder(const String & query_id_, IFileCache * cache_, IFileCache::QueryContextPtr context_) - : query_id(query_id_), cache(cache_), context(context_) -{ -} - -IFileCache::QueryContextHolder::~QueryContextHolder() -{ - /// If only the query_map and the current holder hold the context_query, - /// the query has been completed and the query_context is released. - if (context && context.use_count() == 2) - cache->removeQueryContext(query_id); -} - LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) : IFileCache(cache_base_path_, cache_settings_) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("LRUFileCache")) + , allow_remove_persistent_cache_by_default(cache_settings_.allow_remove_persistent_cache_by_default) { } @@ -155,9 +37,20 @@ void LRUFileCache::initialize() if (!is_initialized) { if (fs::exists(cache_base_path)) - loadCacheInfoIntoMemory(cache_lock); + { + try + { + loadCacheInfoIntoMemory(cache_lock); + } + catch (...) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + return; + } + } else fs::create_directories(cache_base_path); + is_initialized = true; } } @@ -168,7 +61,7 @@ void LRUFileCache::useCell( auto file_segment = cell.file_segment; if (file_segment->isDownloaded() - && fs::file_size(getPathInLocalCache(file_segment->key(), file_segment->offset())) == 0) + && fs::file_size(getPathInLocalCache(file_segment->key(), file_segment->offset(), file_segment->isPersistent())) == 0) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot have zero size downloaded file segments. Current file segment: {}", file_segment->range().toString()); @@ -218,8 +111,10 @@ FileSegments LRUFileCache::getImpl( files.erase(key); + /// Note: it is guaranteed that there is no concurrency with files delition, + /// because cache files are deleted only inside IFileCache and under cache lock. if (fs::exists(key_path)) - fs::remove(key_path); + fs::remove_all(key_path); return {}; } @@ -281,7 +176,7 @@ FileSegments LRUFileCache::getImpl( } FileSegments LRUFileCache::splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, std::lock_guard & cache_lock) + const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock) { assert(size > 0); @@ -297,7 +192,7 @@ FileSegments LRUFileCache::splitRangeIntoCells( current_cell_size = std::min(remaining_size, max_file_segment_size); remaining_size -= current_cell_size; - auto * cell = addCell(key, current_pos, current_cell_size, state, cache_lock); + auto * cell = addCell(key, current_pos, current_cell_size, state, is_persistent, cache_lock); if (cell) file_segments.push_back(cell->file_segment); assert(cell); @@ -314,6 +209,7 @@ void LRUFileCache::fillHolesWithEmptyFileSegments( const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, + bool is_persistent, std::lock_guard & cache_lock) { /// There are segments [segment1, ..., segmentN] @@ -369,7 +265,7 @@ void LRUFileCache::fillHolesWithEmptyFileSegments( } else { - file_segments.splice(it, splitRangeIntoCells(key, current_pos, hole_size, FileSegment::State::EMPTY, cache_lock)); + file_segments.splice(it, splitRangeIntoCells(key, current_pos, hole_size, FileSegment::State::EMPTY, is_persistent, cache_lock)); } current_pos = segment_range.right + 1; @@ -397,12 +293,12 @@ void LRUFileCache::fillHolesWithEmptyFileSegments( else { file_segments.splice( - file_segments.end(), splitRangeIntoCells(key, current_pos, hole_size, FileSegment::State::EMPTY, cache_lock)); + file_segments.end(), splitRangeIntoCells(key, current_pos, hole_size, FileSegment::State::EMPTY, is_persistent, cache_lock)); } } } -FileSegmentsHolder LRUFileCache::getOrSet(const Key & key, size_t offset, size_t size) +FileSegmentsHolder LRUFileCache::getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) { assertInitialized(); @@ -419,11 +315,11 @@ FileSegmentsHolder LRUFileCache::getOrSet(const Key & key, size_t offset, size_t if (file_segments.empty()) { - file_segments = splitRangeIntoCells(key, offset, size, FileSegment::State::EMPTY, cache_lock); + file_segments = splitRangeIntoCells(key, offset, size, FileSegment::State::EMPTY, is_persistent, cache_lock); } else { - fillHolesWithEmptyFileSegments(file_segments, key, range, false, cache_lock); + fillHolesWithEmptyFileSegments(file_segments, key, range, /* fill_with_detached */false, is_persistent, cache_lock); } assert(!file_segments.empty()); @@ -456,14 +352,15 @@ FileSegmentsHolder LRUFileCache::get(const Key & key, size_t offset, size_t size } else { - fillHolesWithEmptyFileSegments(file_segments, key, range, true, cache_lock); + fillHolesWithEmptyFileSegments(file_segments, key, range, /* fill_with_detached */true, /* is_persistent */false, cache_lock); } return FileSegmentsHolder(std::move(file_segments)); } LRUFileCache::FileSegmentCell * LRUFileCache::addCell( - const Key & key, size_t offset, size_t size, FileSegment::State state, + const Key & key, size_t offset, size_t size, + FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock) { /// Create a file segment cell and put it in `files` map by [key][offset]. @@ -475,10 +372,11 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( throw Exception( ErrorCodes::LOGICAL_ERROR, "Cache already exists for key: `{}`, offset: {}, size: {}.\nCurrent cache structure: {}", - keyToStr(key), offset, size, dumpStructureUnlocked(key, cache_lock)); + key.toString(), offset, size, dumpStructureUnlocked(key, cache_lock)); auto skip_or_download = [&]() -> FileSegmentPtr { + FileSegment::State result_state = state; if (state == FileSegment::State::EMPTY && enable_cache_hits_threshold) { auto record = records.find({key, offset}); @@ -496,7 +394,7 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( } /// For segments that do not reach the download threshold, we do not download them, but directly read them - return std::make_shared(offset, size, key, this, FileSegment::State::SKIP_CACHE); + result_state = FileSegment::State::SKIP_CACHE; } else { @@ -504,12 +402,11 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( queue_iter->hits++; stash_queue.moveToEnd(queue_iter, cache_lock); - state = queue_iter->hits >= enable_cache_hits_threshold ? FileSegment::State::EMPTY : FileSegment::State::SKIP_CACHE; - return std::make_shared(offset, size, key, this, state); + result_state = queue_iter->hits >= enable_cache_hits_threshold ? FileSegment::State::EMPTY : FileSegment::State::SKIP_CACHE; } } - else - return std::make_shared(offset, size, key, this, state); + + return std::make_shared(offset, size, key, this, result_state, is_persistent); }; FileSegmentCell cell(skip_or_download(), this, cache_lock); @@ -527,12 +424,16 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( throw Exception( ErrorCodes::LOGICAL_ERROR, "Failed to insert into cache key: `{}`, offset: {}, size: {}", - keyToStr(key), offset, size); + key.toString(), offset, size); return &(it->second); } -FileSegmentsHolder LRUFileCache::setDownloading(const Key & key, size_t offset, size_t size) +FileSegmentsHolder LRUFileCache::setDownloading( + const Key & key, + size_t offset, + size_t size, + bool is_persistent) { std::lock_guard cache_lock(mutex); @@ -545,9 +446,9 @@ FileSegmentsHolder LRUFileCache::setDownloading(const Key & key, size_t offset, throw Exception( ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache cell already exists for key `{}` and offset {}", - keyToStr(key), offset); + key.toString(), offset); - auto file_segments = splitRangeIntoCells(key, offset, size, FileSegment::State::DOWNLOADING, cache_lock); + auto file_segments = splitRangeIntoCells(key, offset, size, FileSegment::State::DOWNLOADING, is_persistent, cache_lock); return FileSegmentsHolder(std::move(file_segments)); } @@ -708,7 +609,7 @@ bool LRUFileCache::tryReserveForMainList( throw Exception( ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache became inconsistent. Key: {}, offset: {}", - keyToStr(key), offset); + key.toString(), offset); size_t cell_size = cell->size(); assert(entry_size == cell_size); @@ -790,7 +691,7 @@ bool LRUFileCache::tryReserveForMainList( return true; } -void LRUFileCache::remove(const Key & key) +void LRUFileCache::removeIfExists(const Key & key) { assertInitialized(); @@ -825,6 +726,7 @@ void LRUFileCache::remove(const Key & key) if (file_segment) { std::lock_guard segment_lock(file_segment->mutex); + file_segment->detach(cache_lock, segment_lock); remove(file_segment->key(), file_segment->offset(), cache_lock, segment_lock); } } @@ -836,11 +738,11 @@ void LRUFileCache::remove(const Key & key) files.erase(key); if (fs::exists(key_path)) - fs::remove(key_path); + fs::remove_all(key_path); } } -void LRUFileCache::remove() +void LRUFileCache::removeIfReleasable(bool remove_persistent_files) { /// Try remove all cached files by cache_base_path. /// Only releasable file segments are evicted. @@ -860,7 +762,10 @@ void LRUFileCache::remove() if (cell->releasable()) { auto file_segment = cell->file_segment; - if (file_segment) + if (file_segment + && (!file_segment->isPersistent() + || remove_persistent_files + || allow_remove_persistent_cache_by_default)) { std::lock_guard segment_lock(file_segment->mutex); file_segment->detach(cache_lock, segment_lock); @@ -872,17 +777,23 @@ void LRUFileCache::remove() /// Remove all access information. records.clear(); stash_queue.removeAll(cache_lock); + +#ifndef NDEBUG + assertCacheCorrectness(cache_lock); +#endif } void LRUFileCache::remove( Key key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) { - LOG_TEST(log, "Remove. Key: {}, offset: {}", keyToStr(key), offset); + LOG_TEST(log, "Remove. Key: {}, offset: {}", key.toString(), offset); auto * cell = getCell(key, offset, cache_lock); if (!cell) - throw Exception(ErrorCodes::LOGICAL_ERROR, "No cache cell for key: {}, offset: {}", keyToStr(key), offset); + throw Exception(ErrorCodes::LOGICAL_ERROR, "No cache cell for key: {}, offset: {}", key.toString(), offset); + + bool is_persistent_file_segment = cell->file_segment->isPersistent(); if (cell->queue_iterator) { @@ -892,7 +803,7 @@ void LRUFileCache::remove( auto & offsets = files[key]; offsets.erase(offset); - auto cache_file_path = getPathInLocalCache(key, offset); + auto cache_file_path = getPathInLocalCache(key, offset, is_persistent_file_segment); if (fs::exists(cache_file_path)) { try @@ -906,14 +817,14 @@ void LRUFileCache::remove( files.erase(key); if (fs::exists(key_path)) - fs::remove(key_path); + fs::remove_all(key_path); } } catch (...) { throw Exception(ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Removal of cached file failed. Key: {}, offset: {}, path: {}, error: {}", - keyToStr(key), offset, cache_file_path, getCurrentExceptionMessage(false)); + key.toString(), offset, cache_file_path, getCurrentExceptionMessage(false)); } } } @@ -927,18 +838,33 @@ void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_l /// cache_base_path / key_prefix / key / offset + if (!files.empty()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cache already initialized"); + fs::directory_iterator key_prefix_it{cache_base_path}; for (; key_prefix_it != fs::directory_iterator(); ++key_prefix_it) { fs::directory_iterator key_it{key_prefix_it->path()}; for (; key_it != fs::directory_iterator(); ++key_it) { - key = unhexUInt(key_it->path().filename().string().data()); + key = Key(unhexUInt(key_it->path().filename().string().data())); fs::directory_iterator offset_it{key_it->path()}; for (; offset_it != fs::directory_iterator(); ++offset_it) { - bool parsed = tryParse(offset, offset_it->path().filename()); + auto offset_with_suffix = offset_it->path().filename().string(); + auto delim_pos = offset_with_suffix.find('_'); + bool parsed; + bool is_persistent = false; + + if (delim_pos == std::string::npos) + parsed = tryParse(offset, offset_with_suffix); + else + { + parsed = tryParse(offset, offset_with_suffix.substr(0, delim_pos)); + is_persistent = offset_with_suffix.substr(delim_pos+1) == "persistent"; + } + if (!parsed) { LOG_WARNING(log, "Unexpected file: ", offset_it->path().string()); @@ -954,7 +880,7 @@ void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_l if (tryReserve(key, offset, size, cache_lock)) { - auto * cell = addCell(key, offset, size, FileSegment::State::DOWNLOADED, cache_lock); + auto * cell = addCell(key, offset, size, FileSegment::State::DOWNLOADED, is_persistent, cache_lock); if (cell) queue_entries.emplace_back(*cell->queue_iterator, cell->file_segment); } @@ -1003,7 +929,7 @@ void LRUFileCache::reduceSizeToDownloaded( throw Exception( ErrorCodes::LOGICAL_ERROR, "No cell found for key: {}, offset: {}", - keyToStr(key), offset); + key.toString(), offset); } const auto & file_segment = cell->file_segment; @@ -1014,7 +940,7 @@ void LRUFileCache::reduceSizeToDownloaded( throw Exception( ErrorCodes::LOGICAL_ERROR, "Nothing to reduce, file segment fully downloaded, key: {}, offset: {}", - keyToStr(key), offset); + key.toString(), offset); } cell->file_segment = std::make_shared(offset, downloaded_size, key, this, FileSegment::State::DOWNLOADED); @@ -1027,7 +953,7 @@ bool LRUFileCache::isLastFileSegmentHolder( auto * cell = getCell(key, offset, cache_lock); if (!cell) - throw Exception(ErrorCodes::LOGICAL_ERROR, "No cell found for key: {}, offset: {}", keyToStr(key), offset); + throw Exception(ErrorCodes::LOGICAL_ERROR, "No cell found for key: {}, offset: {}", key.toString(), offset); /// The caller of this method is last file segment holder if use count is 2 (the second pointer is cache itself) return cell->file_segment.use_count() == 2; @@ -1058,7 +984,7 @@ std::vector LRUFileCache::tryGetCachePaths(const Key & key) for (const auto & [offset, cell] : cells_by_offset) { if (cell.file_segment->state() == FileSegment::State::DOWNLOADED) - cache_paths.push_back(getPathInLocalCache(key, offset)); + cache_paths.push_back(getPathInLocalCache(key, offset, cell.file_segment->isPersistent())); } return cache_paths; @@ -1139,7 +1065,7 @@ IFileCache::LRUQueue::Iterator IFileCache::LRUQueue::add( throw Exception( ErrorCodes::LOGICAL_ERROR, "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", - keyToStr(key), offset, size); + key.toString(), offset, size); } #endif @@ -1190,7 +1116,7 @@ String IFileCache::LRUQueue::toString(std::lock_guard & /* cache_loc { if (!result.empty()) result += ", "; - result += fmt::format("{}: [{}, {}]", keyToStr(key), offset, offset + size - 1); + result += fmt::format("{}: [{}, {}]", key.toString(), offset, offset + size - 1); } return result; } diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCache.h new file mode 100644 index 00000000000..59de57f3ad3 --- /dev/null +++ b/src/Common/LRUFileCache.h @@ -0,0 +1,153 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace DB +{ + +class LRUFileCache final : public IFileCache +{ +public: + LRUFileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_); + + FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) override; + + FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; + + FileSegments getSnapshot() const override; + + void initialize() override; + + void removeIfExists(const Key & key) override; + + void removeIfReleasable(bool remove_persistent_files) override; + + std::vector tryGetCachePaths(const Key & key) override; + + size_t getUsedCacheSize() const override; + + size_t getFileSegmentsNum() const override; + +private: + struct FileSegmentCell : private boost::noncopyable + { + FileSegmentPtr file_segment; + + /// Iterator is put here on first reservation attempt, if successful. + std::optional queue_iterator; + + /// Pointer to file segment is always hold by the cache itself. + /// Apart from pointer in cache, it can be hold by cache users, when they call + /// getorSet(), but cache users always hold it via FileSegmentsHolder. + bool releasable() const {return file_segment.unique(); } + + size_t size() const { return file_segment->reserved_size; } + + FileSegmentCell(FileSegmentPtr file_segment_, LRUFileCache * cache, std::lock_guard & cache_lock); + + FileSegmentCell(FileSegmentCell && other) noexcept + : file_segment(std::move(other.file_segment)) + , queue_iterator(other.queue_iterator) {} + }; + + using FileSegmentsByOffset = std::map; + using CachedFiles = std::unordered_map; + + CachedFiles files; + LRUQueue queue; + + LRUQueue stash_queue; + AccessRecord records; + + size_t max_stash_element_size; + size_t enable_cache_hits_threshold; + + Poco::Logger * log; + bool allow_remove_persistent_cache_by_default; + + FileSegments getImpl( + const Key & key, const FileSegment::Range & range, + std::lock_guard & cache_lock); + + FileSegmentCell * getCell( + const Key & key, size_t offset, std::lock_guard & cache_lock); + + FileSegmentCell * addCell( + const Key & key, size_t offset, size_t size, + FileSegment::State state, bool is_persistent, + std::lock_guard & cache_lock); + + void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); + + bool tryReserve( + const Key & key, size_t offset, size_t size, + std::lock_guard & cache_lock) override; + + bool tryReserveForMainList( + const Key & key, size_t offset, size_t size, + QueryContextPtr query_context, + std::lock_guard & cache_lock); + + void remove( + Key key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + bool isLastFileSegmentHolder( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + size_t getAvailableCacheSize() const; + + void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); + + FileSegments splitRangeIntoCells( + const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock); + + String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); + + void fillHolesWithEmptyFileSegments( + FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, bool is_persistent, std::lock_guard & cache_lock); + + FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) override; + + size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; + + size_t getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const; + + size_t getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const; + + void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); + + void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) override; + +public: + String dumpStructure(const Key & key_) override; + + void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); + + void assertCacheCorrectness(std::lock_guard & cache_lock); + + void assertQueueCorrectness(std::lock_guard & cache_lock); +}; + +} diff --git a/src/Common/tests/gtest_lru_file_cache.cpp b/src/Common/tests/gtest_lru_file_cache.cpp index ca3ca25a0e5..2f268e217df 100644 --- a/src/Common/tests/gtest_lru_file_cache.cpp +++ b/src/Common/tests/gtest_lru_file_cache.cpp @@ -1,7 +1,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -46,14 +47,9 @@ std::vector fromHolder(const DB::FileSegmentsHolder & holder return std::vector(holder.file_segments.begin(), holder.file_segments.end()); } -String keyToStr(const DB::IFileCache::Key & key) -{ - return getHexUIntLowercase(key); -} - String getFileSegmentPath(const String & base_path, const DB::IFileCache::Key & key, size_t offset) { - auto key_str = keyToStr(key); + auto key_str = key.toString(); return fs::path(base_path) / key_str.substr(0, 3) / key_str / DB::toString(offset); } @@ -62,7 +58,7 @@ void download(DB::FileSegmentPtr file_segment) const auto & key = file_segment->key(); size_t size = file_segment->range().size(); - auto key_str = keyToStr(key); + auto key_str = key.toString(); auto subdir = fs::path(cache_base_path) / key_str.substr(0, 3) / key_str; if (!fs::exists(subdir)) fs::create_directories(subdir); @@ -112,7 +108,7 @@ TEST(LRUFileCache, get) auto key = cache.hash("key1"); { - auto holder = cache.getOrSet(key, 0, 10); /// Add range [0, 9] + auto holder = cache.getOrSet(key, 0, 10, false); /// Add range [0, 9] auto segments = fromHolder(holder); /// Range was not present in cache. It should be added in cache as one while file segment. ASSERT_EQ(segments.size(), 1); @@ -141,7 +137,7 @@ TEST(LRUFileCache, get) { /// Want range [5, 14], but [0, 9] already in cache, so only [10, 14] will be put in cache. - auto holder = cache.getOrSet(key, 5, 10); + auto holder = cache.getOrSet(key, 5, 10, false); auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 2); @@ -161,14 +157,14 @@ TEST(LRUFileCache, get) ASSERT_EQ(cache.getUsedCacheSize(), 15); { - auto holder = cache.getOrSet(key, 9, 1); /// Get [9, 9] + auto holder = cache.getOrSet(key, 9, 1, false); /// Get [9, 9] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 1); assertRange(7, segments[0], DB::FileSegment::Range(0, 9), DB::FileSegment::State::DOWNLOADED); } { - auto holder = cache.getOrSet(key, 9, 2); /// Get [9, 10] + auto holder = cache.getOrSet(key, 9, 2, false); /// Get [9, 10] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 2); assertRange(8, segments[0], DB::FileSegment::Range(0, 9), DB::FileSegment::State::DOWNLOADED); @@ -176,16 +172,15 @@ TEST(LRUFileCache, get) } { - auto holder = cache.getOrSet(key, 10, 1); /// Get [10, 10] + auto holder = cache.getOrSet(key, 10, 1, false); /// Get [10, 10] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 1); assertRange(10, segments[0], DB::FileSegment::Range(10, 14), DB::FileSegment::State::DOWNLOADED); } - complete(cache.getOrSet(key, 17, 4)); /// Get [17, 20] - complete(cache.getOrSet(key, 24, 3)); /// Get [24, 26] - // complete(cache.getOrSet(key, 27, 1)); /// Get [27, 27] - + complete(cache.getOrSet(key, 17, 4, false)); /// Get [17, 20] + complete(cache.getOrSet(key, 24, 3, false)); /// Get [24, 26] + /// complete(cache.getOrSet(key, 27, 1, false)); /// Get [27, 27] /// Current cache: [__________][_____] [____] [___][] /// ^ ^^ ^ ^ ^ ^ ^^^ @@ -195,7 +190,7 @@ TEST(LRUFileCache, get) ASSERT_EQ(cache.getUsedCacheSize(), 22); { - auto holder = cache.getOrSet(key, 0, 26); /// Get [0, 25] + auto holder = cache.getOrSet(key, 0, 26, false); /// Get [0, 25] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 6); @@ -229,14 +224,14 @@ TEST(LRUFileCache, get) /// as max elements size is reached, next attempt to put something in cache should fail. /// This will also check that [27, 27] was indeed evicted. - auto holder1 = cache.getOrSet(key, 27, 1); + auto holder1 = cache.getOrSet(key, 27, 1, false); auto segments_1 = fromHolder(holder1); /// Get [27, 27] ASSERT_EQ(segments_1.size(), 1); assertRange(17, segments_1[0], DB::FileSegment::Range(27, 27), DB::FileSegment::State::EMPTY); } { - auto holder = cache.getOrSet(key, 12, 10); /// Get [12, 21] + auto holder = cache.getOrSet(key, 12, 10, false); /// Get [12, 21] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 4); @@ -260,7 +255,7 @@ TEST(LRUFileCache, get) ASSERT_EQ(cache.getFileSegmentsNum(), 5); { - auto holder = cache.getOrSet(key, 23, 5); /// Get [23, 28] + auto holder = cache.getOrSet(key, 23, 5, false); /// Get [23, 28] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 3); @@ -281,12 +276,12 @@ TEST(LRUFileCache, get) /// 17 21 2324 26 28 { - auto holder5 = cache.getOrSet(key, 2, 3); /// Get [2, 4] + auto holder5 = cache.getOrSet(key, 2, 3,false); /// Get [2, 4] auto s5 = fromHolder(holder5); ASSERT_EQ(s5.size(), 1); assertRange(25, s5[0], DB::FileSegment::Range(2, 4), DB::FileSegment::State::EMPTY); - auto holder1 = cache.getOrSet(key, 30, 2); /// Get [30, 31] + auto holder1 = cache.getOrSet(key, 30, 2, false); /// Get [30, 31] auto s1 = fromHolder(holder1); ASSERT_EQ(s1.size(), 1); assertRange(26, s1[0], DB::FileSegment::Range(30, 31), DB::FileSegment::State::EMPTY); @@ -302,20 +297,20 @@ TEST(LRUFileCache, get) /// ^ ^ ^ ^ ^ ^ ^ ^ /// 2 4 23 24 26 27 30 31 - auto holder2 = cache.getOrSet(key, 23, 1); /// Get [23, 23] + auto holder2 = cache.getOrSet(key, 23, 1, false); /// Get [23, 23] auto s2 = fromHolder(holder2); ASSERT_EQ(s2.size(), 1); - auto holder3 = cache.getOrSet(key, 24, 3); /// Get [24, 26] + auto holder3 = cache.getOrSet(key, 24, 3, false); /// Get [24, 26] auto s3 = fromHolder(holder3); ASSERT_EQ(s3.size(), 1); - auto holder4 = cache.getOrSet(key, 27, 1); /// Get [27, 27] + auto holder4 = cache.getOrSet(key, 27, 1, false); /// Get [27, 27] auto s4 = fromHolder(holder4); ASSERT_EQ(s4.size(), 1); /// All cache is now unreleasable because pointers are still hold - auto holder6 = cache.getOrSet(key, 0, 40); + auto holder6 = cache.getOrSet(key, 0, 40, false); auto f = fromHolder(holder6); ASSERT_EQ(f.size(), 9); @@ -336,7 +331,7 @@ TEST(LRUFileCache, get) } { - auto holder = cache.getOrSet(key, 2, 3); /// Get [2, 4] + auto holder = cache.getOrSet(key, 2, 3, false); /// Get [2, 4] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 1); assertRange(31, segments[0], DB::FileSegment::Range(2, 4), DB::FileSegment::State::DOWNLOADED); @@ -347,7 +342,7 @@ TEST(LRUFileCache, get) /// 2 4 23 24 26 27 30 31 { - auto holder = cache.getOrSet(key, 25, 5); /// Get [25, 29] + auto holder = cache.getOrSet(key, 25, 5, false); /// Get [25, 29] auto segments = fromHolder(holder); ASSERT_EQ(segments.size(), 3); @@ -371,7 +366,7 @@ TEST(LRUFileCache, get) DB::CurrentThread::QueryScope query_scope_holder_1(query_context_1); thread_status_1.attachQueryContext(query_context_1); - auto holder_2 = cache.getOrSet(key, 25, 5); /// Get [25, 29] once again. + auto holder_2 = cache.getOrSet(key, 25, 5, false); /// Get [25, 29] once again. auto segments_2 = fromHolder(holder_2); ASSERT_EQ(segments.size(), 3); @@ -414,7 +409,7 @@ TEST(LRUFileCache, get) /// and notify_all() is also called from destructor of holder. std::optional holder; - holder.emplace(cache.getOrSet(key, 3, 23)); /// Get [3, 25] + holder.emplace(cache.getOrSet(key, 3, 23, false)); /// Get [3, 25] auto segments = fromHolder(*holder); ASSERT_EQ(segments.size(), 3); @@ -440,7 +435,7 @@ TEST(LRUFileCache, get) DB::CurrentThread::QueryScope query_scope_holder_1(query_context_1); thread_status_1.attachQueryContext(query_context_1); - auto holder_2 = cache.getOrSet(key, 3, 23); /// Get [3, 25] once again + auto holder_2 = cache.getOrSet(key, 3, 23, false); /// Get [3, 25] once again auto segments_2 = fromHolder(*holder); ASSERT_EQ(segments_2.size(), 3); @@ -487,7 +482,8 @@ TEST(LRUFileCache, get) auto cache2 = DB::LRUFileCache(cache_base_path, settings); cache2.initialize(); - auto holder1 = cache2.getOrSet(key, 2, 28); /// Get [2, 29] + auto holder1 = cache2.getOrSet(key, 2, 28, false); /// Get [2, 29] + auto segments1 = fromHolder(holder1); ASSERT_EQ(segments1.size(), 5); @@ -506,7 +502,7 @@ TEST(LRUFileCache, get) auto cache2 = DB::LRUFileCache(caches_dir / "cache2", settings2); cache2.initialize(); - auto holder1 = cache2.getOrSet(key, 0, 25); /// Get [0, 24] + auto holder1 = cache2.getOrSet(key, 0, 25, false); /// Get [0, 24] auto segments1 = fromHolder(holder1); ASSERT_EQ(segments1.size(), 3); diff --git a/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp b/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp index d968a87de04..88ee584816b 100644 --- a/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp +++ b/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp @@ -56,6 +56,7 @@ CachedReadBufferFromRemoteFS::CachedReadBufferFromRemoteFS( , enable_logging(!query_id.empty() && settings_.enable_filesystem_cache_log) , current_buffer_id(getRandomASCIIString(8)) , query_context_holder(cache_->getQueryContextHolder(query_id, settings_)) + , is_persistent(false) /// Unused for now, see PR 36171 { } @@ -102,7 +103,7 @@ void CachedReadBufferFromRemoteFS::initialize(size_t offset, size_t size) } else { - file_segments_holder.emplace(cache->getOrSet(cache_key, offset, size)); + file_segments_holder.emplace(cache->getOrSet(cache_key, offset, size, is_persistent)); } /** @@ -120,7 +121,7 @@ void CachedReadBufferFromRemoteFS::initialize(size_t offset, size_t size) SeekableReadBufferPtr CachedReadBufferFromRemoteFS::getCacheReadBuffer(size_t offset) const { - auto path = cache->getPathInLocalCache(cache_key, offset); + auto path = cache->getPathInLocalCache(cache_key, offset, is_persistent); ReadSettings local_read_settings{settings}; /// Do not allow to use asynchronous version of LocalFSReadMethod. diff --git a/src/Disks/IO/CachedReadBufferFromRemoteFS.h b/src/Disks/IO/CachedReadBufferFromRemoteFS.h index 5094f1e5047..867b8538260 100644 --- a/src/Disks/IO/CachedReadBufferFromRemoteFS.h +++ b/src/Disks/IO/CachedReadBufferFromRemoteFS.h @@ -1,11 +1,12 @@ #pragma once -#include +#include #include #include #include #include #include +#include namespace CurrentMetrics @@ -125,6 +126,8 @@ private: ProfileEvents::Counters current_file_segment_counters; IFileCache::QueryContextHolder query_context_holder; + + bool is_persistent; }; } diff --git a/src/Disks/ObjectStorages/DiskObjectStorage.cpp b/src/Disks/ObjectStorages/DiskObjectStorage.cpp index d388223cefb..40ae0b22999 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorage.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorage.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp b/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp index eb9d7107d39..9311cb2c12a 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include namespace DB diff --git a/src/Disks/ObjectStorages/DiskObjectStorageRemoteMetadataRestoreHelper.cpp b/src/Disks/ObjectStorages/DiskObjectStorageRemoteMetadataRestoreHelper.cpp index 0e35963e9cb..96667b8496a 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorageRemoteMetadataRestoreHelper.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorageRemoteMetadataRestoreHelper.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace DB { diff --git a/src/Disks/ObjectStorages/IObjectStorage.cpp b/src/Disks/ObjectStorages/IObjectStorage.cpp index 475bcca4ea4..d29ecc24aeb 100644 --- a/src/Disks/ObjectStorages/IObjectStorage.cpp +++ b/src/Disks/ObjectStorages/IObjectStorage.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace DB @@ -31,7 +32,7 @@ void IObjectStorage::removeFromCache(const std::string & path) if (cache) { auto key = cache->hash(path); - cache->remove(key); + cache->removeIfExists(key); } } diff --git a/src/Disks/ObjectStorages/IObjectStorage.h b/src/Disks/ObjectStorages/IObjectStorage.h index b9ac497f54f..4921059c6b7 100644 --- a/src/Disks/ObjectStorages/IObjectStorage.h +++ b/src/Disks/ObjectStorages/IObjectStorage.h @@ -13,7 +13,7 @@ #include #include -#include +#include #include diff --git a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp index e7fb5e1e5ac..851ed31d844 100644 --- a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp +++ b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include namespace DB diff --git a/src/IO/WriteBufferFromS3.cpp b/src/IO/WriteBufferFromS3.cpp index 1d3ec6095d5..a81b81197d3 100644 --- a/src/IO/WriteBufferFromS3.cpp +++ b/src/IO/WriteBufferFromS3.cpp @@ -3,7 +3,7 @@ #if USE_AWS_S3 #include -#include +#include #include #include @@ -95,7 +95,7 @@ void WriteBufferFromS3::nextImpl() { auto cache_key = cache->hash(key); - file_segments_holder.emplace(cache->setDownloading(cache_key, current_download_offset, size)); + file_segments_holder.emplace(cache->setDownloading(cache_key, current_download_offset, size, false)); current_download_offset += size; size_t remaining_size = size; diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 649c6b4e4ab..4ac5acfd60f 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Interpreters/InterpreterSystemQuery.cpp b/src/Interpreters/InterpreterSystemQuery.cpp index 6a9ed69e247..1b3616e8a75 100644 --- a/src/Interpreters/InterpreterSystemQuery.cpp +++ b/src/Interpreters/InterpreterSystemQuery.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -313,12 +313,12 @@ BlockIO InterpreterSystemQuery::execute() { auto caches = FileCacheFactory::instance().getAll(); for (const auto & [_, cache_data] : caches) - cache_data.cache->remove(); + cache_data.cache->removeIfReleasable(false); } else { auto cache = FileCacheFactory::instance().get(query.filesystem_cache_path); - cache->remove(); + cache->removeIfReleasable(false); } break; } diff --git a/src/Storages/MergeTree/MergeTreeDeduplicationLog.cpp b/src/Storages/MergeTree/MergeTreeDeduplicationLog.cpp index 07f6e3f3be7..d0f4d8b3604 100644 --- a/src/Storages/MergeTree/MergeTreeDeduplicationLog.cpp +++ b/src/Storages/MergeTree/MergeTreeDeduplicationLog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/src/Storages/System/StorageSystemFilesystemCache.cpp b/src/Storages/System/StorageSystemFilesystemCache.cpp index f3ead8a95f0..4b76163363a 100644 --- a/src/Storages/System/StorageSystemFilesystemCache.cpp +++ b/src/Storages/System/StorageSystemFilesystemCache.cpp @@ -2,7 +2,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -43,7 +44,8 @@ void StorageSystemFilesystemCache::fillData(MutableColumns & res_columns, Contex for (const auto & file_segment : file_segments) { res_columns[0]->insert(cache_base_path); - res_columns[1]->insert(cache->getPathInLocalCache(file_segment->key(), file_segment->offset())); + res_columns[1]->insert( + cache->getPathInLocalCache(file_segment->key(), file_segment->offset(), file_segment->isPersistent())); const auto & range = file_segment->range(); res_columns[2]->insert(range.left); diff --git a/src/Storages/System/StorageSystemRemoteDataPaths.cpp b/src/Storages/System/StorageSystemRemoteDataPaths.cpp index dbce4c25773..a009f9d25c9 100644 --- a/src/Storages/System/StorageSystemRemoteDataPaths.cpp +++ b/src/Storages/System/StorageSystemRemoteDataPaths.cpp @@ -1,7 +1,7 @@ #include "StorageSystemRemoteDataPaths.h" #include #include -#include +#include #include #include #include From 048f56bf4de257ee65b9973078e34426ce93d3a3 Mon Sep 17 00:00:00 2001 From: Danila Kutenin Date: Wed, 15 Jun 2022 14:40:21 +0000 Subject: [PATCH 119/204] Fix some tests and comments --- src/Common/UTF8Helpers.h | 5 ++++- src/Common/memcmpSmall.h | 2 +- src/Common/memcpySmall.h | 17 +++++++++-------- src/Functions/toValidUTF8.cpp | 18 ++++-------------- src/IO/ReadHelpers.cpp | 1 + src/IO/WriteBufferValidUTF8.cpp | 18 ++++-------------- .../01375_compact_parts_codecs.reference | 2 +- 7 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/Common/UTF8Helpers.h b/src/Common/UTF8Helpers.h index 04326f6910c..72bdb965789 100644 --- a/src/Common/UTF8Helpers.h +++ b/src/Common/UTF8Helpers.h @@ -74,13 +74,16 @@ inline size_t countCodePoints(const UInt8 * data, size_t size) res += __builtin_popcount(_mm_movemask_epi8( _mm_cmpgt_epi8(_mm_loadu_si128(reinterpret_cast(data)), threshold))); #elif defined(__aarch64__) && defined(__ARM_NEON) + /// Returns a 64 bit mask of nibbles (4 bits for each byte). auto get_nibble_mask = [](uint8x16_t input) -> uint64_t { return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(input), 4)), 0); }; constexpr auto bytes_sse = 16; const auto * src_end_sse = data + size / bytes_sse * bytes_sse; + const auto threshold = vdupq_n_s8(0xBF); + for (; data < src_end_sse; data += bytes_sse) - res += __builtin_popcountll(get_nibble_mask(vcgtq_s8(vld1q_s8(reinterpret_cast(data)), vdupq_n_s8(0xBF)))); + res += __builtin_popcountll(get_nibble_mask(vcgtq_s8(vld1q_s8(reinterpret_cast(data)), threshold))); res >>= 2; #endif diff --git a/src/Common/memcmpSmall.h b/src/Common/memcmpSmall.h index 9a47f46639e..7b977a4a23c 100644 --- a/src/Common/memcmpSmall.h +++ b/src/Common/memcmpSmall.h @@ -704,7 +704,7 @@ inline bool memoryIsZeroSmallAllowOverflow15(const void * data, size_t size) #else -# include +#include template inline int memcmpSmallAllowOverflow15(const Char * a, size_t a_size, const Char * b, size_t b_size) diff --git a/src/Common/memcpySmall.h b/src/Common/memcpySmall.h index 16be0906299..6cadc19262f 100644 --- a/src/Common/memcpySmall.h +++ b/src/Common/memcpySmall.h @@ -37,18 +37,19 @@ #ifdef __SSE2__ /// Implementation for x86 platform namespace detail { -inline void memcpySmallAllowReadWriteOverflow15Impl(char * __restrict dst, const char * __restrict src, ssize_t n) -{ - while (n > 0) + inline void memcpySmallAllowReadWriteOverflow15Impl(char * __restrict dst, const char * __restrict src, ssize_t n) { - _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), _mm_loadu_si128(reinterpret_cast(src))); + while (n > 0) + { + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), + _mm_loadu_si128(reinterpret_cast(src))); - dst += 16; - src += 16; - n -= 16; + dst += 16; + src += 16; + n -= 16; + } } } -} /** Works under assumption, that it's possible to read up to 15 excessive bytes after end of 'src' region * and to write any garbage into up to 15 bytes after end of 'dst' region. diff --git a/src/Functions/toValidUTF8.cpp b/src/Functions/toValidUTF8.cpp index 6334cd2516e..0ee62bd4961 100644 --- a/src/Functions/toValidUTF8.cpp +++ b/src/Functions/toValidUTF8.cpp @@ -21,18 +21,6 @@ namespace DB { -#if defined(__aarch64__) && defined(__ARM_NEON) -inline uint64_t getNibbleMask(uint8x16_t res) -{ - return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(res), 4)), 0); -} - -inline bool onlyASCII(uint8x16_t input) -{ - return getNibbleMask(vcgeq_u8(input, vdupq_n_u8(0x80))) == 0; -} -#endif - namespace ErrorCodes { extern const int ILLEGAL_COLUMN; @@ -87,14 +75,16 @@ struct ToValidUTF8Impl /// Fast skip of ASCII for aarch64. static constexpr size_t SIMD_BYTES = 16; const char * simd_end = p + (end - p) / SIMD_BYTES * SIMD_BYTES; - + /// Returns a 64 bit mask of nibbles (4 bits for each byte). + auto get_nibble_mask = [](uint8x16_t input) -> uint64_t + { return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(input), 4)), 0); }; /// Other options include /// vmaxvq_u8(input) < 0b10000000; /// Used by SIMDJSON, has latency 3 for M1, 6 for everything else /// SIMDJSON uses it for 64 byte masks, so it's a little different. /// vmaxvq_u32(vandq_u32(input, vdupq_n_u32(0x80808080))) // u32 version has latency 3 /// shrn version has universally <=3 cycles, on servers 2 cycles. - while (p < simd_end && onlyASCII(vld1q_u8(reinterpret_cast(p)))) + while (p < simd_end && get_nibble_mask(vcgeq_u8(vld1q_u8(reinterpret_cast(p)), vdupq_n_u8(0x80))) == 0) p += SIMD_BYTES; if (!(p < end)) diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index 0972e97e39a..f09292cd349 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -706,6 +706,7 @@ void readCSVStringInto(Vector & s, ReadBuffer & buf, const FormatSettings::CSV & auto rc = vdupq_n_u8('\r'); auto nc = vdupq_n_u8('\n'); auto dc = vdupq_n_u8(delimiter); + /// Returns a 64 bit mask of nibbles (4 bits for each byte). auto get_nibble_mask = [](uint8x16_t input) -> uint64_t { return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(input), 4)), 0); }; for (; next_pos + 15 < buf.buffer().end(); next_pos += 16) diff --git a/src/IO/WriteBufferValidUTF8.cpp b/src/IO/WriteBufferValidUTF8.cpp index a8fac26603f..10e86f01343 100644 --- a/src/IO/WriteBufferValidUTF8.cpp +++ b/src/IO/WriteBufferValidUTF8.cpp @@ -16,18 +16,6 @@ namespace DB { -#if defined(__aarch64__) && defined(__ARM_NEON) -inline uint64_t getNibbleMask(uint8x16_t res) -{ - return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(res), 4)), 0); -} - -inline bool onlyASCII(uint8x16_t input) -{ - return getNibbleMask(vcgeq_u8(input, vdupq_n_u8(0x80))) == 0; -} -#endif - const size_t WriteBufferValidUTF8::DEFAULT_SIZE = 4096; /** Index into the table below with the first byte of a UTF-8 sequence to @@ -98,14 +86,16 @@ void WriteBufferValidUTF8::nextImpl() /// Fast skip of ASCII for aarch64. static constexpr size_t SIMD_BYTES = 16; const char * simd_end = p + (pos - p) / SIMD_BYTES * SIMD_BYTES; - + /// Returns a 64 bit mask of nibbles (4 bits for each byte). + auto get_nibble_mask = [](uint8x16_t input) -> uint64_t + { return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(input), 4)), 0); }; /// Other options include /// vmaxvq_u8(input) < 0b10000000; /// Used by SIMDJSON, has latency 3 for M1, 6 for everything else /// SIMDJSON uses it for 64 byte masks, so it's a little different. /// vmaxvq_u32(vandq_u32(input, vdupq_n_u32(0x80808080))) // u32 version has latency 3 /// shrn version has universally <=3 cycles, on servers 2 cycles. - while (p < simd_end && onlyASCII(vld1q_u8(reinterpret_cast(p)))) + while (p < simd_end && get_nibble_mask(vcgeq_u8(vld1q_u8(reinterpret_cast(p)), vdupq_n_u8(0x80))) == 0) p += SIMD_BYTES; if (!(p < pos)) diff --git a/tests/queries/0_stateless/01375_compact_parts_codecs.reference b/tests/queries/0_stateless/01375_compact_parts_codecs.reference index b48892597b6..0c9e9c8ac47 100644 --- a/tests/queries/0_stateless/01375_compact_parts_codecs.reference +++ b/tests/queries/0_stateless/01375_compact_parts_codecs.reference @@ -4,6 +4,6 @@ 11965 11890 499500 499500 999 499500 499500 999 -5858 11890 +5857 11890 499500 499500 999 499500 499500 999 From b1137c9cba28c5d521e8646866f1bc2e98dcb5db Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Wed, 15 Jun 2022 15:21:05 +0000 Subject: [PATCH 120/204] Fix: build error + clang tidy warning fixed --- src/Columns/ColumnVector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 391baa188d6..f9fc14874aa 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -337,7 +337,7 @@ void ColumnVector::updatePermutation(IColumn::PermutationSortDirection direct PaddedPODArray> pairs(size); size_t index = 0; - for (auto it = begin; it != end; ++it) + for (auto * it = begin; it != end; ++it) { pairs[index] = {data[*it], static_cast(*it)}; ++index; From 9cac78b49865fc83be8255abac3115a63cfd289a Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Jun 2022 19:54:46 +0200 Subject: [PATCH 121/204] make tests with tsan less flaky --- base/harmful/harmful.c | 31 ++++++++++++++++ contrib/librdkafka | 2 +- docker/test/stress/run.sh | 35 ++++++++++--------- .../00984_parser_stack_overflow.sh | 2 ++ .../01172_transaction_counters.sql | 3 +- .../01183_custom_separated_format_http.sh | 2 ++ .../01184_long_insert_values_huge_strings.sh | 3 +- .../0_stateless/01651_lc_insert_tiny_log.sql | 3 ++ ..._long_zstd_http_compression_json_format.sh | 3 +- .../0_stateless/01926_order_by_desc_limit.sql | 3 +- .../00159_parallel_formatting_http.sh | 2 ++ 11 files changed, 67 insertions(+), 22 deletions(-) diff --git a/base/harmful/harmful.c b/base/harmful/harmful.c index 5a27cae0383..6112f9a339c 100644 --- a/base/harmful/harmful.c +++ b/base/harmful/harmful.c @@ -260,4 +260,35 @@ TRAP(mq_timedreceive) TRAP(wordexp) TRAP(wordfree) +/// C11 threading primitives are not supported by ThreadSanitizer. +/// Also we should avoid using them for compatibility with old libc. +TRAP(thrd_create) +TRAP(thrd_equal) +TRAP(thrd_current) +TRAP(thrd_sleep) +TRAP(thrd_yield) +TRAP(thrd_exit) +TRAP(thrd_detach) +TRAP(thrd_join) + +TRAP(mtx_init) +TRAP(mtx_lock) +TRAP(mtx_timedlock) +TRAP(mtx_trylock) +TRAP(mtx_unlock) +TRAP(mtx_destroy) +TRAP(call_once) + +TRAP(cnd_init) +TRAP(cnd_signal) +TRAP(cnd_broadcast) +TRAP(cnd_wait) +TRAP(cnd_timedwait) +TRAP(cnd_destroy) + +TRAP(tss_create) +TRAP(tss_get) +TRAP(tss_set) +TRAP(tss_delete) + #endif diff --git a/contrib/librdkafka b/contrib/librdkafka index b8554f16820..81b413cc1c2 160000 --- a/contrib/librdkafka +++ b/contrib/librdkafka @@ -1 +1 @@ -Subproject commit b8554f1682062c85ba519eb54ef2f90e02b812cb +Subproject commit 81b413cc1c2a33ad4e96df856b89184efbd6221c diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index fa9d427aa33..9f8ab3eec9a 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -7,26 +7,27 @@ set -x # Thread Fuzzer allows to check more permutations of possible thread scheduling # and find more potential issues. +is_tsan_build=$(clickhouse local -q "select value like '% -fsanitize=thread %' from system.build_options where name='CXX_FLAGS'") +if [ "$is_tsan_build" -eq "0" ]; then + export THREAD_FUZZER_CPU_TIME_PERIOD_US=1000 + export THREAD_FUZZER_SLEEP_PROBABILITY=0.1 + export THREAD_FUZZER_SLEEP_TIME_US=100000 -export THREAD_FUZZER_CPU_TIME_PERIOD_US=1000 -export THREAD_FUZZER_SLEEP_PROBABILITY=0.1 -export THREAD_FUZZER_SLEEP_TIME_US=100000 + export THREAD_FUZZER_pthread_mutex_lock_BEFORE_MIGRATE_PROBABILITY=1 + export THREAD_FUZZER_pthread_mutex_lock_AFTER_MIGRATE_PROBABILITY=1 + export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_MIGRATE_PROBABILITY=1 + export THREAD_FUZZER_pthread_mutex_unlock_AFTER_MIGRATE_PROBABILITY=1 -export THREAD_FUZZER_pthread_mutex_lock_BEFORE_MIGRATE_PROBABILITY=1 -export THREAD_FUZZER_pthread_mutex_lock_AFTER_MIGRATE_PROBABILITY=1 -export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_MIGRATE_PROBABILITY=1 -export THREAD_FUZZER_pthread_mutex_unlock_AFTER_MIGRATE_PROBABILITY=1 - -export THREAD_FUZZER_pthread_mutex_lock_BEFORE_SLEEP_PROBABILITY=0.001 -export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_PROBABILITY=0.001 -export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_PROBABILITY=0.001 -export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_PROBABILITY=0.001 -export THREAD_FUZZER_pthread_mutex_lock_BEFORE_SLEEP_TIME_US=10000 - -export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_TIME_US=10000 -export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_TIME_US=10000 -export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_TIME_US=10000 + export THREAD_FUZZER_pthread_mutex_lock_BEFORE_SLEEP_PROBABILITY=0.001 + export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_PROBABILITY=0.001 + export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_PROBABILITY=0.001 + export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_PROBABILITY=0.001 + export THREAD_FUZZER_pthread_mutex_lock_BEFORE_SLEEP_TIME_US=10000 + export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_TIME_US=10000 + export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_TIME_US=10000 + export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_TIME_US=10000 +fi function install_packages() { diff --git a/tests/queries/0_stateless/00984_parser_stack_overflow.sh b/tests/queries/0_stateless/00984_parser_stack_overflow.sh index 329e51e774a..7c4a6336a51 100755 --- a/tests/queries/0_stateless/00984_parser_stack_overflow.sh +++ b/tests/queries/0_stateless/00984_parser_stack_overflow.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: no-tsan +# FIXME should work with tsan # Such a huge timeout mostly for debug build. CLICKHOUSE_CURL_TIMEOUT=60 diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index b84a7b25c47..83bad35c40b 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -1,5 +1,6 @@ --- Tags: no-s3-storage +-- Tags: no-s3-storage, no-tsan -- FIXME this test fails with S3 due to a bug in DiskCacheWrapper +-- FIXME should work with tsan drop table if exists txn_counters; create table txn_counters (n Int64, creation_tid DEFAULT transactionID()) engine=MergeTree order by n; diff --git a/tests/queries/0_stateless/01183_custom_separated_format_http.sh b/tests/queries/0_stateless/01183_custom_separated_format_http.sh index 8eaa22f4ecc..0aa750a171f 100755 --- a/tests/queries/0_stateless/01183_custom_separated_format_http.sh +++ b/tests/queries/0_stateless/01183_custom_separated_format_http.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: no-tsan +# FIXME should work with tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh b/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh index 09a43d13a42..9c3a2d295f7 100755 --- a/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh +++ b/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Tags: long +# Tags: long, no-tsan +# FIXME should work with tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql b/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql index 22532529812..15baa88ec4e 100644 --- a/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql +++ b/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql @@ -1,3 +1,6 @@ +-- Tags: no-tsan +-- FIXME should work with tsan + drop table if exists perf_lc_num; CREATE TABLE perf_lc_num(  num UInt8,  arr Array(LowCardinality(Int64)) default [num]  ) ENGINE = TinyLog; diff --git a/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh b/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh index e10032e04fd..e198adc2dc6 100755 --- a/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh +++ b/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Tags: long, no-fasttest +# Tags: long, no-fasttest, no-tsan +# FIXME should work with tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01926_order_by_desc_limit.sql b/tests/queries/0_stateless/01926_order_by_desc_limit.sql index 86468b4fcd6..785a2e10ee3 100644 --- a/tests/queries/0_stateless/01926_order_by_desc_limit.sql +++ b/tests/queries/0_stateless/01926_order_by_desc_limit.sql @@ -1,4 +1,5 @@ --- Tags: no-random-settings +-- Tags: no-random-settings, no-tsan +-- FIXME should work with tsan DROP TABLE IF EXISTS order_by_desc; diff --git a/tests/queries/1_stateful/00159_parallel_formatting_http.sh b/tests/queries/1_stateful/00159_parallel_formatting_http.sh index ea4a4d12867..b7382c5f491 100755 --- a/tests/queries/1_stateful/00159_parallel_formatting_http.sh +++ b/tests/queries/1_stateful/00159_parallel_formatting_http.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: no-tsan +# FIXME should work with tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 607dd8d6ca77d36c13437bc68d3e33983021715d Mon Sep 17 00:00:00 2001 From: Danila Kutenin Date: Wed, 15 Jun 2022 18:16:56 +0000 Subject: [PATCH 122/204] Restart the pipeline, I guess --- src/Columns/ColumnVector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 391baa188d6..f9fc14874aa 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -337,7 +337,7 @@ void ColumnVector::updatePermutation(IColumn::PermutationSortDirection direct PaddedPODArray> pairs(size); size_t index = 0; - for (auto it = begin; it != end; ++it) + for (auto * it = begin; it != end; ++it) { pairs[index] = {data[*it], static_cast(*it)}; ++index; From d07753aa20d8f7ec6bf851dc6151a38387471d13 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Wed, 15 Jun 2022 14:59:47 -0400 Subject: [PATCH 123/204] add more description --- .../settings/merge-tree-settings.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/en/operations/settings/merge-tree-settings.md b/docs/en/operations/settings/merge-tree-settings.md index 2a010d71f78..e72314d84a5 100644 --- a/docs/en/operations/settings/merge-tree-settings.md +++ b/docs/en/operations/settings/merge-tree-settings.md @@ -166,7 +166,7 @@ Similar to [replicated_deduplication_window](#replicated-deduplication-window), ## max_replicated_logs_to_keep -How many records may be in log, if there is inactive replica. Inactive replica becomes lost when when this number exceed. +How many records may be in the ClickHouse Keeper log if there is inactive replica. An inactive replica becomes lost when when this number exceed. Possible values: @@ -186,7 +186,7 @@ Default value: 10 ## prefer_fetch_merged_part_time_threshold -If time passed after replication log entry creation exceeds this threshold and sum size of parts is greater than `prefer_fetch_merged_part_size_threshold`, prefer fetching merged part from replica instead of doing merge locally. To speed up very long merges. +If the time passed since a replication log (ClickHouse Keeper or ZooKeeper) entry creation exceeds this threshold, and the sum of the size of parts is greater than `prefer_fetch_merged_part_size_threshold`, then prefer fetching merged part from a replica instead of doing merge locally. This is to speed up very long merges. Possible values: @@ -196,7 +196,7 @@ Default value: 3600 ## prefer_fetch_merged_part_size_threshold -If sum size of parts exceeds this threshold and time passed after replication log entry creation is greater than `prefer_fetch_merged_part_time_threshold`, prefer fetching merged part from replica instead of doing merge locally. To speed up very long merges. +If the sum of the size of parts exceeds this threshold and the time since a replication log entry creation is greater than `prefer_fetch_merged_part_time_threshold`, then prefer fetching merged part from a replica instead of doing merge locally. This is to speed up very long merges. Possible values: @@ -206,17 +206,17 @@ Default value: 10,737,418,240 ## execute_merges_on_single_replica_time_threshold -When greater than zero only a single replica starts the merge immediately, others wait up to that amount of time to download the result instead of doing merges locally. If the chosen replica doesn't finish the merge during that amount of time, fallback to standard behavior happens. +When this setting has a value greater than zero, only a single replica starts the merge immediately, and other replicas wait up to that amount of time to download the result instead of doing merges locally. If the chosen replica doesn't finish the merge during that amount of time, fallback to standard behavior happens. Possible values: - Any positive integer. -Default value: 0 +Default value: 0 (seconds) ## remote_fs_execute_merges_on_single_replica_time_threshold -When greater than zero only a single replica starts the merge immediately if merged part on shared storage and `allow_remote_fs_zero_copy_replication` is enabled. +When this setting has a value greater than than zero only a single replica starts the merge immediately if merged part on shared storage and `allow_remote_fs_zero_copy_replication` is enabled. Possible values: @@ -236,7 +236,7 @@ Default value: 7200 ## always_fetch_merged_part -If true, replica never merge parts and always download merged parts from other replicas. +If true, this replica never merges parts and always downloads merged parts from other replicas. Possible values: @@ -267,7 +267,7 @@ Default value: 1,073,741,824 ## max_files_to_modify_in_alter_columns -Not apply ALTER if number of files for modification(deletion, addition) more than this. +Do not apply ALTER if number of files for modification(deletion, addition) is greater than this setting. Possible values: @@ -277,7 +277,7 @@ Default value: 75 ## max_files_to_remove_in_alter_columns -Not apply ALTER, if number of files for deletion more than this. +Do not apply ALTER, if the number of files for deletion is greater than this setting. Possible values: @@ -287,7 +287,7 @@ Default value: 50 ## replicated_max_ratio_of_wrong_parts -If ratio of wrong parts to total number of parts is less than this - allow to start. +If the ratio of wrong parts to total number of parts is less than this - allow to start. Possible values: @@ -317,7 +317,7 @@ Default value: Inherited from default profile `http_connection_timeout` if not s ## replicated_can_become_leader -If true, Replicated tables replicas on this node will try to acquire leadership. +If true, replicated tables replicas on this node will try to acquire leadership. Possible values: From e34994331f4297f12c368d4ca38339d328eb7e8d Mon Sep 17 00:00:00 2001 From: Danila Kutenin Date: Wed, 15 Jun 2022 20:58:35 +0000 Subject: [PATCH 124/204] Restart checks, DockerHubPush failed for some reason From 2297708032b6cac56879c73876359e84c0827ac7 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Jun 2022 20:08:18 +0200 Subject: [PATCH 125/204] use random container name --- tests/integration/runner | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/integration/runner b/tests/integration/runner index 522fdb7b745..ef48dfe687d 100755 --- a/tests/integration/runner +++ b/tests/integration/runner @@ -9,11 +9,21 @@ import logging import signal import subprocess import sys +import string +import random + + +def random_str(length=6): + alphabet = string.ascii_lowercase + string.digits + return "".join( + random.SystemRandom().choice(alphabet) for _ in range(length) + ) + CUR_FILE_DIR = os.path.dirname(os.path.realpath(__file__)) DEFAULT_CLICKHOUSE_ROOT = os.path.abspath(os.path.join(CUR_FILE_DIR, "../../")) CURRENT_WORK_DIR = os.getcwd() -CONTAINER_NAME = "clickhouse_integration_tests" +CONTAINER_NAME = f"clickhouse_integration_tests_{random_str()}" CONFIG_DIR_IN_REPO = "programs/server" INTERGATION_DIR_IN_REPO = "tests/integration" From b936abe8136a96ccb8910a3c5dc40ac84f330e89 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Thu, 16 Jun 2022 00:07:59 +0200 Subject: [PATCH 126/204] + comments about keeping stuff in sync --- programs/server/Server.cpp | 2 ++ src/Daemon/BaseDaemon.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index cd2ecd56bf5..b013ba9ee05 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -744,6 +744,8 @@ int Server::main(const std::vector & /*args*/) /// But there are other sections of the binary (e.g. exception handling tables) /// that are interpreted (not executed) but can alter the behaviour of the program as well. + /// Please keep the below log messages in-sync with the ones in daemon/BaseDaemon.cpp + String calculated_binary_hash = getHashOfLoadedBinaryHex(); if (stored_binary_hash.empty()) diff --git a/src/Daemon/BaseDaemon.cpp b/src/Daemon/BaseDaemon.cpp index 30f96592366..62fcebb10bb 100644 --- a/src/Daemon/BaseDaemon.cpp +++ b/src/Daemon/BaseDaemon.cpp @@ -352,6 +352,7 @@ private: #if defined(OS_LINUX) /// Write information about binary checksum. It can be difficult to calculate, so do it only after printing stack trace. + /// Please keep the below log messages in-sync with the ones in programs/server/Server.cpp String calculated_binary_hash = getHashOfLoadedBinaryHex(); if (daemon.stored_binary_hash.empty()) { From 92b7b9789a39ea3c811ca0f3011402638d0355bd Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 16 Jun 2022 03:41:09 +0200 Subject: [PATCH 127/204] try to fix fpc codec --- src/Compression/CompressionCodecFPC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compression/CompressionCodecFPC.cpp b/src/Compression/CompressionCodecFPC.cpp index 28a0b1b0299..247eb73b65b 100644 --- a/src/Compression/CompressionCodecFPC.cpp +++ b/src/Compression/CompressionCodecFPC.cpp @@ -41,7 +41,7 @@ protected: bool isGenericCompression() const override { return false; } private: - static constexpr UInt32 HEADER_SIZE = 3; + static constexpr UInt32 HEADER_SIZE = 2; // below members are used by compression, decompression ignores them: const UInt8 float_width; // size of uncompressed float in bytes From 02cce40b3ab7bba8b6e57c832e180d83f33765f3 Mon Sep 17 00:00:00 2001 From: wangdh15 <13020023780@163.com> Date: Thu, 16 Jun 2022 10:19:32 +0800 Subject: [PATCH 128/204] when using clang12 compile, the unused filed shard_count will cause compile error. So delete it. --- src/Interpreters/ClusterProxy/SelectStreamFactory.cpp | 1 - src/Processors/QueryPlan/ReadFromRemote.cpp | 2 -- src/Processors/QueryPlan/ReadFromRemote.h | 3 --- 3 files changed, 6 deletions(-) diff --git a/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp b/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp index 498e296a3af..fce2e9b2f08 100644 --- a/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp +++ b/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp @@ -257,7 +257,6 @@ SelectStreamFactory::ShardPlans SelectStreamFactory::createForShardWithParallelR std::move(scalars), std::move(external_tables), &Poco::Logger::get("ReadFromParallelRemoteReplicasStep"), - shard_count, storage_limits); remote_plan->addStep(std::move(read_from_remote)); diff --git a/src/Processors/QueryPlan/ReadFromRemote.cpp b/src/Processors/QueryPlan/ReadFromRemote.cpp index 6e2d776e1e4..65b902230f4 100644 --- a/src/Processors/QueryPlan/ReadFromRemote.cpp +++ b/src/Processors/QueryPlan/ReadFromRemote.cpp @@ -239,7 +239,6 @@ ReadFromParallelRemoteReplicasStep::ReadFromParallelRemoteReplicasStep( Scalars scalars_, Tables external_tables_, Poco::Logger * log_, - UInt32 shard_count_, std::shared_ptr storage_limits_) : ISourceStep(DataStream{.header = std::move(header_)}) , coordinator(std::move(coordinator_)) @@ -253,7 +252,6 @@ ReadFromParallelRemoteReplicasStep::ReadFromParallelRemoteReplicasStep( , external_tables{external_tables_} , storage_limits(std::move(storage_limits_)) , log(log_) - , shard_count(shard_count_) { std::vector description; diff --git a/src/Processors/QueryPlan/ReadFromRemote.h b/src/Processors/QueryPlan/ReadFromRemote.h index 0a21f240f5a..4d37a637250 100644 --- a/src/Processors/QueryPlan/ReadFromRemote.h +++ b/src/Processors/QueryPlan/ReadFromRemote.h @@ -83,7 +83,6 @@ public: Scalars scalars_, Tables external_tables_, Poco::Logger * log_, - UInt32 shard_count_, std::shared_ptr storage_limits_); String getName() const override { return "ReadFromRemoteParallelReplicas"; } @@ -110,8 +109,6 @@ private: std::shared_ptr storage_limits; Poco::Logger * log; - - UInt32 shard_count{0}; }; } From b2736b15fef1af661d64ac4fbe679eb3ab9d627f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 07:00:07 +0200 Subject: [PATCH 129/204] Changelog for 22.6 --- CHANGELOG.md | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9c70b24548..7013b791103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ### Table of Contents +**[ClickHouse release v22.6, 2022-06-16](#226)**
**[ClickHouse release v22.5, 2022-05-19](#225)**
**[ClickHouse release v22.4, 2022-04-20](#224)**
**[ClickHouse release v22.3-lts, 2022-03-17](#223)**
@@ -6,6 +7,170 @@ **[ClickHouse release v22.1, 2022-01-18](#221)**
**[Changelog for 2021](https://clickhouse.com/docs/en/whats-new/changelog/2021/)**
+### ClickHouse release 22.6, 2022-06-16 + +#### Backward Incompatible Change +* Remove support for octal number literals in SQL. In previous versions they were parsed as Float64. [#37765](https://github.com/ClickHouse/ClickHouse/pull/37765) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Changes how settings using `seconds` as type are parsed to support floating point values (for example: `max_execution_time=0.5`). Infinity or NaN values will throw an exception. [#37187](https://github.com/ClickHouse/ClickHouse/pull/37187) ([Raúl Marín](https://github.com/Algunenano)). +* Changed format of binary serialization of columns of experimental type `Object`. New format is more convenient to implement by third-party clients. [#37482](https://github.com/ClickHouse/ClickHouse/pull/37482) ([Anton Popov](https://github.com/CurtizJ)). +* Turn on setting `output_format_json_named_tuples_as_objects` by default. It allows to serialize named tuples as JSON objects in JSON formats. [#37756](https://github.com/ClickHouse/ClickHouse/pull/37756) ([Anton Popov](https://github.com/CurtizJ)). +* LIKE patterns with trailing escape symbol ('\\') are now disallowed (as mandated by the SQL standard). [#37764](https://github.com/ClickHouse/ClickHouse/pull/37764) ([Robert Schulze](https://github.com/rschu1ze)). + +#### New Feature +* A new codec [FPC](https://userweb.cs.txstate.edu/~burtscher/papers/dcc07a.pdf) algorithm for floating point data compression. [#37553](https://github.com/ClickHouse/ClickHouse/pull/37553) ([Mikhail Guzov](https://github.com/koloshmet)). +* Add `GROUPING` function. Closes [#19426](https://github.com/ClickHouse/ClickHouse/issues/19426). [#37163](https://github.com/ClickHouse/ClickHouse/pull/37163) ([Dmitry Novik](https://github.com/novikd)). +* Add new columnar JSON formats: `JSONColumns`, `JSONCompactColumns`, `JSONColumnsWithMetadata`. Closes [#36338](https://github.com/ClickHouse/ClickHouse/issues/36338) Closes [#34509](https://github.com/ClickHouse/ClickHouse/issues/34509). [#36975](https://github.com/ClickHouse/ClickHouse/pull/36975) ([Kruglov Pavel](https://github.com/Avogar)). +* Added open telemetry traces visualizing tool based on d3js. [#37810](https://github.com/ClickHouse/ClickHouse/pull/37810) ([Sergei Trifonov](https://github.com/serxa)). +* Support INSERTs into `system.zookeeper` table. Closes [#22130](https://github.com/ClickHouse/ClickHouse/issues/22130). [#37596](https://github.com/ClickHouse/ClickHouse/pull/37596) ([Han Fei](https://github.com/hanfei1991)). +* Executable user defined functions now support parameters. Example: `SELECT test_function(parameters)(arguments)`. Closes [#37578](https://github.com/ClickHouse/ClickHouse/issues/37578). [#37720](https://github.com/ClickHouse/ClickHouse/pull/37720) ([Maksim Kita](https://github.com/kitaisreal)). +* Add `merge_reason` column to system.part_log table. [#36912](https://github.com/ClickHouse/ClickHouse/pull/36912) ([Sema Checherinda](https://github.com/CheSema)). +* Add support for Maps and Records in Avro format. Add new setting `input_format_avro_null_as_default ` that allow to insert null as default in Avro format. Closes [#18925](https://github.com/ClickHouse/ClickHouse/issues/18925) Closes [#37378](https://github.com/ClickHouse/ClickHouse/issues/37378) Closes [#32899](https://github.com/ClickHouse/ClickHouse/issues/32899). [#37525](https://github.com/ClickHouse/ClickHouse/pull/37525) ([Kruglov Pavel](https://github.com/Avogar)). +* Add `clickhouse-disks` tool to introspect and operate on virtual filesystems configured for ClickHouse. [#36060](https://github.com/ClickHouse/ClickHouse/pull/36060) ([Artyom Yurkov](https://github.com/Varinara)). +* Adds H3 unidirectional edge functions. [#36843](https://github.com/ClickHouse/ClickHouse/pull/36843) ([Bharat Nallan](https://github.com/bharatnc)). +* Add support for calculating [hashids](https://hashids.org/) from unsigned integers. [#37013](https://github.com/ClickHouse/ClickHouse/pull/37013) ([Michael Nutt](https://github.com/mnutt)). +* Explicit `SALT` specification is allowed for `CREATE USER IDENTIFIED WITH sha256_hash`. [#37377](https://github.com/ClickHouse/ClickHouse/pull/37377) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Add two new settings `input_format_csv_skip_first_lines/input_format_tsv_skip_first_lines` to allow skipping specified number of lines in the beginning of the file in CSV/TSV formats. [#37537](https://github.com/ClickHouse/ClickHouse/pull/37537) ([Kruglov Pavel](https://github.com/Avogar)). +* `showCertificate` function shows current server's SSL certificate. [#37540](https://github.com/ClickHouse/ClickHouse/pull/37540) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* HTTP source for Data Dictionaries in Named Collections is supported. [#37581](https://github.com/ClickHouse/ClickHouse/pull/37581) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Added a new window function `nonNegativeDerivative(metric_column, timestamp_column[, INTERVAL x SECOND])`. [#37628](https://github.com/ClickHouse/ClickHouse/pull/37628) ([Andrey Zvonov](https://github.com/zvonand)). +* Implemented changing the comment for `ReplicatedMergeTree` tables. [#37416](https://github.com/ClickHouse/ClickHouse/pull/37416) ([Vasily Nemkov](https://github.com/Enmk)). +* Added `SYSTEM UNFREEZE` query that deletes the whole backup regardless if the corresponding table is deleted or not. [#36424](https://github.com/ClickHouse/ClickHouse/pull/36424) ([Vadim Volodin](https://github.com/PolyProgrammist)). + +#### Experimental Feature +* Enables `POPULATE` for `WINDOW VIEW`. [#36945](https://github.com/ClickHouse/ClickHouse/pull/36945) ([vxider](https://github.com/Vxider)). +* `ALTER TABLE ... MODIFY QUERY` support for `WINDOW VIEW`. [#37188](https://github.com/ClickHouse/ClickHouse/pull/37188) ([vxider](https://github.com/Vxider)). +* This PR changes the behavior of the `ENGINE` syntax in `WINDOW VIEW`, to make it like in `MATERIALIZED VIEW`. [#37214](https://github.com/ClickHouse/ClickHouse/pull/37214) ([vxider](https://github.com/Vxider)). + +#### Performance Improvement +* Improve performance and memory usage for select of subset of columns for formats Native, Protobuf, CapnProto, JSONEachRow, TSKV, all formats with suffixes WithNames/WithNamesAndTypes. Previously while selecting only subset of columns from files in these formats all columns were read and stored in memory. Now only required columns are read. This PR enables setting `input_format_skip_unknown_fields` by default, because otherwise in case of select of subset of columns exception will be thrown. [#37192](https://github.com/ClickHouse/ClickHouse/pull/37192) ([Kruglov Pavel](https://github.com/Avogar)). +* Now more filters can be pushed down for join. [#37472](https://github.com/ClickHouse/ClickHouse/pull/37472) ([Amos Bird](https://github.com/amosbird)). +* Load marks for only necessary columns when reading wide parts. [#36879](https://github.com/ClickHouse/ClickHouse/pull/36879) ([Anton Kozlov](https://github.com/tonickkozlov)). +* Improved performance of aggregation in case, when sparse columns (can be enabled by experimental setting `ratio_of_defaults_for_sparse_serialization` in `MergeTree` tables) are used as arguments in aggregate functions. [#37617](https://github.com/ClickHouse/ClickHouse/pull/37617) ([Anton Popov](https://github.com/CurtizJ)). +* Optimize function `COALESCE` with two arguments. [#37666](https://github.com/ClickHouse/ClickHouse/pull/37666) ([Anton Popov](https://github.com/CurtizJ)). +* Replace `multiIf` to `if` in case when `multiIf` has only one condition, because function `if` is more performant. [#37695](https://github.com/ClickHouse/ClickHouse/pull/37695) ([Anton Popov](https://github.com/CurtizJ)). +* Improve performance of `dictGetDescendants`, `dictGetChildren` functions, create temporary parent to children hierarchical index per query, not per function call during query. Allow to specify `BIDIRECTIONAL` for `HIERARHICAL` attributes, dictionary will maintain parent to children index in memory, that way functions `dictGetDescendants`, `dictGetChildren` will not create temporary index per query. Closes [#32481](https://github.com/ClickHouse/ClickHouse/issues/32481). [#37148](https://github.com/ClickHouse/ClickHouse/pull/37148) ([Maksim Kita](https://github.com/kitaisreal)). +* Aggregates state destruction now may be posted on a thread pool. For queries with LIMIT and big state it provides significant speedup, e.g. `select uniq(number) from numbers_mt(1e7) group by number limit 100` became around 2.5x faster. [#37855](https://github.com/ClickHouse/ClickHouse/pull/37855) ([Nikita Taranov](https://github.com/nickitat)). +* Improve sort performance by single column. [#37195](https://github.com/ClickHouse/ClickHouse/pull/37195) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of single column sorting using sorting queue specializations. [#37990](https://github.com/ClickHouse/ClickHouse/pull/37990) ([Maksim Kita](https://github.com/kitaisreal)). +* Improved performance on array norm and distance functions 2x-4x times. [#37394](https://github.com/ClickHouse/ClickHouse/pull/37394) ([Alexander Gololobov](https://github.com/davenger)). +* Improve performance of number comparison functions using dynamic dispatch. [#37399](https://github.com/ClickHouse/ClickHouse/pull/37399) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of ORDER BY with LIMIT. [#37481](https://github.com/ClickHouse/ClickHouse/pull/37481) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of `hasAll` function using dynamic dispatch infrastructure. [#37484](https://github.com/ClickHouse/ClickHouse/pull/37484) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of `greatCircleAngle`, `greatCircleDistance`, `geoDistance` functions. [#37524](https://github.com/ClickHouse/ClickHouse/pull/37524) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of insert into MergeTree if there are multiple columns in ORDER BY. [#35762](https://github.com/ClickHouse/ClickHouse/pull/35762) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix excessive CPU usage in background when there are a lot of tables. [#38028](https://github.com/ClickHouse/ClickHouse/pull/38028) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of `not` function using dynamic dispatch. [#38058](https://github.com/ClickHouse/ClickHouse/pull/38058) ([Maksim Kita](https://github.com/kitaisreal)). +* Optimized the internal caching of re2 patterns which occur e.g. in LIKE and MATCH functions. [#37544](https://github.com/ClickHouse/ClickHouse/pull/37544) ([Robert Schulze](https://github.com/rschu1ze)). +* Improve filter bitmask generator function all in one with AVX-512 instructions. [#37588](https://github.com/ClickHouse/ClickHouse/pull/37588) ([yaqi-zhao](https://github.com/yaqi-zhao)). +* Apply read method `threadpool` for Hive integration engine. This will significantly speed up reading. [#36328](https://github.com/ClickHouse/ClickHouse/pull/36328) ([李扬](https://github.com/taiyang-li)). +* When all the columns to read are partition keys, construct columns by the file's row number without real reading the Hive file. [#37103](https://github.com/ClickHouse/ClickHouse/pull/37103) ([lgbo](https://github.com/lgbo-ustc)). +* Support multi disks for caching hive files. [#37279](https://github.com/ClickHouse/ClickHouse/pull/37279) ([lgbo](https://github.com/lgbo-ustc)). +* Limiting the maximum cache usage per query can effectively prevent cache pool contamination. [Related Issues](https://github.com/ClickHouse/ClickHouse/issues/28961). [#37859](https://github.com/ClickHouse/ClickHouse/pull/37859) ([Han Shukai](https://github.com/KinderRiven)). +* Currently clickhouse directly downloads all remote files to the local cache (even if they are only read once), which will frequently cause IO of the local hard disk. In some scenarios, these IOs may not be necessary and may easily cause negative optimization. As shown in the figure below, when we run SSB Q1-Q4, the performance of the cache has caused negative optimization. [#37516](https://github.com/ClickHouse/ClickHouse/pull/37516) ([Han Shukai](https://github.com/KinderRiven)). +* Allow to prune the list of files via virtual columns such as `_file` and `_path` when reading from S3. This is for [#37174](https://github.com/ClickHouse/ClickHouse/issues/37174) , [#23494](https://github.com/ClickHouse/ClickHouse/issues/23494). [#37356](https://github.com/ClickHouse/ClickHouse/pull/37356) ([Amos Bird](https://github.com/amosbird)). +* In function: CompressedWriteBuffer::nextImpl(), there is an unnecessary write-copy step that would happen frequently during inserting data. Below shows the differentiation with this patch: - Before: 1. Compress "working_buffer" into "compressed_buffer" 2. write-copy into "out" - After: Directly Compress "working_buffer" into "out". [#37242](https://github.com/ClickHouse/ClickHouse/pull/37242) ([jasperzhu](https://github.com/jinjunzh)). + +#### Improvement +* Support types with non-standard defaults in ROLLUP, CUBE, GROUPING SETS. Closes [#37360](https://github.com/ClickHouse/ClickHouse/issues/37360). [#37667](https://github.com/ClickHouse/ClickHouse/pull/37667) ([Dmitry Novik](https://github.com/novikd)). +* Support non-constant SQL functions (NOT) (I)LIKE and MATCH. [#37251](https://github.com/ClickHouse/ClickHouse/pull/37251) ([Robert Schulze](https://github.com/rschu1ze)). +* Fix stack traces collection on ARM. Closes [#37044](https://github.com/ClickHouse/ClickHouse/issues/37044). Closes [#15638](https://github.com/ClickHouse/ClickHouse/issues/15638). [#37797](https://github.com/ClickHouse/ClickHouse/pull/37797) ([Maksim Kita](https://github.com/kitaisreal)). +* Client will try every IP address returned by DNS resolution until successful connection. [#37273](https://github.com/ClickHouse/ClickHouse/pull/37273) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Allow to use String type instead of Binary in Arrow/Parquet/ORC formats. This PR introduces 3 new settings for it: `output_format_arrow_string_as_string`, `output_format_parquet_string_as_string`, `output_format_orc_string_as_string`. Default value for all settings is `false`. [#37327](https://github.com/ClickHouse/ClickHouse/pull/37327) ([Kruglov Pavel](https://github.com/Avogar)). +* Apply setting `input_format_max_rows_to_read_for_schema_inference` for all read rows in total from all files in globs. Previously setting `input_format_max_rows_to_read_for_schema_inference` was applied for each file in glob separately and in case of huge number of nulls we could read first `input_format_max_rows_to_read_for_schema_inference` rows from each file and get nothing. Also increase default value for this setting to 25000. [#37332](https://github.com/ClickHouse/ClickHouse/pull/37332) ([Kruglov Pavel](https://github.com/Avogar)). +* Add separate `CLUSTER` grant (and `access_control_improvements.on_cluster_queries_require_cluster_grant` configuration directive, for backward compatibility, default to `false`). [#35767](https://github.com/ClickHouse/ClickHouse/pull/35767) ([Azat Khuzhin](https://github.com/azat)). +* Added support for schema inference for `hdfsCluster`. [#35812](https://github.com/ClickHouse/ClickHouse/pull/35812) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)). +* Implement `least_used` load balancing algorithm for disks inside volume (multi disk configuration). [#36686](https://github.com/ClickHouse/ClickHouse/pull/36686) ([Azat Khuzhin](https://github.com/azat)). +* Modify the HTTP Endpoint to return the full stats under the `X-ClickHouse-Summary` header when `send_progress_in_http_headers=0` (before it would return all zeros). - Modify the HTTP Endpoint to return `X-ClickHouse-Exception-Code` header when progress has been sent before (`send_progress_in_http_headers=1`) - Modify the HTTP Endpoint to return `HTTP_REQUEST_TIMEOUT` (408) instead of `HTTP_INTERNAL_SERVER_ERROR` (500) on `TIMEOUT_EXCEEDED` errors. [#36884](https://github.com/ClickHouse/ClickHouse/pull/36884) ([Raúl Marín](https://github.com/Algunenano)). +* Allow a user to inspect grants from granted roles. [#36941](https://github.com/ClickHouse/ClickHouse/pull/36941) ([nvartolomei](https://github.com/nvartolomei)). +* Do not calculate an integral numerically but use CDF functions instead. This will speed up execution and will increase the precision. This fixes [#36714](https://github.com/ClickHouse/ClickHouse/issues/36714). [#36953](https://github.com/ClickHouse/ClickHouse/pull/36953) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)). +* Add default implementation for Nothing in functions. Now most of the functions will return column with type Nothing in case one of it's arguments is Nothing. It also solves problem with functions like arrayMap/arrayFilter and similar when they have empty array as an argument. Previously queries like `select arrayMap(x -> 2 * x, []);` failed because function inside lambda cannot work with type `Nothing`, now such queries return empty array with type `Array(Nothing)`. Also add support for arrays of nullable types in functions like arrayFilter/arrayFill. Previously, queries like `select arrayFilter(x -> x % 2, [1, NULL])` failed, now they work (if the result of lambda is NULL, then this value won't be included in the result). Closes [#37000](https://github.com/ClickHouse/ClickHouse/issues/37000). [#37048](https://github.com/ClickHouse/ClickHouse/pull/37048) ([Kruglov Pavel](https://github.com/Avogar)). +* Now if a shard has local replica we create a local plan and a plan to read from all remote replicas. They have shared initiator which coordinates reading. [#37204](https://github.com/ClickHouse/ClickHouse/pull/37204) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)). +* Do no longer abort server startup if configuration option "mark_cache_size" is not explicitly set. [#37326](https://github.com/ClickHouse/ClickHouse/pull/37326) ([Robert Schulze](https://github.com/rschu1ze)). +* Allows providing `NULL`/`NOT NULL` right after type in column declaration. [#37337](https://github.com/ClickHouse/ClickHouse/pull/37337) ([Igor Nikonov](https://github.com/devcrafter)). +* optimize file segment PARTIALLY_DOWNLOADED get read buffer. [#37338](https://github.com/ClickHouse/ClickHouse/pull/37338) ([xiedeyantu](https://github.com/xiedeyantu)). +* Try to improve short circuit functions processing to fix problems with stress tests. [#37384](https://github.com/ClickHouse/ClickHouse/pull/37384) ([Kruglov Pavel](https://github.com/Avogar)). +* Closes [#37395](https://github.com/ClickHouse/ClickHouse/issues/37395). [#37415](https://github.com/ClickHouse/ClickHouse/pull/37415) ([Memo](https://github.com/Joeywzr)). +* Fix extremely rare deadlock during part fetch in zero-copy replication. Fixes [#37423](https://github.com/ClickHouse/ClickHouse/issues/37423). [#37424](https://github.com/ClickHouse/ClickHouse/pull/37424) ([metahys](https://github.com/metahys)). +* Don't allow to create storage with unknown data format. [#37450](https://github.com/ClickHouse/ClickHouse/pull/37450) ([Kruglov Pavel](https://github.com/Avogar)). +* Set `global_memory_usage_overcommit_max_wait_microseconds` default value to 5 seconds. Add info about `OvercommitTracker` to OOM exception message. Add `MemoryOvercommitWaitTimeMicroseconds` profile event. [#37460](https://github.com/ClickHouse/ClickHouse/pull/37460) ([Dmitry Novik](https://github.com/novikd)). +* Do not display `-0.0` CPU time in clickhouse-client. It can appear due to rounding errors. This closes [#38003](https://github.com/ClickHouse/ClickHouse/issues/38003). This closes [#38038](https://github.com/ClickHouse/ClickHouse/issues/38038). [#38064](https://github.com/ClickHouse/ClickHouse/pull/38064) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Play UI: Keep controls in place when the page is scrolled horizontally. This makes edits comfortable even if the table is wide and it was scrolled far to the right. The feature proposed by Maksym Tereshchenko from CaspianDB. [#37470](https://github.com/ClickHouse/ClickHouse/pull/37470) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Modify query div in play.html to be extendable beyond 20% height. In case of very long queries it is helpful to extend the textarea element, only today, since the div is fixed height, the extended textarea hides the data div underneath. With this fix, extending the textarea element will push the data div down/up such the extended textarea won't hide it. Also, keeps query box width 100% even when the user adjusting the size of the query textarea. [#37488](https://github.com/ClickHouse/ClickHouse/pull/37488) ([guyco87](https://github.com/guyco87)). +* Added `ProfileEvents` for introspection of type of written (inserted or merged) parts (`Inserted{Wide/Compact/InMemory}Parts`, `MergedInto{Wide/Compact/InMemory}Parts`. Added column `part_type` to `system.part_log`. Resolves [#37495](https://github.com/ClickHouse/ClickHouse/issues/37495). [#37536](https://github.com/ClickHouse/ClickHouse/pull/37536) ([Anton Popov](https://github.com/CurtizJ)). +* clickhouse-keeper improvement: move broken logs to a timestamped folder. [#37565](https://github.com/ClickHouse/ClickHouse/pull/37565) ([Antonio Andelic](https://github.com/antonio2368)). +* Do not write expired columns by TTL after subsequent merges (before only first merge/optimize of the part will not write expired by TTL columns, all other will do). [#37570](https://github.com/ClickHouse/ClickHouse/pull/37570) ([Azat Khuzhin](https://github.com/azat)). +* More precise result of the `dumpColumnStructure` miscellaneous function in presence of LowCardinality or Sparse columns. In previous versions, these functions were converting the argument to a full column before returning the result. This is needed to provide an answer in [#6935](https://github.com/ClickHouse/ClickHouse/issues/6935). [#37633](https://github.com/ClickHouse/ClickHouse/pull/37633) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* clickhouse-keeper: store only unique session IDs for watches. [#37641](https://github.com/ClickHouse/ClickHouse/pull/37641) ([Azat Khuzhin](https://github.com/azat)). +* Fix possible "Cannot write to finalized buffer". [#37645](https://github.com/ClickHouse/ClickHouse/pull/37645) ([Azat Khuzhin](https://github.com/azat)). +* Add setting `support_batch_delete` for `DiskS3` to disable multiobject delete calls, which Google Cloud Storage doesn't support. [#37659](https://github.com/ClickHouse/ClickHouse/pull/37659) ([Fred Wulff](https://github.com/frew)). +* Add an option to disable connection pooling in ODBC bridge. [#37705](https://github.com/ClickHouse/ClickHouse/pull/37705) ([Anton Kozlov](https://github.com/tonickkozlov)). +* Functions `dictGetHierarchy`, `dictIsIn`, `dictGetChildren`, `dictGetDescendants` added support nullable `HIERARCHICAL` attribute in dictionaries. Closes [#35521](https://github.com/ClickHouse/ClickHouse/issues/35521). [#37805](https://github.com/ClickHouse/ClickHouse/pull/37805) ([Maksim Kita](https://github.com/kitaisreal)). +* Expose BoringSSL version related info in the `system.build_options` table. [#37850](https://github.com/ClickHouse/ClickHouse/pull/37850) ([Bharat Nallan](https://github.com/bharatnc)). +* Now clickhouse-server removes `delete_tmp` directories on server start. Fixes [#26503](https://github.com/ClickHouse/ClickHouse/issues/26503). [#37906](https://github.com/ClickHouse/ClickHouse/pull/37906) ([alesapin](https://github.com/alesapin)). +* Clean up broken detached parts after timeout. Closes [#25195](https://github.com/ClickHouse/ClickHouse/issues/25195). [#37975](https://github.com/ClickHouse/ClickHouse/pull/37975) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Now in MergeTree table engines family failed-to-move parts will be removed instantly. [#37994](https://github.com/ClickHouse/ClickHouse/pull/37994) ([alesapin](https://github.com/alesapin)). +* Now if setting `always_fetch_merged_part` is enabled for ReplicatedMergeTree merges will try to find parts on other replicas rarely with smaller load for [Zoo]Keeper. [#37995](https://github.com/ClickHouse/ClickHouse/pull/37995) ([alesapin](https://github.com/alesapin)). +* Add implicit grants with grant option too. For example `GRANT CREATE TABLE ON test.* TO A WITH GRANT OPTION` now allows `A` to execute `GRANT CREATE VIEW ON test.* TO B`. [#38017](https://github.com/ClickHouse/ClickHouse/pull/38017) ([Vitaly Baranov](https://github.com/vitlibar)). + +#### Build/Testing/Packaging Improvement +* Use `clang-14` and LLVM infrastructure version 14 for builds. This closes [#34681](https://github.com/ClickHouse/ClickHouse/issues/34681). [#34754](https://github.com/ClickHouse/ClickHouse/pull/34754) ([Alexey Milovidov](https://github.com/alexey-milovidov)). Note: `clang-14` has [a bug](https://github.com/google/sanitizers/issues/1540) in ThreadSanitizer that makes our CI work worse. +* Allow to drop privileges at startup. This simplifies Docker images. Closes [#36293](https://github.com/ClickHouse/ClickHouse/issues/36293). [#36341](https://github.com/ClickHouse/ClickHouse/pull/36341) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Add docs spellcheck to CI. [#37790](https://github.com/ClickHouse/ClickHouse/pull/37790) ([Vladimir C](https://github.com/vdimir)). +* Fix overly aggressive stripping which removed the embedded hash required for checking the consistency of the executable. [#37993](https://github.com/ClickHouse/ClickHouse/pull/37993) ([Robert Schulze](https://github.com/rschu1ze)). + +#### Bug Fix + +* Fix `SELECT ... INTERSECT` and `EXCEPT SELECT` statements with constant string types. [#37738](https://github.com/ClickHouse/ClickHouse/pull/37738) ([Antonio Andelic](https://github.com/antonio2368)). +* Fix `GROUP BY` `AggregateFunction` (i.e. you `GROUP BY` by the column that has `AggregateFunction` type). [#37093](https://github.com/ClickHouse/ClickHouse/pull/37093) ([Azat Khuzhin](https://github.com/azat)). +* (experimental WINDOW VIEW) Fix `addDependency` in WindowView. This bug can be reproduced like [#37237](https://github.com/ClickHouse/ClickHouse/issues/37237). [#37224](https://github.com/ClickHouse/ClickHouse/pull/37224) ([vxider](https://github.com/Vxider)). +* Fix inconsistency in ORDER BY ... WITH FILL feature. Query, containing ORDER BY ... WITH FILL, can generate extra rows when multiple WITH FILL columns are present. [#38074](https://github.com/ClickHouse/ClickHouse/pull/38074) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* This PR moving `addDependency` from constructor to `startup()` to avoid adding dependency to a **dropped** table, fix [#37237](https://github.com/ClickHouse/ClickHouse/issues/37237). [#37243](https://github.com/ClickHouse/ClickHouse/pull/37243) ([vxider](https://github.com/Vxider)). +* Fix inserting defaults for missing values in columnar formats. Previously missing columns were filled with defaults for types, not for columns. [#37253](https://github.com/ClickHouse/ClickHouse/pull/37253) ([Kruglov Pavel](https://github.com/Avogar)). +* (experimental Object type) Fix some cases of insertion nested arrays to columns of type `Object`. [#37305](https://github.com/ClickHouse/ClickHouse/pull/37305) ([Anton Popov](https://github.com/CurtizJ)). +* Fix unexpected errors with a clash of constant strings in aggregate function, prewhere and join. Close [#36891](https://github.com/ClickHouse/ClickHouse/issues/36891). [#37336](https://github.com/ClickHouse/ClickHouse/pull/37336) ([Vladimir C](https://github.com/vdimir)). +* Fix projections with GROUP/ORDER BY in query and optimize_aggregation_in_order (before the result was incorrect since only finish sorting was performed). [#37342](https://github.com/ClickHouse/ClickHouse/pull/37342) ([Azat Khuzhin](https://github.com/azat)). +* Fixed error with symbols in key name in S3. Fixes [#33009](https://github.com/ClickHouse/ClickHouse/issues/33009). [#37344](https://github.com/ClickHouse/ClickHouse/pull/37344) ([Vladimir Chebotarev](https://github.com/excitoon)). +* Throw an exception when GROUPING SETS used with ROLLUP or CUBE. [#37367](https://github.com/ClickHouse/ClickHouse/pull/37367) ([Dmitry Novik](https://github.com/novikd)). +* Fix LOGICAL_ERROR in getMaxSourcePartsSizeForMerge during merges (in case of non standard, greater, values of `background_pool_size`/`background_merges_mutations_concurrency_ratio` has been specified in `config.xml` (new way) not in `users.xml` (deprecated way)). [#37413](https://github.com/ClickHouse/ClickHouse/pull/37413) ([Azat Khuzhin](https://github.com/azat)). +* Stop removing UTF-8 BOM in RowBinary format. [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). ```. [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). +* clickhouse-keeper bugfix: fix force recovery for single node cluster. [#37440](https://github.com/ClickHouse/ClickHouse/pull/37440) ([Antonio Andelic](https://github.com/antonio2368)). +* Fix logical error in normalizeUTF8 functions. Closes [#37298](https://github.com/ClickHouse/ClickHouse/issues/37298). [#37443](https://github.com/ClickHouse/ClickHouse/pull/37443) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix cast lowcard of nullable in JoinSwitcher, close [#37385](https://github.com/ClickHouse/ClickHouse/issues/37385). [#37453](https://github.com/ClickHouse/ClickHouse/pull/37453) ([Vladimir C](https://github.com/vdimir)). +* Fix named tuples output in ORC/Arrow/Parquet formats. [#37458](https://github.com/ClickHouse/ClickHouse/pull/37458) ([Kruglov Pavel](https://github.com/Avogar)). +* Fix optimization of monotonous functions in ORDER BY clause in presence of GROUPING SETS. Fixes [#37401](https://github.com/ClickHouse/ClickHouse/issues/37401). [#37493](https://github.com/ClickHouse/ClickHouse/pull/37493) ([Dmitry Novik](https://github.com/novikd)). +* Fix error on joining with dictionary on some conditions. Close [#37386](https://github.com/ClickHouse/ClickHouse/issues/37386). [#37530](https://github.com/ClickHouse/ClickHouse/pull/37530) ([Vladimir C](https://github.com/vdimir)). +* Prohibit `optimize_aggregation_in_order` with `GROUPING SETS` (fixes `LOGICAL_ERROR`). [#37542](https://github.com/ClickHouse/ClickHouse/pull/37542) ([Azat Khuzhin](https://github.com/azat)). +* Fix wrong dump information of ActionsDAG. [#37587](https://github.com/ClickHouse/ClickHouse/pull/37587) ([zhanglistar](https://github.com/zhanglistar)). +* Fix converting types for UNION queries (may produce LOGICAL_ERROR). [#37593](https://github.com/ClickHouse/ClickHouse/pull/37593) ([Azat Khuzhin](https://github.com/azat)). +* Fix `WITH FILL` modifier with negative intervals in `STEP` clause. Fixes [#37514](https://github.com/ClickHouse/ClickHouse/issues/37514). [#37600](https://github.com/ClickHouse/ClickHouse/pull/37600) ([Anton Popov](https://github.com/CurtizJ)). +* Fix illegal joinGet array usage when ` join_use_nulls = 1`. This fixes [#37562](https://github.com/ClickHouse/ClickHouse/issues/37562) . [#37650](https://github.com/ClickHouse/ClickHouse/pull/37650) ([Amos Bird](https://github.com/amosbird)). +* Fix columns number mismatch in cross join, close [#37561](https://github.com/ClickHouse/ClickHouse/issues/37561). [#37653](https://github.com/ClickHouse/ClickHouse/pull/37653) ([Vladimir C](https://github.com/vdimir)). +* Fix segmentation fault in `show create table` from mysql database when it is configured with named collections. Closes [#37683](https://github.com/ClickHouse/ClickHouse/issues/37683). [#37690](https://github.com/ClickHouse/ClickHouse/pull/37690) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Fix RabbitMQ Storage not being able to startup on server restart if storage was create without SETTINGS clause. Closes [#37463](https://github.com/ClickHouse/ClickHouse/issues/37463). [#37691](https://github.com/ClickHouse/ClickHouse/pull/37691) ([Kseniia Sumarokova](https://github.com/kssenii)). +* SQL user defined functions disable CREATE/DROP in readonly mode. Closes [#37280](https://github.com/ClickHouse/ClickHouse/issues/37280). [#37699](https://github.com/ClickHouse/ClickHouse/pull/37699) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix formatting of Nullable arguments for executable user defined functions. Closes [#35897](https://github.com/ClickHouse/ClickHouse/issues/35897). [#37711](https://github.com/ClickHouse/ClickHouse/pull/37711) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix optimization enabled by setting `optimize_monotonous_functions_in_order_by` in distributed queries. Fixes [#36037](https://github.com/ClickHouse/ClickHouse/issues/36037). [#37724](https://github.com/ClickHouse/ClickHouse/pull/37724) ([Anton Popov](https://github.com/CurtizJ)). +* Fix possible logical error: `Invalid Field get from type UInt64 to type Float64` in `values` table function. Closes [#37602](https://github.com/ClickHouse/ClickHouse/issues/37602). [#37754](https://github.com/ClickHouse/ClickHouse/pull/37754) ([Kruglov Pavel](https://github.com/Avogar)). +* Fix possible segfault in schema inference in case of exception in SchemaReader constructor. Closes [#37680](https://github.com/ClickHouse/ClickHouse/issues/37680). [#37760](https://github.com/ClickHouse/ClickHouse/pull/37760) ([Kruglov Pavel](https://github.com/Avogar)). +* Fix setting cast_ipv4_ipv6_default_on_conversion_error for internal cast function. Closes [#35156](https://github.com/ClickHouse/ClickHouse/issues/35156). [#37761](https://github.com/ClickHouse/ClickHouse/pull/37761) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix toString error on DatatypeDate32. [#37775](https://github.com/ClickHouse/ClickHouse/pull/37775) ([LiuNeng](https://github.com/liuneng1994)). +* The clickhouse-keeper setting `dead_session_check_period_ms` was transformed into microseconds (multiplied by 1000), which lead to dead sessions only being cleaned up after several minutes (instead of 500ms). [#37824](https://github.com/ClickHouse/ClickHouse/pull/37824) ([Michael Lex](https://github.com/mlex)). +* Fix possible "No more packets are available" for distributed queries (in case of `async_socket_for_remote`/`use_hedged_requests` is disabled). [#37826](https://github.com/ClickHouse/ClickHouse/pull/37826) ([Azat Khuzhin](https://github.com/azat)). +* (experimental WINDOW VIEW) Do not drop the inner target table when executing `ALTER TABLE … MODIFY QUERY` in WindowView. [#37879](https://github.com/ClickHouse/ClickHouse/pull/37879) ([vxider](https://github.com/Vxider)). +* Fix directory ownership of coordination dir in clickhouse-keeper Docker image. Fixes [#37914](https://github.com/ClickHouse/ClickHouse/issues/37914). [#37915](https://github.com/ClickHouse/ClickHouse/pull/37915) ([James Maidment](https://github.com/jamesmaidment)). +* Dictionaries fix custom query with update field and `{condition}`. Closes [#33746](https://github.com/ClickHouse/ClickHouse/issues/33746). [#37947](https://github.com/ClickHouse/ClickHouse/pull/37947) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix possible incorrect result of `SELECT ... WITH FILL` in the case when `ORDER BY` should be applied after `WITH FILL` result (e.g. for outer query). Incorrect result was caused by optimization for `ORDER BY` expressions ([#35623](https://github.com/ClickHouse/ClickHouse/issues/35623)). Closes [#37904](https://github.com/ClickHouse/ClickHouse/issues/37904). [#37959](https://github.com/ClickHouse/ClickHouse/pull/37959) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* (experimental WINDOW VIEW) Add missing default columns when pushing to the target table in WindowView, fix [#37815](https://github.com/ClickHouse/ClickHouse/issues/37815). [#37965](https://github.com/ClickHouse/ClickHouse/pull/37965) ([vxider](https://github.com/Vxider)). +* Fixed too large stack frame that would cause compilation to fail. [#37996](https://github.com/ClickHouse/ClickHouse/pull/37996) ([Han Shukai](https://github.com/KinderRiven)). +* When open enable_filesystem_query_cache_limit, throw Reserved cache size exceeds the remaining cache size. [#38004](https://github.com/ClickHouse/ClickHouse/pull/38004) ([xiedeyantu](https://github.com/xiedeyantu)). +* Fix converting types for UNION queries (may produce LOGICAL_ERROR). [#34775](https://github.com/ClickHouse/ClickHouse/pull/34775) ([Azat Khuzhin](https://github.com/azat)). +* TTL merge may not be scheduled again if BackgroundExecutor is busy. --merges_with_ttl_counter is increased in selectPartsToMerge() --merge task will be ignored if BackgroundExecutor is busy --merges_with_ttl_counter will not be decrease. [#36387](https://github.com/ClickHouse/ClickHouse/pull/36387) ([lthaooo](https://github.com/lthaooo)). +* Fix overridden settings value of `normalize_function_names`. [#36937](https://github.com/ClickHouse/ClickHouse/pull/36937) ([李扬](https://github.com/taiyang-li)). +* Fix for exponential time decaying window functions. Now respecting boundaries of the window. [#36944](https://github.com/ClickHouse/ClickHouse/pull/36944) ([Vladimir Chebotarev](https://github.com/excitoon)). +* Fix possible heap-use-after-free error when reading system.projection_parts and system.projection_parts_columns . This fixes [#37184](https://github.com/ClickHouse/ClickHouse/issues/37184). [#37185](https://github.com/ClickHouse/ClickHouse/pull/37185) ([Amos Bird](https://github.com/amosbird)). +* Fixed `DateTime64` fractional seconds behavior prior to Unix epoch. [#37697](https://github.com/ClickHouse/ClickHouse/pull/37697) ([Andrey Zvonov](https://github.com/zvonand)). [#37039](https://github.com/ClickHouse/ClickHouse/pull/37039) ([李扬](https://github.com/taiyang-li)). + + ### ClickHouse release 22.5, 2022-05-19 #### Upgrade Notes From 5107c9dc50837abec6bf67180feed694fc402b53 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 07:01:47 +0200 Subject: [PATCH 130/204] Changelog for 22.6, edits --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7013b791103..52c017d7072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,7 +124,7 @@ * Fix `GROUP BY` `AggregateFunction` (i.e. you `GROUP BY` by the column that has `AggregateFunction` type). [#37093](https://github.com/ClickHouse/ClickHouse/pull/37093) ([Azat Khuzhin](https://github.com/azat)). * (experimental WINDOW VIEW) Fix `addDependency` in WindowView. This bug can be reproduced like [#37237](https://github.com/ClickHouse/ClickHouse/issues/37237). [#37224](https://github.com/ClickHouse/ClickHouse/pull/37224) ([vxider](https://github.com/Vxider)). * Fix inconsistency in ORDER BY ... WITH FILL feature. Query, containing ORDER BY ... WITH FILL, can generate extra rows when multiple WITH FILL columns are present. [#38074](https://github.com/ClickHouse/ClickHouse/pull/38074) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). -* This PR moving `addDependency` from constructor to `startup()` to avoid adding dependency to a **dropped** table, fix [#37237](https://github.com/ClickHouse/ClickHouse/issues/37237). [#37243](https://github.com/ClickHouse/ClickHouse/pull/37243) ([vxider](https://github.com/Vxider)). +* This PR moving `addDependency` from constructor to `startup()` to avoid adding dependency to a *dropped* table, fix [#37237](https://github.com/ClickHouse/ClickHouse/issues/37237). [#37243](https://github.com/ClickHouse/ClickHouse/pull/37243) ([vxider](https://github.com/Vxider)). * Fix inserting defaults for missing values in columnar formats. Previously missing columns were filled with defaults for types, not for columns. [#37253](https://github.com/ClickHouse/ClickHouse/pull/37253) ([Kruglov Pavel](https://github.com/Avogar)). * (experimental Object type) Fix some cases of insertion nested arrays to columns of type `Object`. [#37305](https://github.com/ClickHouse/ClickHouse/pull/37305) ([Anton Popov](https://github.com/CurtizJ)). * Fix unexpected errors with a clash of constant strings in aggregate function, prewhere and join. Close [#36891](https://github.com/ClickHouse/ClickHouse/issues/36891). [#37336](https://github.com/ClickHouse/ClickHouse/pull/37336) ([Vladimir C](https://github.com/vdimir)). @@ -132,7 +132,7 @@ * Fixed error with symbols in key name in S3. Fixes [#33009](https://github.com/ClickHouse/ClickHouse/issues/33009). [#37344](https://github.com/ClickHouse/ClickHouse/pull/37344) ([Vladimir Chebotarev](https://github.com/excitoon)). * Throw an exception when GROUPING SETS used with ROLLUP or CUBE. [#37367](https://github.com/ClickHouse/ClickHouse/pull/37367) ([Dmitry Novik](https://github.com/novikd)). * Fix LOGICAL_ERROR in getMaxSourcePartsSizeForMerge during merges (in case of non standard, greater, values of `background_pool_size`/`background_merges_mutations_concurrency_ratio` has been specified in `config.xml` (new way) not in `users.xml` (deprecated way)). [#37413](https://github.com/ClickHouse/ClickHouse/pull/37413) ([Azat Khuzhin](https://github.com/azat)). -* Stop removing UTF-8 BOM in RowBinary format. [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). ```. [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). +* Stop removing UTF-8 BOM in RowBinary format. [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). * clickhouse-keeper bugfix: fix force recovery for single node cluster. [#37440](https://github.com/ClickHouse/ClickHouse/pull/37440) ([Antonio Andelic](https://github.com/antonio2368)). * Fix logical error in normalizeUTF8 functions. Closes [#37298](https://github.com/ClickHouse/ClickHouse/issues/37298). [#37443](https://github.com/ClickHouse/ClickHouse/pull/37443) ([Maksim Kita](https://github.com/kitaisreal)). * Fix cast lowcard of nullable in JoinSwitcher, close [#37385](https://github.com/ClickHouse/ClickHouse/issues/37385). [#37453](https://github.com/ClickHouse/ClickHouse/pull/37453) ([Vladimir C](https://github.com/vdimir)). From a904dab37278548f39a4d6c283d256271a8298c1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 08:12:42 +0300 Subject: [PATCH 131/204] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52c017d7072..5ee53f95041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * Add new columnar JSON formats: `JSONColumns`, `JSONCompactColumns`, `JSONColumnsWithMetadata`. Closes [#36338](https://github.com/ClickHouse/ClickHouse/issues/36338) Closes [#34509](https://github.com/ClickHouse/ClickHouse/issues/34509). [#36975](https://github.com/ClickHouse/ClickHouse/pull/36975) ([Kruglov Pavel](https://github.com/Avogar)). * Added open telemetry traces visualizing tool based on d3js. [#37810](https://github.com/ClickHouse/ClickHouse/pull/37810) ([Sergei Trifonov](https://github.com/serxa)). * Support INSERTs into `system.zookeeper` table. Closes [#22130](https://github.com/ClickHouse/ClickHouse/issues/22130). [#37596](https://github.com/ClickHouse/ClickHouse/pull/37596) ([Han Fei](https://github.com/hanfei1991)). +* Support non-constant pattern argument for `LIKE`, `ILIKE` and `match` functions. [#37251](https://github.com/ClickHouse/ClickHouse/pull/37251) ([Robert Schulze](https://github.com/rschu1ze)). * Executable user defined functions now support parameters. Example: `SELECT test_function(parameters)(arguments)`. Closes [#37578](https://github.com/ClickHouse/ClickHouse/issues/37578). [#37720](https://github.com/ClickHouse/ClickHouse/pull/37720) ([Maksim Kita](https://github.com/kitaisreal)). * Add `merge_reason` column to system.part_log table. [#36912](https://github.com/ClickHouse/ClickHouse/pull/36912) ([Sema Checherinda](https://github.com/CheSema)). * Add support for Maps and Records in Avro format. Add new setting `input_format_avro_null_as_default ` that allow to insert null as default in Avro format. Closes [#18925](https://github.com/ClickHouse/ClickHouse/issues/18925) Closes [#37378](https://github.com/ClickHouse/ClickHouse/issues/37378) Closes [#32899](https://github.com/ClickHouse/ClickHouse/issues/32899). [#37525](https://github.com/ClickHouse/ClickHouse/pull/37525) ([Kruglov Pavel](https://github.com/Avogar)). @@ -72,7 +73,6 @@ #### Improvement * Support types with non-standard defaults in ROLLUP, CUBE, GROUPING SETS. Closes [#37360](https://github.com/ClickHouse/ClickHouse/issues/37360). [#37667](https://github.com/ClickHouse/ClickHouse/pull/37667) ([Dmitry Novik](https://github.com/novikd)). -* Support non-constant SQL functions (NOT) (I)LIKE and MATCH. [#37251](https://github.com/ClickHouse/ClickHouse/pull/37251) ([Robert Schulze](https://github.com/rschu1ze)). * Fix stack traces collection on ARM. Closes [#37044](https://github.com/ClickHouse/ClickHouse/issues/37044). Closes [#15638](https://github.com/ClickHouse/ClickHouse/issues/15638). [#37797](https://github.com/ClickHouse/ClickHouse/pull/37797) ([Maksim Kita](https://github.com/kitaisreal)). * Client will try every IP address returned by DNS resolution until successful connection. [#37273](https://github.com/ClickHouse/ClickHouse/pull/37273) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). * Allow to use String type instead of Binary in Arrow/Parquet/ORC formats. This PR introduces 3 new settings for it: `output_format_arrow_string_as_string`, `output_format_parquet_string_as_string`, `output_format_orc_string_as_string`. Default value for all settings is `false`. [#37327](https://github.com/ClickHouse/ClickHouse/pull/37327) ([Kruglov Pavel](https://github.com/Avogar)). From ce2b4d78e474f0e0fddf560044e45bfa9461626b Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 07:38:28 +0200 Subject: [PATCH 132/204] Add one more item to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee53f95041..b54ed597374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ * This PR changes the behavior of the `ENGINE` syntax in `WINDOW VIEW`, to make it like in `MATERIALIZED VIEW`. [#37214](https://github.com/ClickHouse/ClickHouse/pull/37214) ([vxider](https://github.com/Vxider)). #### Performance Improvement +* Added numerous optimizations for ARM NEON [#38093](https://github.com/ClickHouse/ClickHouse/pull/38093)([Daniel Kutenin](https://github.com/danlark1)), ([Alexandra Pilipyuk](https://github.com/chalice19)) * Improve performance and memory usage for select of subset of columns for formats Native, Protobuf, CapnProto, JSONEachRow, TSKV, all formats with suffixes WithNames/WithNamesAndTypes. Previously while selecting only subset of columns from files in these formats all columns were read and stored in memory. Now only required columns are read. This PR enables setting `input_format_skip_unknown_fields` by default, because otherwise in case of select of subset of columns exception will be thrown. [#37192](https://github.com/ClickHouse/ClickHouse/pull/37192) ([Kruglov Pavel](https://github.com/Avogar)). * Now more filters can be pushed down for join. [#37472](https://github.com/ClickHouse/ClickHouse/pull/37472) ([Amos Bird](https://github.com/amosbird)). * Load marks for only necessary columns when reading wide parts. [#36879](https://github.com/ClickHouse/ClickHouse/pull/36879) ([Anton Kozlov](https://github.com/tonickkozlov)). From 11b31d1b10ed9f985724a18fda2553a69153716e Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 08:12:30 +0200 Subject: [PATCH 133/204] Add a comment --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b54ed597374..efc90109af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ * This PR changes the behavior of the `ENGINE` syntax in `WINDOW VIEW`, to make it like in `MATERIALIZED VIEW`. [#37214](https://github.com/ClickHouse/ClickHouse/pull/37214) ([vxider](https://github.com/Vxider)). #### Performance Improvement -* Added numerous optimizations for ARM NEON [#38093](https://github.com/ClickHouse/ClickHouse/pull/38093)([Daniel Kutenin](https://github.com/danlark1)), ([Alexandra Pilipyuk](https://github.com/chalice19)) +* Added numerous optimizations for ARM NEON [#38093](https://github.com/ClickHouse/ClickHouse/pull/38093)([Daniel Kutenin](https://github.com/danlark1)), ([Alexandra Pilipyuk](https://github.com/chalice19)) Note: if you run different ClickHouse versions on a cluster with ARM CPU and use distributed queries with GROUP BY multiple keys of fixed-size type that fit in 256 bits but don't fit in 64 bits, the result of the aggregation query will be wrong during upgrade. Workaround: upgrade with downtime instead of a rolling upgrade. * Improve performance and memory usage for select of subset of columns for formats Native, Protobuf, CapnProto, JSONEachRow, TSKV, all formats with suffixes WithNames/WithNamesAndTypes. Previously while selecting only subset of columns from files in these formats all columns were read and stored in memory. Now only required columns are read. This PR enables setting `input_format_skip_unknown_fields` by default, because otherwise in case of select of subset of columns exception will be thrown. [#37192](https://github.com/ClickHouse/ClickHouse/pull/37192) ([Kruglov Pavel](https://github.com/Avogar)). * Now more filters can be pushed down for join. [#37472](https://github.com/ClickHouse/ClickHouse/pull/37472) ([Amos Bird](https://github.com/amosbird)). * Load marks for only necessary columns when reading wide parts. [#36879](https://github.com/ClickHouse/ClickHouse/pull/36879) ([Anton Kozlov](https://github.com/tonickkozlov)). From bbdbfd9f0154677145ed971c7eb7223a0fb7463f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 08:13:49 +0200 Subject: [PATCH 134/204] Add a comment --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efc90109af9..efe25592a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * Changed format of binary serialization of columns of experimental type `Object`. New format is more convenient to implement by third-party clients. [#37482](https://github.com/ClickHouse/ClickHouse/pull/37482) ([Anton Popov](https://github.com/CurtizJ)). * Turn on setting `output_format_json_named_tuples_as_objects` by default. It allows to serialize named tuples as JSON objects in JSON formats. [#37756](https://github.com/ClickHouse/ClickHouse/pull/37756) ([Anton Popov](https://github.com/CurtizJ)). * LIKE patterns with trailing escape symbol ('\\') are now disallowed (as mandated by the SQL standard). [#37764](https://github.com/ClickHouse/ClickHouse/pull/37764) ([Robert Schulze](https://github.com/rschu1ze)). +* If you run different ClickHouse versions on a cluster with AArch64 CPU or mix AArch64 and amd64 on a cluster, and use distributed queries with GROUP BY multiple keys of fixed-size type that fit in 256 bits but don't fit in 64 bits, and the size of the result is huge, the data will not be fully aggregated in the result of these queries during upgrade. Workaround: upgrade with downtime instead of a rolling upgrade. #### New Feature * A new codec [FPC](https://userweb.cs.txstate.edu/~burtscher/papers/dcc07a.pdf) algorithm for floating point data compression. [#37553](https://github.com/ClickHouse/ClickHouse/pull/37553) ([Mikhail Guzov](https://github.com/koloshmet)). From c1732e865f77069a4ad07ca3f2a1284c995c8f73 Mon Sep 17 00:00:00 2001 From: lirulei Date: Thu, 16 Jun 2022 14:42:51 +0800 Subject: [PATCH 135/204] Update postgresql.md fix doc --- docs/en/engines/database-engines/postgresql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/engines/database-engines/postgresql.md b/docs/en/engines/database-engines/postgresql.md index 843c10c7a48..07181cc23e8 100644 --- a/docs/en/engines/database-engines/postgresql.md +++ b/docs/en/engines/database-engines/postgresql.md @@ -52,7 +52,7 @@ Database in ClickHouse, exchanging data with the PostgreSQL server: ``` sql CREATE DATABASE test_database -ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 1); +ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 'schema_name',1); ``` ``` sql From 518a726c1da9455f3d7c9501e8afa255f14699d4 Mon Sep 17 00:00:00 2001 From: "Yu, Peng" Date: Thu, 16 Jun 2022 15:54:30 +0800 Subject: [PATCH 136/204] Fix redundant memory reservation for output block in MergeSorter --- src/Processors/Transforms/SortingTransform.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Processors/Transforms/SortingTransform.cpp b/src/Processors/Transforms/SortingTransform.cpp index 779e4e6047d..2360b4b6f6a 100644 --- a/src/Processors/Transforms/SortingTransform.cpp +++ b/src/Processors/Transforms/SortingTransform.cpp @@ -82,8 +82,9 @@ Chunk MergeSorter::mergeImpl(TSortingHeap & queue) /// Reserve if (queue.isValid()) { - /// The expected size of output block is the same as input block - size_t size_to_reserve = chunks[0].getNumRows(); + /// The size of output block will not be larger than the `max_merged_block_size`. + /// If redundant memory space is reserved, `MemoryTracker` will count more memory usage than actual usage. + size_t size_to_reserve = std::min(chunks[0].getNumRows(), max_merged_block_size); for (auto & column : merged_columns) column->reserve(size_to_reserve); } From 01fd592dda3a5096b113fd682bb6b0f9abc40098 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 10:55:17 +0300 Subject: [PATCH 137/204] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efe25592a03..317bb766336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ #### New Feature * A new codec [FPC](https://userweb.cs.txstate.edu/~burtscher/papers/dcc07a.pdf) algorithm for floating point data compression. [#37553](https://github.com/ClickHouse/ClickHouse/pull/37553) ([Mikhail Guzov](https://github.com/koloshmet)). -* Add `GROUPING` function. Closes [#19426](https://github.com/ClickHouse/ClickHouse/issues/19426). [#37163](https://github.com/ClickHouse/ClickHouse/pull/37163) ([Dmitry Novik](https://github.com/novikd)). * Add new columnar JSON formats: `JSONColumns`, `JSONCompactColumns`, `JSONColumnsWithMetadata`. Closes [#36338](https://github.com/ClickHouse/ClickHouse/issues/36338) Closes [#34509](https://github.com/ClickHouse/ClickHouse/issues/34509). [#36975](https://github.com/ClickHouse/ClickHouse/pull/36975) ([Kruglov Pavel](https://github.com/Avogar)). * Added open telemetry traces visualizing tool based on d3js. [#37810](https://github.com/ClickHouse/ClickHouse/pull/37810) ([Sergei Trifonov](https://github.com/serxa)). * Support INSERTs into `system.zookeeper` table. Closes [#22130](https://github.com/ClickHouse/ClickHouse/issues/22130). [#37596](https://github.com/ClickHouse/ClickHouse/pull/37596) ([Han Fei](https://github.com/hanfei1991)). @@ -39,6 +38,7 @@ * Added `SYSTEM UNFREEZE` query that deletes the whole backup regardless if the corresponding table is deleted or not. [#36424](https://github.com/ClickHouse/ClickHouse/pull/36424) ([Vadim Volodin](https://github.com/PolyProgrammist)). #### Experimental Feature +* Add `GROUPING` function. Closes [#19426](https://github.com/ClickHouse/ClickHouse/issues/19426). [#37163](https://github.com/ClickHouse/ClickHouse/pull/37163) ([Dmitry Novik](https://github.com/novikd)). The behavior of this function will be changed in subsequent releases. * Enables `POPULATE` for `WINDOW VIEW`. [#36945](https://github.com/ClickHouse/ClickHouse/pull/36945) ([vxider](https://github.com/Vxider)). * `ALTER TABLE ... MODIFY QUERY` support for `WINDOW VIEW`. [#37188](https://github.com/ClickHouse/ClickHouse/pull/37188) ([vxider](https://github.com/Vxider)). * This PR changes the behavior of the `ENGINE` syntax in `WINDOW VIEW`, to make it like in `MATERIALIZED VIEW`. [#37214](https://github.com/ClickHouse/ClickHouse/pull/37214) ([vxider](https://github.com/Vxider)). From 3b29db6e9f6ae1f34a51c67f65d9693a00baf210 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 1 Jun 2022 07:26:18 +0300 Subject: [PATCH 138/204] LocalServer: remove superfluous ProgressIndicator.h Signed-off-by: Azat Khuzhin --- programs/local/LocalServer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/local/LocalServer.h b/programs/local/LocalServer.h index 5defd01663a..ca0ce513b09 100644 --- a/programs/local/LocalServer.h +++ b/programs/local/LocalServer.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include From 4baa7690ae556926a288dc098f285716cf674fc8 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 20 May 2022 18:19:40 +0300 Subject: [PATCH 139/204] Send profile events for INSERT queries (previously only SELECT was supported) Reproducer: echo "1" | clickhouse-client --query="insert into function null('foo String') format TSV" --print-profile-events --profile-events-delay-ms=-1 However, clickhouse-local is differnt, it does sent the periodically, but only if query was long enough, i.e.: # yes | head -n100000 | clickhouse-local --query="insert into function null('foo String') format TSV" --print-profile-events --profile-events-delay-ms=-1 [s1.ch] 2022.05.20 15:20:27 [ 0 ] ContextLock: 10 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] DiskReadElapsedMicroseconds: 29 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] IOBufferAllocBytes: 200000 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] IOBufferAllocs: 1 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] InsertQuery: 1 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] InsertedBytes: 1000000 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] InsertedRows: 100000 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] MemoryTrackerUsage: 1521975 (gauge) [s1.ch] 2022.05.20 15:20:27 [ 0 ] OSCPUVirtualTimeMicroseconds: 102148 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] OSReadChars: 135700 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] OSWriteChars: 8 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] Query: 1 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] RWLockAcquiredReadLocks: 2 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] ReadBufferFromFileDescriptorRead: 5 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] ReadBufferFromFileDescriptorReadBytes: 134464 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] RealTimeMicroseconds: 293747 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] SoftPageFaults: 382 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] TableFunctionExecute: 1 (increment) [s1.ch] 2022.05.20 15:20:27 [ 0 ] UserTimeMicroseconds: 102148 (increment) v2: Proper support ProfileEvents in INSERTs (with protocol change) v3: Receive profile events on INSERT queries Signed-off-by: Azat Khuzhin --- src/Client/ClientBase.cpp | 6 ++-- src/Client/ClientBase.h | 2 +- src/Client/LocalConnection.cpp | 30 +++++++++---------- src/Client/LocalConnection.h | 2 +- src/Core/ProtocolDefines.h | 4 ++- src/QueryPipeline/RemoteInserter.cpp | 8 +++++ src/Server/TCPHandler.cpp | 29 ++++++++++++++---- src/Server/TCPHandler.h | 2 ++ .../02310_profile_events_insert.reference | 4 +++ .../02310_profile_events_insert.sh | 13 ++++++++ 10 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 tests/queries/0_stateless/02310_profile_events_insert.reference create mode 100755 tests/queries/0_stateless/02310_profile_events_insert.sh diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 733c2d6b4df..72144c99f3c 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -1275,7 +1275,7 @@ try } /// Check if server send Log packet - receiveLogs(parsed_query); + receiveLogsAndProfileEvents(parsed_query); /// Check if server send Exception packet auto packet_type = connection->checkPacket(0); @@ -1328,11 +1328,11 @@ void ClientBase::sendDataFromStdin(Block & sample, const ColumnsDescription & co /// Process Log packets, used when inserting data by blocks -void ClientBase::receiveLogs(ASTPtr parsed_query) +void ClientBase::receiveLogsAndProfileEvents(ASTPtr parsed_query) { auto packet_type = connection->checkPacket(0); - while (packet_type && *packet_type == Protocol::Server::Log) + while (packet_type && (*packet_type == Protocol::Server::Log || *packet_type == Protocol::Server::ProfileEvents)) { receiveAndProcessPacket(parsed_query, false); packet_type = connection->checkPacket(0); diff --git a/src/Client/ClientBase.h b/src/Client/ClientBase.h index d11977e984a..d34fe282839 100644 --- a/src/Client/ClientBase.h +++ b/src/Client/ClientBase.h @@ -117,7 +117,7 @@ protected: private: void receiveResult(ASTPtr parsed_query); bool receiveAndProcessPacket(ASTPtr parsed_query, bool cancelled_); - void receiveLogs(ASTPtr parsed_query); + void receiveLogsAndProfileEvents(ASTPtr parsed_query); bool receiveSampleBlock(Block & out, ColumnsDescription & columns_description, ASTPtr parsed_query); bool receiveEndOfQuery(); void cancelQuery(); diff --git a/src/Client/LocalConnection.cpp b/src/Client/LocalConnection.cpp index 0707b0bcdc0..425e54fb392 100644 --- a/src/Client/LocalConnection.cpp +++ b/src/Client/LocalConnection.cpp @@ -18,6 +18,7 @@ namespace ErrorCodes extern const int UNKNOWN_PACKET_FROM_SERVER; extern const int UNKNOWN_EXCEPTION; extern const int NOT_IMPLEMENTED; + extern const int LOGICAL_ERROR; } LocalConnection::LocalConnection(ContextPtr context_, bool send_progress_, bool send_profile_events_, const String & server_display_name_) @@ -62,9 +63,13 @@ void LocalConnection::updateProgress(const Progress & value) state->progress.incrementPiecewiseAtomically(value); } -void LocalConnection::getProfileEvents(Block & block) +void LocalConnection::sendProfileEvents() { - ProfileEvents::getProfileEvents(server_display_name, state->profile_queue, block, last_sent_snapshots); + Block profile_block; + state->after_send_profile_events.restart(); + next_packet_type = Protocol::Server::ProfileEvents; + ProfileEvents::getProfileEvents(server_display_name, state->profile_queue, profile_block, last_sent_snapshots); + state->block.emplace(std::move(profile_block)); } void LocalConnection::sendQuery( @@ -192,13 +197,14 @@ void LocalConnection::sendData(const Block & block, const String &, bool) return; if (state->pushing_async_executor) - { state->pushing_async_executor->push(block); - } else if (state->pushing_executor) - { state->pushing_executor->push(block); - } + else + throw Exception("Unknown executor", ErrorCodes::LOGICAL_ERROR); + + if (send_profile_events) + sendProfileEvents(); } void LocalConnection::sendCancel() @@ -264,11 +270,7 @@ bool LocalConnection::poll(size_t) if (send_profile_events && (state->after_send_profile_events.elapsedMicroseconds() >= query_context->getSettingsRef().interactive_delay)) { - Block block; - state->after_send_profile_events.restart(); - next_packet_type = Protocol::Server::ProfileEvents; - getProfileEvents(block); - state->block.emplace(std::move(block)); + sendProfileEvents(); return true; } @@ -349,11 +351,7 @@ bool LocalConnection::poll(size_t) if (send_profile_events && state->executor) { - Block block; - state->after_send_profile_events.restart(); - next_packet_type = Protocol::Server::ProfileEvents; - getProfileEvents(block); - state->block.emplace(std::move(block)); + sendProfileEvents(); return true; } } diff --git a/src/Client/LocalConnection.h b/src/Client/LocalConnection.h index 1ad6ad73238..1ebe4a1d901 100644 --- a/src/Client/LocalConnection.h +++ b/src/Client/LocalConnection.h @@ -142,7 +142,7 @@ private: void updateProgress(const Progress & value); - void getProfileEvents(Block & block); + void sendProfileEvents(); bool pollImpl(); diff --git a/src/Core/ProtocolDefines.h b/src/Core/ProtocolDefines.h index 6ee491f3ab5..2df48a79776 100644 --- a/src/Core/ProtocolDefines.h +++ b/src/Core/ProtocolDefines.h @@ -52,6 +52,8 @@ /// NOTE: DBMS_TCP_PROTOCOL_VERSION has nothing common with VERSION_REVISION, /// later is just a number for server version (one number instead of commit SHA) /// for simplicity (sometimes it may be more convenient in some use cases). -#define DBMS_TCP_PROTOCOL_VERSION 54455 +#define DBMS_TCP_PROTOCOL_VERSION 54456 #define DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME 54449 + +#define DBMS_MIN_PROTOCOL_VERSION_WITH_PROFILE_EVENTS_IN_INSERT 54456 diff --git a/src/QueryPipeline/RemoteInserter.cpp b/src/QueryPipeline/RemoteInserter.cpp index d5cef72b020..1e1c4537d2f 100644 --- a/src/QueryPipeline/RemoteInserter.cpp +++ b/src/QueryPipeline/RemoteInserter.cpp @@ -72,6 +72,10 @@ RemoteInserter::RemoteInserter( if (auto log_queue = CurrentThread::getInternalTextLogsQueue()) log_queue->pushBlock(std::move(packet.block)); } + else if (Protocol::Server::ProfileEvents == packet.type) + { + // Do nothing + } else if (Protocol::Server::TableColumns == packet.type) { /// Server could attach ColumnsDescription in front of stream for column defaults. There's no need to pass it through cause @@ -132,6 +136,10 @@ void RemoteInserter::onFinish() { // Do nothing } + else if (Protocol::Server::ProfileEvents == packet.type) + { + // Do nothing + } else throw NetException( ErrorCodes::UNEXPECTED_PACKET_FROM_SERVER, diff --git a/src/Server/TCPHandler.cpp b/src/Server/TCPHandler.cpp index cc51901ac40..eff91ae2302 100644 --- a/src/Server/TCPHandler.cpp +++ b/src/Server/TCPHandler.cpp @@ -357,7 +357,7 @@ void TCPHandler::runImpl() return true; sendProgress(); - sendProfileEvents(); + sendSelectProfileEvents(); sendLogs(); return false; @@ -586,7 +586,10 @@ bool TCPHandler::readDataNext() } if (read_ok) + { sendLogs(); + sendInsertProfileEvents(); + } else state.read_all_data = true; @@ -659,6 +662,8 @@ void TCPHandler::processInsertQuery() PushingPipelineExecutor executor(state.io.pipeline); run_executor(executor); } + + sendInsertProfileEvents(); } @@ -701,7 +706,7 @@ void TCPHandler::processOrdinaryQueryWithProcessors() /// Some time passed and there is a progress. after_send_progress.restart(); sendProgress(); - sendProfileEvents(); + sendSelectProfileEvents(); } sendLogs(); @@ -727,7 +732,7 @@ void TCPHandler::processOrdinaryQueryWithProcessors() sendProfileInfo(executor.getProfileInfo()); sendProgress(); sendLogs(); - sendProfileEvents(); + sendSelectProfileEvents(); } if (state.is_connection_closed) @@ -861,9 +866,6 @@ void TCPHandler::sendExtremes(const Block & extremes) void TCPHandler::sendProfileEvents() { - if (client_tcp_protocol_version < DBMS_MIN_PROTOCOL_VERSION_WITH_INCREMENTAL_PROFILE_EVENTS) - return; - Block block; ProfileEvents::getProfileEvents(server_display_name, state.profile_queue, block, last_sent_snapshots); if (block.rows() != 0) @@ -878,6 +880,21 @@ void TCPHandler::sendProfileEvents() } } +void TCPHandler::sendSelectProfileEvents() +{ + if (client_tcp_protocol_version < DBMS_MIN_PROTOCOL_VERSION_WITH_INCREMENTAL_PROFILE_EVENTS) + return; + + sendProfileEvents(); +} + +void TCPHandler::sendInsertProfileEvents() +{ + if (client_tcp_protocol_version < DBMS_MIN_PROTOCOL_VERSION_WITH_PROFILE_EVENTS_IN_INSERT) + return; + + sendProfileEvents(); +} bool TCPHandler::receiveProxyHeader() { diff --git a/src/Server/TCPHandler.h b/src/Server/TCPHandler.h index 4f2516e7923..a873f9ba75c 100644 --- a/src/Server/TCPHandler.h +++ b/src/Server/TCPHandler.h @@ -251,6 +251,8 @@ private: void sendTotals(const Block & totals); void sendExtremes(const Block & extremes); void sendProfileEvents(); + void sendSelectProfileEvents(); + void sendInsertProfileEvents(); /// Creates state.block_in/block_out for blocks read/write, depending on whether compression is enabled. void initBlockInput(); diff --git a/tests/queries/0_stateless/02310_profile_events_insert.reference b/tests/queries/0_stateless/02310_profile_events_insert.reference new file mode 100644 index 00000000000..7308b2da5b1 --- /dev/null +++ b/tests/queries/0_stateless/02310_profile_events_insert.reference @@ -0,0 +1,4 @@ +client +InsertedRows: 1 (increment) +local +InsertedRows: 1 (increment) diff --git a/tests/queries/0_stateless/02310_profile_events_insert.sh b/tests/queries/0_stateless/02310_profile_events_insert.sh new file mode 100755 index 00000000000..e51297ea7c9 --- /dev/null +++ b/tests/queries/0_stateless/02310_profile_events_insert.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +echo client +$CLICKHOUSE_CLIENT --print-profile-events --profile-events-delay-ms=-1 -q "insert into function null('foo Int') values (1)" |& grep -o 'InsertedRows: .*' + +echo local +$CLICKHOUSE_LOCAL --print-profile-events --profile-events-delay-ms=-1 -q "insert into function null('foo Int') values (1)" |& grep -o 'InsertedRows: .*' + +exit 0 From b3bf7589efae9aa54a2e46ce78991e026a8935e1 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Tue, 31 May 2022 11:05:35 +0300 Subject: [PATCH 140/204] Fix possible concurrent access in ProgressIndication In case of all of the above: - clickhouse-local - input_format_parallel_parsing=true - write_progress_on_update=true It is possible concurrent access to the following: - writeProgress() (class properties) (guarded with progress_mutex) - thread_data/host_cpu_usage (guarded with profile_events_mutex) v2: decrease number of rows for INSERT ProfileEvents test (10 times) CI: https://s3.amazonaws.com/clickhouse-test-reports/37391/4bd5c335182279dcc5020aa081b13c3044135951/stateless_tests__debug__actions__[1/3].html v3: decrease number of rows for INSERT ProfileEvents test (10 times) and add a comment CI: https://s3.amazonaws.com/clickhouse-test-reports/37391/026d7f732cb166c90d6c287b02824b6c7fdebf0c/stateless_tests_flaky_check__address__actions_/runlog.log Signed-off-by: Azat Khuzhin f --- src/Common/ProgressIndication.cpp | 19 +++++++++++++++++-- src/Common/ProgressIndication.h | 11 +++++++++++ ...t_INSERT_progress_profile_events.reference | 2 ++ ...e_client_INSERT_progress_profile_events.sh | 19 +++++++++++++++++++ ...l_INSERT_progress_profile_events.reference | 2 ++ ...se_local_INSERT_progress_profile_events.sh | 19 +++++++++++++++++++ 6 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.reference create mode 100755 tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.sh create mode 100644 tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.reference create mode 100755 tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.sh diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index 315080115a6..7bea00f5b1e 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -53,8 +53,11 @@ void ProgressIndication::resetProgress() show_progress_bar = false; written_progress_chars = 0; write_progress_on_update = false; - host_cpu_usage.clear(); - thread_data.clear(); + { + std::lock_guard lock(profile_events_mutex); + host_cpu_usage.clear(); + thread_data.clear(); + } } void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, bool write_progress_on_update_) @@ -71,6 +74,8 @@ void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, bool void ProgressIndication::addThreadIdToList(String const & host, UInt64 thread_id) { + std::lock_guard lock(profile_events_mutex); + auto & thread_to_times = thread_data[host]; if (thread_to_times.contains(thread_id)) return; @@ -79,6 +84,8 @@ void ProgressIndication::addThreadIdToList(String const & host, UInt64 thread_id void ProgressIndication::updateThreadEventData(HostToThreadTimesMap & new_thread_data, UInt64 elapsed_time) { + std::lock_guard lock(profile_events_mutex); + for (auto & new_host_map : new_thread_data) { host_cpu_usage[new_host_map.first] = calculateCPUUsage(new_host_map.second, elapsed_time); @@ -88,6 +95,8 @@ void ProgressIndication::updateThreadEventData(HostToThreadTimesMap & new_thread size_t ProgressIndication::getUsedThreadsCount() const { + std::lock_guard lock(profile_events_mutex); + return std::accumulate(thread_data.cbegin(), thread_data.cend(), 0, [] (size_t acc, auto const & threads) { @@ -97,6 +106,8 @@ size_t ProgressIndication::getUsedThreadsCount() const double ProgressIndication::getCPUUsage() const { + std::lock_guard lock(profile_events_mutex); + double res = 0; for (const auto & elem : host_cpu_usage) res += elem.second; @@ -105,6 +116,8 @@ double ProgressIndication::getCPUUsage() const ProgressIndication::MemoryUsage ProgressIndication::getMemoryUsage() const { + std::lock_guard lock(profile_events_mutex); + return std::accumulate(thread_data.cbegin(), thread_data.cend(), MemoryUsage{}, [](MemoryUsage const & acc, auto const & host_data) { @@ -137,6 +150,8 @@ void ProgressIndication::writeFinalProgress() void ProgressIndication::writeProgress() { + std::lock_guard lock(progress_mutex); + /// Output all progress bar commands to stderr at once to avoid flicker. WriteBufferFromFileDescriptor message(STDERR_FILENO, 1024); diff --git a/src/Common/ProgressIndication.h b/src/Common/ProgressIndication.h index d44becc416a..9ce29ef0d3c 100644 --- a/src/Common/ProgressIndication.h +++ b/src/Common/ProgressIndication.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -92,6 +93,16 @@ private: std::unordered_map host_cpu_usage; HostToThreadTimesMap thread_data; + /// In case of all of the above: + /// - clickhouse-local + /// - input_format_parallel_parsing=true + /// - write_progress_on_update=true + /// + /// It is possible concurrent access to the following: + /// - writeProgress() (class properties) (guarded with progress_mutex) + /// - thread_data/host_cpu_usage (guarded with profile_events_mutex) + mutable std::mutex profile_events_mutex; + mutable std::mutex progress_mutex; }; } diff --git a/tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.reference b/tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.reference new file mode 100644 index 00000000000..64ab61e6765 --- /dev/null +++ b/tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.reference @@ -0,0 +1,2 @@ +0 +--progress produce some rows diff --git a/tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.sh b/tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.sh new file mode 100755 index 00000000000..6c37d870652 --- /dev/null +++ b/tests/queries/0_stateless/02310_clickhouse_client_INSERT_progress_profile_events.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Tags: long + +# This is the regression for the concurrent access in ProgressIndication, +# so it is important to read enough rows here (10e6). +# +# Initially there was 100e6, but under thread fuzzer 10min may be not enough sometimes, +# but I believe that CI will catch possible issues even with less rows anyway. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +tmp_file_progress="$(mktemp "$CUR_DIR/$CLICKHOUSE_TEST_UNIQUE_NAME.XXXXXX.progress")" +trap 'rm $tmp_file_progress' EXIT + +yes | head -n10000000 | $CLICKHOUSE_CLIENT -q "insert into function null('foo String') format TSV" --progress 2> "$tmp_file_progress" +echo $? +test -s "$tmp_file_progress" && echo "--progress produce some rows" || echo "FAIL: no rows with --progress" diff --git a/tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.reference b/tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.reference new file mode 100644 index 00000000000..64ab61e6765 --- /dev/null +++ b/tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.reference @@ -0,0 +1,2 @@ +0 +--progress produce some rows diff --git a/tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.sh b/tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.sh new file mode 100755 index 00000000000..00a8b7a2a90 --- /dev/null +++ b/tests/queries/0_stateless/02310_clickhouse_local_INSERT_progress_profile_events.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Tags: long + +# This is the regression for the concurrent access in ProgressIndication, +# so it is important to read enough rows here (10e6). +# +# Initially there was 100e6, but under thread fuzzer 10min may be not enough sometimes, +# but I believe that CI will catch possible issues even with less rows anyway. + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +tmp_file_progress="$(mktemp "$CUR_DIR/$CLICKHOUSE_TEST_UNIQUE_NAME.XXXXXX.progress")" +trap 'rm $tmp_file_progress' EXIT + +yes | head -n10000000 | $CLICKHOUSE_LOCAL -q "insert into function null('foo String') format TSV" --progress 2> "$tmp_file_progress" +echo $? +test -s "$tmp_file_progress" && echo "--progress produce some rows" || echo "FAIL: no rows with --progress" From 20f0602c20729eb3ac0ae3c0dfb2499399e19b2f Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Tue, 7 Jun 2022 09:28:33 +0300 Subject: [PATCH 141/204] tests/integration: add ability to query specific host Signed-off-by: Azat Khuzhin --- tests/integration/helpers/client.py | 8 +++++--- tests/integration/helpers/cluster.py | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/integration/helpers/client.py b/tests/integration/helpers/client.py index 41c5608081d..f03a6d2ab23 100644 --- a/tests/integration/helpers/client.py +++ b/tests/integration/helpers/client.py @@ -25,6 +25,7 @@ class Client: user=None, password=None, database=None, + host=None, ignore_error=False, query_id=None, ): @@ -36,6 +37,7 @@ class Client: user=user, password=password, database=database, + host=host, ignore_error=ignore_error, query_id=query_id, ).get_answer() @@ -49,6 +51,7 @@ class Client: user=None, password=None, database=None, + host=None, ignore_error=False, query_id=None, ): @@ -66,13 +69,12 @@ class Client: if user is not None: command += ["--user", user] - if password is not None: command += ["--password", password] - if database is not None: command += ["--database", database] - + if host is not None: + command += ["--host", host] if query_id is not None: command += ["--query_id", query_id] diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index f8ad9213e5b..3cf0641a2aa 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -2876,6 +2876,7 @@ class ClickHouseInstance: user=None, password=None, database=None, + host=None, ignore_error=False, query_id=None, ): @@ -2890,6 +2891,7 @@ class ClickHouseInstance: database=database, ignore_error=ignore_error, query_id=query_id, + host=host, ) def query_with_retry( @@ -2901,6 +2903,7 @@ class ClickHouseInstance: user=None, password=None, database=None, + host=None, ignore_error=False, retry_count=20, sleep_time=0.5, @@ -2918,6 +2921,7 @@ class ClickHouseInstance: user=user, password=password, database=database, + host=host, ignore_error=ignore_error, ) if check_callback(result): From 68b4f3fbb36d31c0aaa064e1d737939b7393f9e1 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Tue, 7 Jun 2022 09:18:54 +0300 Subject: [PATCH 142/204] Add backward compatiblity test for INSERT w/ and w/o ProfileEvents v2: apply black formatting (sigh) Signed-off-by: Azat Khuzhin --- .../test_insert_profile_events.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/integration/test_backward_compatibility/test_insert_profile_events.py diff --git a/tests/integration/test_backward_compatibility/test_insert_profile_events.py b/tests/integration/test_backward_compatibility/test_insert_profile_events.py new file mode 100644 index 00000000000..8047c088e4c --- /dev/null +++ b/tests/integration/test_backward_compatibility/test_insert_profile_events.py @@ -0,0 +1,42 @@ +# pylint: disable=line-too-long +# pylint: disable=unused-argument +# pylint: disable=redefined-outer-name + +import pytest + +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__, name="insert_profile_events") +upstream_node = cluster.add_instance("upstream_node") +old_node = cluster.add_instance( + "old_node", + image="clickhouse/clickhouse-server", + tag="22.5.1.2079", + with_installed_binary=True, +) + + +@pytest.fixture(scope="module") +def start_cluster(): + try: + cluster.start() + yield cluster + + finally: + cluster.shutdown() + + +def test_old_client_compatible(start_cluster): + old_node.query("INSERT INTO FUNCTION null('foo String') VALUES ('foo')('bar')") + old_node.query( + "INSERT INTO FUNCTION null('foo String') VALUES ('foo')('bar')", + host=upstream_node.ip_address, + ) + + +def test_new_client_compatible(start_cluster): + upstream_node.query( + "INSERT INTO FUNCTION null('foo String') VALUES ('foo')('bar')", + host=old_node.ip_address, + ) + upstream_node.query("INSERT INTO FUNCTION null('foo String') VALUES ('foo')('bar')") From cf6f865f8000f780a86e9feb8de46dc8b6e29274 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 12:35:13 +0300 Subject: [PATCH 143/204] Revert "Revert "add d3js based trace visualizer as gantt chart"" --- utils/trace-visualizer/README.md | 36 ++ utils/trace-visualizer/css/bootstrap.min.css | 9 + utils/trace-visualizer/css/d3-gantt.css | 78 +++ utils/trace-visualizer/index.html | 118 +++++ utils/trace-visualizer/js/bootstrap.min.js | 9 + utils/trace-visualizer/js/d3-gantt.js | 485 ++++++++++++++++++ .../js/d3-tip-0.8.0-alpha.1.js | 352 +++++++++++++ utils/trace-visualizer/js/d3.v4.min.js | 2 + utils/trace-visualizer/js/jquery.min.js | 5 + 9 files changed, 1094 insertions(+) create mode 100644 utils/trace-visualizer/README.md create mode 100644 utils/trace-visualizer/css/bootstrap.min.css create mode 100644 utils/trace-visualizer/css/d3-gantt.css create mode 100644 utils/trace-visualizer/index.html create mode 100644 utils/trace-visualizer/js/bootstrap.min.js create mode 100644 utils/trace-visualizer/js/d3-gantt.js create mode 100644 utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js create mode 100644 utils/trace-visualizer/js/d3.v4.min.js create mode 100644 utils/trace-visualizer/js/jquery.min.js diff --git a/utils/trace-visualizer/README.md b/utils/trace-visualizer/README.md new file mode 100644 index 00000000000..c38c4b8eeea --- /dev/null +++ b/utils/trace-visualizer/README.md @@ -0,0 +1,36 @@ +Trace visualizer is a tool for representation of a tracing data as a Gantt diagram. + +# Quick start +For now this tool is not integrate into clickhouse and require a lot of manual adjustments. +```bash +cd utils/trace-visualizer +python3 -m http.server +``` +Open [localhost](http://localhost:8000). It will show an example of data. To show your tracing data you have to put it in JSON format near `index.html` and change call to `fetchData()` function at the bottom of `index.html`. (Do not forget to disable browser caching while changing it). + +# Visualizing query trace +First of all [opentelemetry_span_log](https://clickhouse.com/docs/en/operations/opentelemetry/) system table must be enabled to save query traces. Then run a query you want to trace with a setting: +```sql +set opentelemetry_start_trace_probability=1; +SELECT 1; +``` + +To find out `trace_id` of a query run the following command: +```sql +SELECT DISTINCT trace_id FROM system.opentelemetry_span_log ORDER BY query_start_time DESC; +``` + +To obtain JSON data suitable for visualizing run: +```sql +SELECT tuple (parent_span_id, attribute['clickhouse.thread_id'] || attribute['thread_number'] as thread_id)::Tuple(parent_span_id UInt64, thread_id String) as group, operation_name, start_time_us, finish_time_us, sipHash64(operation_name) as color, attribute +from system.opentelemetry_span_log +WHERE trace_id = 'your-trace-id' +ORDER BY group ASC +FORMAT JSON SETTINGS output_format_json_named_tuples_as_objects = 1; +``` + +# Dependencies + 1. [D3js](https://github.com/d3/d3) (v4). + 2. [Tooltips for D3](https://github.com/caged/d3-tip). + 3. [jquery](https://github.com/jquery/jquery). + 4. [Bootstrap](https://github.com/twbs/bootstrap). diff --git a/utils/trace-visualizer/css/bootstrap.min.css b/utils/trace-visualizer/css/bootstrap.min.css new file mode 100644 index 00000000000..f602cacbf8d --- /dev/null +++ b/utils/trace-visualizer/css/bootstrap.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap v3.0.2 by @fat and @mdo + * Copyright 2013 Twitter, Inc. + * Licensed under http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + +/*! normalize.css v2.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a{background:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-primary:hover{color:#3071a9}.text-warning{color:#c09853}.text-warning:hover{color:#a47e3c}.text-danger{color:#b94a48}.text-danger:hover{color:#953b39}.text-success{color:#468847}.text-success:hover{color:#356635}.text-info{color:#3a87ad}.text-info:hover{color:#2d6987}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h1 small,h2 small,h3 small,h1 .small,h2 .small,h3 .small{font-size:65%}h4,h5,h6{margin-top:10px;margin-bottom:10px}h4 small,h5 small,h6 small,h4 .small,h5 .small,h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}.list-inline>li:first-child{padding-left:0}dl{margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.428571429;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small,blockquote.pull-right .small{text-align:right}blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.428571429}code,kbd,pre,samp{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.col-xs-offset-0{margin-left:0}@media(min-width:768px){.container{width:750px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-0{margin-left:0}}@media(min-width:992px){.container{width:970px}.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-0{margin-left:0}}@media(min-width:1200px){.container{width:1170px}.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}@media(max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}output{display:block;padding-top:7px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:normal;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm{height:auto}.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:45px;line-height:45px}textarea.input-lg{height:auto}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline .radio,.form-inline .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-control-static{padding-top:7px}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-link{font-weight:normal;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:normal;line-height:1;-moz-osx-font-smoothing:grayscale}.glyphicon:empty{width:1em}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid #000;border-right:4px solid transparent;border-bottom:0 dotted;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0 dotted;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.btn-default .caret{border-top-color:#333}.btn-primary .caret,.btn-success .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret{border-top-color:#fff}.dropup .btn-default .caret{border-bottom-color:#333}.dropup .btn-primary .caret,.dropup .btn-success .caret,.dropup .btn-warning .caret,.dropup .btn-danger .caret,.dropup .btn-info .caret{border-bottom-color:#fff}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:5px 10px;padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified .btn{display:table-cell;float:none;width:1%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group.col{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:45px;line-height:45px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn:first-child>.btn{margin-right:-1px}.input-group-btn:last-child>.btn{margin-left:-1px}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .open>a .caret,.nav .open>a:hover .caret,.nav .open>a:focus .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media(min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-pills>li.active>a .caret,.nav-pills>li.active>a:hover .caret,.nav-pills>li.active>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media(min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca}.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:auto}.navbar-collapse .navbar-nav.navbar-left:first-child{margin-left:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:-15px}.navbar-collapse .navbar-text:last-child{margin-right:0}}.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-header,.container>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-text{float:left;margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{margin-right:15px;margin-left:15px}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#ccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.dropdown>a:hover .caret,.navbar-default .navbar-nav>.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.open>a .caret,.navbar-default .navbar-nav>.open>a:hover .caret,.navbar-default .navbar-nav>.open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.navbar-default .navbar-nav>.dropdown>a .caret{border-top-color:#777;border-bottom-color:#777}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#eee}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.btn .badge{position:relative;top:-1px}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.thumbnail{display:inline-block;display:block;height:auto;max-width:100%;padding:4px;margin-bottom:20px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img{display:block;height:auto;max-width:100%;margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{color:#c09853;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#a47e3c}.alert-danger{color:#b94a48;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#953b39}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive{margin-bottom:0}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:last-child>th,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:last-child>td,.panel>.table-responsive>.table-bordered>thead>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group .panel{margin-bottom:0;overflow:hidden;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-heading>.dropdown .caret{border-color:#333 transparent}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-heading>.dropdown .caret{border-color:#fff transparent}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading>.dropdown .caret{border-color:#468847 transparent}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading>.dropdown .caret{border-color:#c09853 transparent}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#b94a48;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading>.dropdown .caret{border-color:#b94a48 transparent}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading>.dropdown .caret{border-color:#3a87ad transparent}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;z-index:1050;width:auto;padding:10px;margin-right:auto;margin-left:auto}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{width:600px;padding-top:30px;padding-bottom:30px}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.5)),to(rgba(0,0,0,0.0001)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.0001)),to(rgba(0,0,0,0.5)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicons-chevron-left,.carousel-control .glyphicons-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,tr.visible-xs,th.visible-xs,td.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}tr.visible-xs.visible-sm{display:table-row!important}th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}tr.visible-xs.visible-md{display:table-row!important}th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}tr.visible-xs.visible-lg{display:table-row!important}th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell!important}}.visible-sm,tr.visible-sm,th.visible-sm,td.visible-sm{display:none!important}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}tr.visible-sm.visible-xs{display:table-row!important}th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}tr.visible-sm.visible-md{display:table-row!important}th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}tr.visible-sm.visible-lg{display:table-row!important}th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell!important}}.visible-md,tr.visible-md,th.visible-md,td.visible-md{display:none!important}@media(max-width:767px){.visible-md.visible-xs{display:block!important}tr.visible-md.visible-xs{display:table-row!important}th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}tr.visible-md.visible-sm{display:table-row!important}th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}tr.visible-md.visible-lg{display:table-row!important}th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell!important}}.visible-lg,tr.visible-lg,th.visible-lg,td.visible-lg{display:none!important}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}tr.visible-lg.visible-xs{display:table-row!important}th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}tr.visible-lg.visible-sm{display:table-row!important}th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}tr.visible-lg.visible-md{display:table-row!important}th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}tr.hidden-xs{display:table-row!important}th.hidden-xs,td.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs,tr.hidden-xs,th.hidden-xs,td.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm,tr.hidden-xs.hidden-sm,th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md,tr.hidden-xs.hidden-md,th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg,tr.hidden-xs.hidden-lg,th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs,tr.hidden-sm.hidden-xs,th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm,tr.hidden-sm,th.hidden-sm,td.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md,tr.hidden-sm.hidden-md,th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg,tr.hidden-sm.hidden-lg,th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs,tr.hidden-md.hidden-xs,th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm,tr.hidden-md.hidden-sm,th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md,tr.hidden-md,th.hidden-md,td.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg,tr.hidden-md.hidden-lg,th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs,tr.hidden-lg.hidden-xs,th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm,tr.hidden-lg.hidden-sm,th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md,tr.hidden-lg.hidden-md,th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg,tr.hidden-lg,th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print,tr.visible-print,th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print,tr.hidden-print,th.hidden-print,td.hidden-print{display:none!important}} diff --git a/utils/trace-visualizer/css/d3-gantt.css b/utils/trace-visualizer/css/d3-gantt.css new file mode 100644 index 00000000000..31da093dd2d --- /dev/null +++ b/utils/trace-visualizer/css/d3-gantt.css @@ -0,0 +1,78 @@ +/* + * d3-gantt.css by @serxa + */ + +.chart { + font-family: Arial, sans-serif; + font-size: 12px; +} + +rect.zoom-panel { + /*cursor: ew-resize;*/ + fill: none; + pointer-events: all; +} + +.axis path,.axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.axis.y { + font-size: 16px; + cursor: ns-resize; +} + +.axis.x { + font-size: 16px; +} + +#ruler { + text-anchor: middle; + alignment-baseline: before-edge; + font-size: 16px; + font-family: sans-serif; + pointer-events: none; +} + +.d3-tip { + line-height: 1; + font-weight: bold; + padding: 12px; + background: rgba(0, 0, 0, 0.8); + color: #fff; + border-radius: 2px; +} + +.d3-tip pre { + font-weight: bold; + padding: 12px; + background: rgba(0, 0, 0, 0); + color: #fff; + border: 0px; +} + +/* Style northward tooltips differently */ +.d3-tip.n:after { + margin: -1px 0 0 0; + top: 100%; + left: 0; +} + +/* for arrowhead marker */ +#arrow { + stroke-width:1; + stroke-dasharray:0; +} + +.bar:hover { + stroke-width: 1px; + stroke: black; +} + +#errmsg { + width: 95vw; + margin: 0 auto; + padding: 10px; +} diff --git a/utils/trace-visualizer/index.html b/utils/trace-visualizer/index.html new file mode 100644 index 00000000000..ea02b3141ad --- /dev/null +++ b/utils/trace-visualizer/index.html @@ -0,0 +1,118 @@ + + + + + + + Trace Gantt + + + + + + + + + +
+ + + + diff --git a/utils/trace-visualizer/js/bootstrap.min.js b/utils/trace-visualizer/js/bootstrap.min.js new file mode 100644 index 00000000000..80e40418f2d --- /dev/null +++ b/utils/trace-visualizer/js/bootstrap.min.js @@ -0,0 +1,9 @@ +/*! + * Bootstrap v3.0.2 by @fat and @mdo + * Copyright 2013 Twitter, Inc. + * Licensed under http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + +if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); diff --git a/utils/trace-visualizer/js/d3-gantt.js b/utils/trace-visualizer/js/d3-gantt.js new file mode 100644 index 00000000000..21a9dab6133 --- /dev/null +++ b/utils/trace-visualizer/js/d3-gantt.js @@ -0,0 +1,485 @@ +/* + * d3-gantt.js by @serxa + * Based on https://github.com/ydb-platform/ydb/blob/stable-22-2/library/cpp/lwtrace/mon/static/js/d3-gantt.js + */ + d3.gantt = function() { + function gantt(input_data) { + data = input_data; + + initAxis(); + + // create svg element + svg = d3.select(selector) + .append("svg") + .attr("class", "chart") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + ; + + // create arrowhead marker + defs = svg.append("defs"); + defs.append("marker") + .attr("id", "arrow") + .attr("viewBox", "0 -5 10 10") + .attr("refX", 5) + .attr("refY", 0) + .attr("markerWidth", 4) + .attr("markerHeight", 4) + .attr("orient", "auto") + .append("path") + .attr("d", "M0,-5L10,0L0,5") + .attr("class","arrowHead") + ; + + zoom = d3.zoom() + .scaleExtent([0.1, 1000]) + //.translateExtent([0, 0], [1000,0]) + .on("zoom", function() { + if (tipShown != null) { + tip.hide(tipShown); + } + var tr = d3.event.transform; + xZoomed = tr.rescaleX(x); + svg.select("g.x.axis").call(xAxis.scale(xZoomed)); + + var dy = d3.event.sourceEvent.screenY - zoom.startScreenY; + var newScrollTop = documentBodyScrollTop() - dy; + window.scrollTo(documentBodyScrollLeft(), newScrollTop); + documentBodyScrollTop(newScrollTop); + zoom.startScreenY = d3.event.sourceEvent.screenY; + + zoomContainer1.attr("transform", "translate(" + tr.x + ",0) scale(" + tr.k + ",1)"); + zoomContainer2.attr("transform", "translate(" + tr.x + ",0) scale(" + tr.k + ",1)"); + + render(); + }) + .on("start", function() { + zoom.startScreenY = d3.event.sourceEvent.screenY; + }) + .on("end", function() { + }) + ; + + svgChartContainer = svg.append('g') + .attr("transform", "translate(" + margin.left + ", " + margin.top + ")") + ; + svgChart = svgChartContainer.append("svg") + .attr("top", 0) + .attr("left", 0) + .attr("width", width) + .attr("height", height) + .attr("viewBox", "0 0 " + width + " " + height) + ; + + zoomContainer1 = svgChart.append("g"); + + zoomPanel = svgChart.append("rect") + .attr("class", "zoom-panel") + .attr("width", width) + .attr("height", height) + .call(zoom) + ; + + zoomContainer2 = svgChart.append("g"); + bandsSvg = zoomContainer2.append("g"); + + // tooltips for bands + var maxTipHeight = 130; + const tipDirection = d => y(d.band) - maxTipHeight < documentBodyScrollTop()? 's': 'n'; + tip = d3.tip() + .attr("class", "d3-tip") + .offset(function(d) { + // compute x to return tip in chart region + var t0 = (d.t1 + d.t2) / 2; + var t1 = Math.min(Math.max(t0, xZoomed.invert(0)), xZoomed.invert(width)); + var dir = tipDirection(d); + return [dir === 'n'? -10 : 10, xZoomed(t1) - xZoomed(t0)]; + }) + .direction(tipDirection) + .html(d => "
" + d.text + "
") + ; + + bandsSvg.call(tip); + + render(); + + // container for non-zoomable elements + fixedContainer = svg.append("g") + .attr("transform", "translate(" + margin.left + ", " + margin.top + ")") + ; + + // create x axis + fixedContainer.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0, " + (height - margin.top - margin.bottom) + ")") + .transition() + .call(xAxis) + ; + + // create y axis + fixedContainer.append("g") + .attr("class", "y axis") + .transition() + .call(yAxis) + ; + + // make y axis ticks draggable + var ytickdrag = d3.drag() + .on("drag", function(d) { + var ypos = d3.event.y - margin.top; + var index = Math.floor((ypos / y.step())); + index = Math.min(Math.max(index, 0), this.initDomain.length - 1); + if (index != this.curIndex) { + var newDomain = []; + for (var i = 0; i < this.initDomain.length; ++i) { + newDomain.push(this.initDomain[i]); + } + newDomain.splice(this.initIndex, 1); + newDomain.splice(index, 0, this.initDomain[this.initIndex]); + + this.curIndex = index; + this.curDomain = newDomain; + y.domain(newDomain); + + // rearange y scale and axis + svg.select("g.y.axis").transition().call(yAxis); + + // rearange other stuff + render(-1, true); + } + }) + .on("start", function(d) { + var ypos = d3.event.y - margin.top; + this.initIndex = Math.floor((ypos / y.step())); + this.initDomain = y.domain(); + }) + .on("end", function(d) { + svg.select("g.y.axis").call(yAxis); + }) + ; + svg.selectAll("g.y.axis .tick") + .call(ytickdrag) + ; + + // right margin + var rmargin = fixedContainer.append("g") + .attr("id", "right-margin") + .attr("transform", "translate(" + width + ", 0)") + ; + rmargin.append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", 1) + .attr("height", height - margin.top - margin.bottom) + ; + + // top margin + var tmargin = fixedContainer.append("g") + .attr("id", "top-margin") + .attr("transform", "translate(0, 0)") + ; + tmargin.append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", width) + .attr("height", 1) + ; + + // ruler + ruler = fixedContainer.append("g") + .attr("id", "ruler") + .attr("transform", "translate(0, 0)") + ; + ruler.append("rect") + .attr("id", "ruler-line") + .attr("x", 0) + .attr("y", 0) + .attr("width", "1") + .attr("height", height - margin.top - margin.bottom + 8) + ; + ruler.append("rect") + .attr("id", "bgrect") + .attr("x", 0) + .attr("y", 0) + .attr("width", 0) + .attr("height", 0) + .style("fill", "white") + ; + ruler.append("text") + .attr("x", 0) + .attr("y", height - margin.top - margin.bottom + 16) + .attr("dy", "0.71em") + .text("0") + ; + + svg.on('mousemove', function() { + positionRuler(d3.event.pageX); + }); + + // scroll handling + window.onscroll = function myFunction() { + documentBodyScrollLeft(document.body.scrollLeft); + documentBodyScrollTop(document.body.scrollTop); + var scroll = scrollParams(); + + svgChartContainer + .attr("transform", "translate(" + margin.left + + ", " + (margin.top + scroll.y1) + ")"); + svgChart + .attr("viewBox", "0 " + scroll.y1 + " " + width + " " + scroll.h) + .attr("height", scroll.h); + tmargin + .attr("transform", "translate(0," + scroll.y1 + ")"); + fixedContainer.select(".x.axis") + .attr("transform", "translate(0," + scroll.y2 + ")"); + rmargin.select("rect") + .attr("y", scroll.y1) + .attr("height", scroll.h); + ruler.select("#ruler-line") + .attr("y", scroll.y1) + .attr("height", scroll.h); + + positionRuler(); + } + + // render axis + svg.select("g.x.axis").call(xAxis); + svg.select("g.y.axis").call(yAxis); + + // update to initiale state + window.onscroll(0); + + return gantt; + } + +// private: + + var keyFunction = function(d) { + return d.t1.toString() + d.t2.toString() + d.band.toString(); + } + + var bandTransform = function(d) { + return "translate(" + x(d.t1) + "," + y(d.band) + ")"; + } + + var xPixel = function(d) { + return xZoomed.invert(1) - xZoomed.invert(0); + } + + var render = function(t0, smooth) { + // Save/restore last t0 value + if (!arguments.length || t0 == -1) { + t0 = render.t0; + } + render.t0 = t0; + smooth = smooth || false; + + // Create rectangles for bands + bands = bandsSvg.selectAll("rect.bar") + .data(data, keyFunction); + bands.exit().remove(); + bands.enter().append("rect") + .attr("class", "bar") + .attr("vector-effect", "non-scaling-stroke") + .style("fill", d => d.color) + .on('click', function(d) { + if (tipShown != d) { + tipShown = d; + tip.show(d); + } else { + tipShown = null; + tip.hide(d); + } + }) + .merge(bands) + .transition().duration(smooth? 250: 0) + .attr("y", 0) + .attr("transform", bandTransform) + .attr("height", y.bandwidth()) + .attr("width", d => Math.max(1*xPixel(), x(d.t2) - x(d.t1))) + ; + + var emptyMarker = bandsSvg.selectAll("text") + .data(data.length == 0? ["no data to show"]: []); + emptyMarker.exit().remove(); + emptyMarker.enter().append("text") + .text(d => d) + ; + } + + function initAxis() { + x = d3.scaleLinear() + .domain([timeDomainStart, timeDomainEnd]) + .range([0, width]) + //.clamp(true); // dosn't work with zoom/pan + xZoomed = x; + y = d3.scaleBand() + .domain(Object.values(data).map(d => d.band).sort()) + .rangeRound([0, height - margin.top - margin.bottom]) + .padding(0.5); + xAxis = d3.axisBottom() + .scale(x) + //.tickSubdivide(true) + .tickSize(8) + .tickPadding(8); + yAxis = d3.axisLeft() + .scale(y) + .tickSize(0); + } + + // slow function wrapper + var documentBodyScrollLeft = function(value) { + if (!arguments.length) { + if (documentBodyScrollLeft.value === undefined) { + documentBodyScrollLeft.value = document.body.scrollLeft; + } + return documentBodyScrollLeft.value; + } else { + documentBodyScrollLeft.value = value; + } + } + + // slow function wrapper + var documentBodyScrollTop = function(value) { + if (!arguments.length) { + if (!documentBodyScrollTop.value === undefined) { + documentBodyScrollTop.value = document.body.scrollTop; + } + return documentBodyScrollTop.value; + } else { + documentBodyScrollTop.value = value; + } + } + + var scrollParams = function() { + var y1 = documentBodyScrollTop(); + var y2 = y1 + window.innerHeight - margin.footer; + y2 = Math.min(y2, height - margin.top - margin.bottom); + var h = y2 - y1; + return { + y1: y1, + y2: y2, + h: h + }; + } + + var posTextFormat = d3.format(".1f"); + + var positionRuler = function(pageX) { + if (!arguments.length) { + pageX = positionRuler.pageX || 0; + } else { + positionRuler.pageX = pageX; + } + + // x-coordinate + if (!positionRuler.svgLeft) { + positionRuler.svgLeft = svg.node().getBoundingClientRect().x; + } + + var xpos = pageX - margin.left + 1 - positionRuler.svgLeft; + var tpos = xZoomed.invert(xpos); + tpos = Math.min(Math.max(tpos, xZoomed.invert(0)), xZoomed.invert(width)); + ruler.attr("transform", "translate(" + xZoomed(tpos) + ", 0)"); + var posText = posTextFormat(tpos); + + // scroll-related + var scroll = scrollParams(); + + var text = ruler.select("text") + .attr("y", scroll.y2 + 16) + ; + + // getBBox() is very slow, so compute symbol width once + var xpadding = 5; + var ypadding = 5; + if (!positionRuler.bbox) { + positionRuler.bbox = text.node().getBBox(); + } + + text.text(posText); + var textWidth = 10 * posText.length; + ruler.select("#bgrect") + .attr("x", -textWidth/2 - xpadding) + .attr("y", positionRuler.bbox.y - ypadding) + .attr("width", textWidth + (xpadding*2)) + .attr("height", positionRuler.bbox.height + (ypadding*2)) + ; + + render(tpos); + } + +// public: + + gantt.width = function(value) { + if (!arguments.length) + return width; + width = +value; + return gantt; + } + + gantt.height = function(value) { + if (!arguments.length) + return height; + height = +value; + return gantt; + } + + gantt.selector = function(value) { + if (!arguments.length) + return selector; + selector = value; + return gantt; + } + + gantt.timeDomain = function(value) { + if (!arguments.length) + return [timeDomainStart, timeDomainEnd]; + timeDomainStart = value[0]; + timeDomainEnd = value[1]; + return gantt; + } + + gantt.data = function() { + return data; + } + + // constructor + + // Config + var margin = { top: 20, right: 40, bottom: 20, left: 200, footer: 100 }, + height = document.body.clientHeight - margin.top - margin.bottom - 5, + width = document.body.clientWidth - margin.right - margin.left - 5, + selector = 'body', + timeDomainStart = 0, + timeDomainEnd = 1000, + scales = {}; + ; + + // View + var x = null, + xZoomed = null, + y = null, + xAxis = null, + yAxis = null, + svg = null, + defs = null, + svgChartContainer = null, + svgChart = null, + zoomPanel = null, + zoomContainer1 = null, + zoomContainer2 = null, + fixedContainer = null, + zoom = null, + bandsSvg = null, + bands = null, + tip = null, + tipShown = null, + ruler = null + ; + + // Model + var data = null; + + return gantt; +} diff --git a/utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js b/utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js new file mode 100644 index 00000000000..ad3a6c0d199 --- /dev/null +++ b/utils/trace-visualizer/js/d3-tip-0.8.0-alpha.1.js @@ -0,0 +1,352 @@ +/** + * d3.tip + * Copyright (c) 2013 Justin Palmer + * + * Tooltips for d3.js SVG visualizations + */ +// eslint-disable-next-line no-extra-semi +;(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module with d3 as a dependency. + define([ + 'd3-collection', + 'd3-selection' + ], factory) + } else if (typeof module === 'object' && module.exports) { + /* eslint-disable global-require */ + // CommonJS + var d3Collection = require('d3-collection'), + d3Selection = require('d3-selection') + module.exports = factory(d3Collection, d3Selection) + /* eslint-enable global-require */ + } else { + // Browser global. + var d3 = root.d3 + // eslint-disable-next-line no-param-reassign + root.d3.tip = factory(d3, d3) + } +}(this, function(d3Collection, d3Selection) { + // Public - contructs a new tooltip + // + // Returns a tip + return function() { + var direction = d3TipDirection, + offset = d3TipOffset, + html = d3TipHTML, + rootElement = document.body, + node = initNode(), + svg = null, + point = null, + target = null + + function tip(vis) { + svg = getSVGNode(vis) + if (!svg) return + point = svg.createSVGPoint() + rootElement.appendChild(node) + } + + // Public - show the tooltip on the screen + // + // Returns a tip + tip.show = function() { + var args = Array.prototype.slice.call(arguments) + if (args[args.length - 1] instanceof SVGElement) target = args.pop() + + var content = html.apply(this, args), + poffset = offset.apply(this, args), + dir = direction.apply(this, args), + nodel = getNodeEl(), + i = directions.length, + coords, + scrollTop = document.documentElement.scrollTop || + rootElement.scrollTop, + scrollLeft = document.documentElement.scrollLeft || + rootElement.scrollLeft + + nodel.html(content) + .style('opacity', 1).style('pointer-events', 'all') + + while (i--) nodel.classed(directions[i], false) + coords = directionCallbacks.get(dir).apply(this) + nodel.classed(dir, true) + .style('top', (coords.top + poffset[0]) + scrollTop + 'px') + .style('left', (coords.left + poffset[1]) + scrollLeft + 'px') + + return tip + } + + // Public - hide the tooltip + // + // Returns a tip + tip.hide = function() { + var nodel = getNodeEl() + nodel.style('opacity', 0).style('pointer-events', 'none') + return tip + } + + // Public: Proxy attr calls to the d3 tip container. + // Sets or gets attribute value. + // + // n - name of the attribute + // v - value of the attribute + // + // Returns tip or attribute value + // eslint-disable-next-line no-unused-vars + tip.attr = function(n, v) { + if (arguments.length < 2 && typeof n === 'string') { + return getNodeEl().attr(n) + } + + var args = Array.prototype.slice.call(arguments) + d3Selection.selection.prototype.attr.apply(getNodeEl(), args) + return tip + } + + // Public: Proxy style calls to the d3 tip container. + // Sets or gets a style value. + // + // n - name of the property + // v - value of the property + // + // Returns tip or style property value + // eslint-disable-next-line no-unused-vars + tip.style = function(n, v) { + if (arguments.length < 2 && typeof n === 'string') { + return getNodeEl().style(n) + } + + var args = Array.prototype.slice.call(arguments) + d3Selection.selection.prototype.style.apply(getNodeEl(), args) + return tip + } + + // Public: Set or get the direction of the tooltip + // + // v - One of n(north), s(south), e(east), or w(west), nw(northwest), + // sw(southwest), ne(northeast) or se(southeast) + // + // Returns tip or direction + tip.direction = function(v) { + if (!arguments.length) return direction + direction = v == null ? v : functor(v) + + return tip + } + + // Public: Sets or gets the offset of the tip + // + // v - Array of [x, y] offset + // + // Returns offset or + tip.offset = function(v) { + if (!arguments.length) return offset + offset = v == null ? v : functor(v) + + return tip + } + + // Public: sets or gets the html value of the tooltip + // + // v - String value of the tip + // + // Returns html value or tip + tip.html = function(v) { + if (!arguments.length) return html + html = v == null ? v : functor(v) + + return tip + } + + // Public: sets or gets the root element anchor of the tooltip + // + // v - root element of the tooltip + // + // Returns root node of tip + tip.rootElement = function(v) { + if (!arguments.length) return rootElement + rootElement = v == null ? v : functor(v) + + return tip + } + + // Public: destroys the tooltip and removes it from the DOM + // + // Returns a tip + tip.destroy = function() { + if (node) { + getNodeEl().remove() + node = null + } + return tip + } + + function d3TipDirection() { return 'n' } + function d3TipOffset() { return [0, 0] } + function d3TipHTML() { return ' ' } + + var directionCallbacks = d3Collection.map({ + n: directionNorth, + s: directionSouth, + e: directionEast, + w: directionWest, + nw: directionNorthWest, + ne: directionNorthEast, + sw: directionSouthWest, + se: directionSouthEast + }), + directions = directionCallbacks.keys() + + function directionNorth() { + var bbox = getScreenBBox() + return { + top: bbox.n.y - node.offsetHeight, + left: bbox.n.x - node.offsetWidth / 2 + } + } + + function directionSouth() { + var bbox = getScreenBBox() + return { + top: bbox.s.y, + left: bbox.s.x - node.offsetWidth / 2 + } + } + + function directionEast() { + var bbox = getScreenBBox() + return { + top: bbox.e.y - node.offsetHeight / 2, + left: bbox.e.x + } + } + + function directionWest() { + var bbox = getScreenBBox() + return { + top: bbox.w.y - node.offsetHeight / 2, + left: bbox.w.x - node.offsetWidth + } + } + + function directionNorthWest() { + var bbox = getScreenBBox() + return { + top: bbox.nw.y - node.offsetHeight, + left: bbox.nw.x - node.offsetWidth + } + } + + function directionNorthEast() { + var bbox = getScreenBBox() + return { + top: bbox.ne.y - node.offsetHeight, + left: bbox.ne.x + } + } + + function directionSouthWest() { + var bbox = getScreenBBox() + return { + top: bbox.sw.y, + left: bbox.sw.x - node.offsetWidth + } + } + + function directionSouthEast() { + var bbox = getScreenBBox() + return { + top: bbox.se.y, + left: bbox.se.x + } + } + + function initNode() { + var div = d3Selection.select(document.createElement('div')) + div + .style('position', 'absolute') + .style('top', 0) + .style('opacity', 0) + .style('pointer-events', 'none') + .style('box-sizing', 'border-box') + + return div.node() + } + + function getSVGNode(element) { + var svgNode = element.node() + if (!svgNode) return null + if (svgNode.tagName.toLowerCase() === 'svg') return svgNode + return svgNode.ownerSVGElement + } + + function getNodeEl() { + if (node == null) { + node = initNode() + // re-add node to DOM + rootElement.appendChild(node) + } + return d3Selection.select(node) + } + + // Private - gets the screen coordinates of a shape + // + // Given a shape on the screen, will return an SVGPoint for the directions + // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), + // nw(northwest), sw(southwest). + // + // +-+-+ + // | | + // + + + // | | + // +-+-+ + // + // Returns an Object {n, s, e, w, nw, sw, ne, se} + function getScreenBBox() { + var targetel = target || d3Selection.event.target + + while (targetel.getScreenCTM == null && targetel.parentNode == null) { + targetel = targetel.parentNode + } + + var bbox = {}, + matrix = targetel.getScreenCTM(), + tbbox = targetel.getBBox(), + width = tbbox.width, + height = tbbox.height, + x = tbbox.x, + y = tbbox.y + + point.x = x + point.y = y + bbox.nw = point.matrixTransform(matrix) + point.x += width + bbox.ne = point.matrixTransform(matrix) + point.y += height + bbox.se = point.matrixTransform(matrix) + point.x -= width + bbox.sw = point.matrixTransform(matrix) + point.y -= height / 2 + bbox.w = point.matrixTransform(matrix) + point.x += width + bbox.e = point.matrixTransform(matrix) + point.x -= width / 2 + point.y -= height / 2 + bbox.n = point.matrixTransform(matrix) + point.y += height + bbox.s = point.matrixTransform(matrix) + + return bbox + } + + // Private - replace D3JS 3.X d3.functor() function + function functor(v) { + return typeof v === 'function' ? v : function() { + return v + } + } + + return tip + } +// eslint-disable-next-line semi +})); diff --git a/utils/trace-visualizer/js/d3.v4.min.js b/utils/trace-visualizer/js/d3.v4.min.js new file mode 100644 index 00000000000..6a2705865cd --- /dev/null +++ b/utils/trace-visualizer/js/d3.v4.min.js @@ -0,0 +1,2 @@ +// https://d3js.org Version 4.10.0. Copyright 2017 Mike Bostock. +(function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t.d3=t.d3||{})})(this,function(t){"use strict";function n(t){return function(n,e){return ss(t(n),e)}}function e(t,n){return[t,n]}function r(t,n,e){var r=(n-t)/Math.max(0,e),i=Math.floor(Math.log(r)/Math.LN10),o=r/Math.pow(10,i);return i>=0?(o>=Ts?10:o>=ks?5:o>=Ns?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(o>=Ts?10:o>=ks?5:o>=Ns?2:1)}function i(t,n,e){var r=Math.abs(n-t)/Math.max(0,e),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),o=r/i;return o>=Ts?i*=10:o>=ks?i*=5:o>=Ns&&(i*=2),n=0&&(e=t.slice(r+1),t=t.slice(0,r)),t&&!n.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:e}})}function v(t,n){for(var e,r=0,i=t.length;r=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}})}function T(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;rn?1:t>=n?0:NaN}function R(t){return function(){this.removeAttribute(t)}}function L(t){return function(){this.removeAttributeNS(t.space,t.local)}}function q(t,n){return function(){this.setAttribute(t,n)}}function U(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function D(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function O(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function F(t){return function(){this.style.removeProperty(t)}}function I(t,n,e){return function(){this.style.setProperty(t,n,e)}}function Y(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function B(t,n){return t.style.getPropertyValue(n)||uf(t).getComputedStyle(t,null).getPropertyValue(n)}function j(t){return function(){delete this[t]}}function H(t,n){return function(){this[t]=n}}function X(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function $(t){return t.trim().split(/^|\s+/)}function V(t){return t.classList||new W(t)}function W(t){this._node=t,this._names=$(t.getAttribute("class")||"")}function Z(t,n){for(var e=V(t),r=-1,i=n.length;++r>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1)):(n=gf.exec(t))?kt(parseInt(n[1],16)):(n=mf.exec(t))?new At(n[1],n[2],n[3],1):(n=xf.exec(t))?new At(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=bf.exec(t))?Nt(n[1],n[2],n[3],n[4]):(n=wf.exec(t))?Nt(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=Mf.exec(t))?Ct(n[1],n[2]/100,n[3]/100,1):(n=Tf.exec(t))?Ct(n[1],n[2]/100,n[3]/100,n[4]):kf.hasOwnProperty(t)?kt(kf[t]):"transparent"===t?new At(NaN,NaN,NaN,0):null}function kt(t){return new At(t>>16&255,t>>8&255,255&t,1)}function Nt(t,n,e,r){return r<=0&&(t=n=e=NaN),new At(t,n,e,r)}function St(t){return t instanceof Mt||(t=Tt(t)),t?(t=t.rgb(),new At(t.r,t.g,t.b,t.opacity)):new At}function Et(t,n,e,r){return 1===arguments.length?St(t):new At(t,n,e,null==r?1:r)}function At(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function Ct(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new Rt(t,n,e,r)}function zt(t){if(t instanceof Rt)return new Rt(t.h,t.s,t.l,t.opacity);if(t instanceof Mt||(t=Tt(t)),!t)return new Rt;if(t instanceof Rt)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),u=NaN,a=o-i,c=(o+i)/2;return a?(u=n===o?(e-r)/a+6*(e0&&c<1?0:u,new Rt(u,a,c,t.opacity)}function Pt(t,n,e,r){return 1===arguments.length?zt(t):new Rt(t,n,e,null==r?1:r)}function Rt(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Lt(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}function qt(t){if(t instanceof Dt)return new Dt(t.l,t.a,t.b,t.opacity);if(t instanceof Ht){var n=t.h*Nf;return new Dt(t.l,Math.cos(n)*t.c,Math.sin(n)*t.c,t.opacity)}t instanceof At||(t=St(t));var e=Yt(t.r),r=Yt(t.g),i=Yt(t.b),o=Ot((.4124564*e+.3575761*r+.1804375*i)/Ef),u=Ot((.2126729*e+.7151522*r+.072175*i)/Af);return new Dt(116*u-16,500*(o-u),200*(u-Ot((.0193339*e+.119192*r+.9503041*i)/Cf)),t.opacity)}function Ut(t,n,e,r){return 1===arguments.length?qt(t):new Dt(t,n,e,null==r?1:r)}function Dt(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function Ot(t){return t>Lf?Math.pow(t,1/3):t/Rf+zf}function Ft(t){return t>Pf?t*t*t:Rf*(t-zf)}function It(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function Yt(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function Bt(t){if(t instanceof Ht)return new Ht(t.h,t.c,t.l,t.opacity);t instanceof Dt||(t=qt(t));var n=Math.atan2(t.b,t.a)*Sf;return new Ht(n<0?n+360:n,Math.sqrt(t.a*t.a+t.b*t.b),t.l,t.opacity)}function jt(t,n,e,r){return 1===arguments.length?Bt(t):new Ht(t,n,e,null==r?1:r)}function Ht(t,n,e,r){this.h=+t,this.c=+n,this.l=+e,this.opacity=+r}function Xt(t){if(t instanceof Vt)return new Vt(t.h,t.s,t.l,t.opacity);t instanceof At||(t=St(t));var n=t.r/255,e=t.g/255,r=t.b/255,i=(Bf*r+If*n-Yf*e)/(Bf+If-Yf),o=r-i,u=(Ff*(e-i)-Df*o)/Of,a=Math.sqrt(u*u+o*o)/(Ff*i*(1-i)),c=a?Math.atan2(u,o)*Sf-120:NaN;return new Vt(c<0?c+360:c,a,i,t.opacity)}function $t(t,n,e,r){return 1===arguments.length?Xt(t):new Vt(t,n,e,null==r?1:r)}function Vt(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Wt(t,n,e,r,i){var o=t*t,u=o*t;return((1-3*t+3*o-u)*n+(4-6*o+3*u)*e+(1+3*t+3*o-3*u)*r+u*i)/6}function Zt(t,n){return function(e){return t+e*n}}function Gt(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}function Jt(t,n){var e=n-t;return e?Zt(t,e>180||e<-180?e-360*Math.round(e/360):e):Jf(isNaN(t)?n:t)}function Qt(t){return 1==(t=+t)?Kt:function(n,e){return e-n?Gt(n,e,t):Jf(isNaN(n)?e:n)}}function Kt(t,n){var e=n-t;return e?Zt(t,e):Jf(isNaN(t)?n:t)}function tn(t){return function(n){var e,r,i=n.length,o=new Array(i),u=new Array(i),a=new Array(i);for(e=0;e180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:rl(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}function a(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:rl(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}function c(t,n,e,r,o,u){if(t!==e||n!==r){var a=o.push(i(o)+"scale(",null,",",null,")");u.push({i:a-4,x:rl(t,e)},{i:a-2,x:rl(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}return function(n,e){var r=[],i=[];return n=t(n),e=t(e),o(n.translateX,n.translateY,e.translateX,e.translateY,r,i),u(n.rotate,e.rotate,r,i),a(n.skewX,e.skewX,r,i),c(n.scaleX,n.scaleY,e.scaleX,e.scaleY,r,i),n=e=null,function(t){for(var n,e=-1,o=i.length;++e=0&&n._call.call(null,t),n=n._next;--Ml}function _n(){El=(Sl=Cl.now())+Al,Ml=Tl=0;try{vn()}finally{Ml=0,gn(),El=0}}function yn(){var t=Cl.now(),n=t-Sl;n>Nl&&(Al-=n,Sl=t)}function gn(){for(var t,n,e=Vf,r=1/0;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:Vf=n);Wf=t,mn(r)}function mn(t){if(!Ml){Tl&&(Tl=clearTimeout(Tl));var n=t-El;n>24?(t<1/0&&(Tl=setTimeout(_n,n)),kl&&(kl=clearInterval(kl))):(kl||(Sl=El,kl=setInterval(yn,Nl)),Ml=1,zl(_n))}}function xn(t,n){var e=t.__transition;if(!e||!(e=e[n])||e.state>ql)throw new Error("too late");return e}function bn(t,n){var e=t.__transition;if(!e||!(e=e[n])||e.state>Dl)throw new Error("too late");return e}function wn(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("too late");return e}function Mn(t,n,e){function r(c){var s,f,l,h;if(e.state!==Ul)return o();for(s in a)if((h=a[s]).name===e.name){if(h.state===Ol)return Pl(r);h.state===Fl?(h.state=Yl,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete a[s]):+s=0&&(t=t.slice(0,n)),!t||"start"===t})}function Yn(t,n,e){var r,i,o=In(n)?xn:bn;return function(){var u=o(this,t),a=u.on;a!==r&&(i=(r=a).copy()).on(n,e),u.on=i}}function Bn(t){return function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}}function jn(t,n){var e,r,i;return function(){var o=B(this,t),u=(this.style.removeProperty(t),B(this,t));return o===u?null:o===e&&u===r?i:i=n(e=o,r=u)}}function Hn(t){return function(){this.style.removeProperty(t)}}function Xn(t,n,e){var r,i;return function(){var o=B(this,t);return o===e?null:o===r?i:i=n(r=o,e)}}function $n(t,n,e){var r,i,o;return function(){var u=B(this,t),a=e(this);return null==a&&(this.style.removeProperty(t),a=B(this,t)),u===a?null:u===r&&a===i?o:o=n(r=u,i=a)}}function Vn(t,n,e){function r(){var r=this,i=n.apply(r,arguments);return i&&function(n){r.style.setProperty(t,i(n),e)}}return r._value=n,r}function Wn(t){return function(){this.textContent=t}}function Zn(t){return function(){var n=t(this);this.textContent=null==n?"":n}}function Gn(t,n,e,r){this._groups=t,this._parents=n,this._name=e,this._id=r}function Jn(t){return dt().transition(t)}function Qn(){return++$l}function Kn(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function te(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}function ne(t){return(1-Math.cos(Jl*t))/2}function ee(t){return((t*=2)<=1?Math.pow(2,10*t-10):2-Math.pow(2,10-10*t))/2}function re(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}function ie(t){return(t=+t)Math.abs(t[1]-U[1])?b=!0:x=!0),U=t,m=!0,xh(),o()}function o(){var t;switch(y=U[0]-q[0],g=U[1]-q[1],T){case wh:case bh:k&&(y=Math.max(C-a,Math.min(P-p,y)),s=a+y,d=p+y),N&&(g=Math.max(z-l,Math.min(R-v,g)),h=l+g,_=v+g);break;case Mh:k<0?(y=Math.max(C-a,Math.min(P-a,y)),s=a+y,d=p):k>0&&(y=Math.max(C-p,Math.min(P-p,y)),s=a,d=p+y),N<0?(g=Math.max(z-l,Math.min(R-l,g)),h=l+g,_=v):N>0&&(g=Math.max(z-v,Math.min(R-v,g)),h=l,_=v+g);break;case Th:k&&(s=Math.max(C,Math.min(P,a-y*k)),d=Math.max(C,Math.min(P,p+y*k))),N&&(h=Math.max(z,Math.min(R,l-g*N)),_=Math.max(z,Math.min(R,v+g*N)))}d0&&(a=s-y),N<0?v=_-g:N>0&&(l=h-g),T=wh,F.attr("cursor",Eh.selection),o());break;default:return}xh()},!0).on("keyup.brush",function(){switch(t.event.keyCode){case 16:L&&(x=b=L=!1,o());break;case 18:T===Th&&(k<0?p=d:k>0&&(a=s),N<0?v=_:N>0&&(l=h),T=Mh,o());break;case 32:T===wh&&(t.event.altKey?(k&&(p=d-y*k,a=s+y*k),N&&(v=_-g*N,l=h+g*N),T=Th):(k<0?p=d:k>0&&(a=s),N<0?v=_:N>0&&(l=h),T=Mh),F.attr("cursor",Eh[M]),o());break;default:return}xh()},!0).on("mousemove.brush",e,!0).on("mouseup.brush",u,!0);lf(t.event.view)}ue(),jl(w),r.call(w),D.start()}}function a(){var t=this.__brush||{selection:null};return t.extent=s.apply(this,arguments),t.dim=n,t}var c,s=se,f=ce,l=h(e,"start","brush","end"),p=6;return e.move=function(t,e){t.selection?t.on("start.brush",function(){i(this,arguments).beforestart().start()}).on("interrupt.brush end.brush",function(){i(this,arguments).end()}).tween("brush",function(){function t(t){u.selection=1===t&&le(s)?null:f(t),r.call(o),a.brush()}var o=this,u=o.__brush,a=i(o,arguments),c=u.selection,s=n.input("function"==typeof e?e.apply(this,arguments):e,u.extent),f=cl(c,s);return c&&s?t:t(1)}):t.each(function(){var t=this,o=arguments,u=t.__brush,a=n.input("function"==typeof e?e.apply(t,o):e,u.extent),c=i(t,o).beforestart();jl(t),u.selection=null==a||le(a)?null:a,r.call(t),c.start().brush().end()})},o.prototype={beforestart:function(){return 1==++this.active&&(this.state.emitter=this,this.starting=!0),this},start:function(){return this.starting&&(this.starting=!1,this.emit("start")),this},brush:function(){return this.emit("brush"),this},end:function(){return 0==--this.active&&(delete this.state.emitter,this.emit("end")),this},emit:function(t){N(new mh(e,t,n.output(this.state.selection)),l.apply,l,[t,this.that,this.args])}},e.extent=function(t){return arguments.length?(s="function"==typeof t?t:gh([[+t[0][0],+t[0][1]],[+t[1][0],+t[1][1]]]),e):s},e.filter=function(t){return arguments.length?(f="function"==typeof t?t:gh(!!t),e):f},e.handleSize=function(t){return arguments.length?(p=+t,e):p},e.on=function(){var t=l.on.apply(l,arguments);return t===l?e:t},e}function pe(t){return function(n,e){return t(n.source.value+n.target.value,e.source.value+e.target.value)}}function de(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function ve(){return new de}function _e(t){return t.source}function ye(t){return t.target}function ge(t){return t.radius}function me(t){return t.startAngle}function xe(t){return t.endAngle}function be(){}function we(t,n){var e=new be;if(t instanceof be)t.each(function(t,n){e.set(n,t)});else if(Array.isArray(t)){var r,i=-1,o=t.length;if(null==n)for(;++i=(o=(v+y)/2))?v=o:y=o,(f=e>=(u=(_+g)/2))?_=u:g=u,i=p,!(p=p[l=f<<1|s]))return i[l]=d,t;if(a=+t._x.call(null,p.data),c=+t._y.call(null,p.data),n===a&&e===c)return d.next=p,i?i[l]=d:t._root=d,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(s=n>=(o=(v+y)/2))?v=o:y=o,(f=e>=(u=(_+g)/2))?_=u:g=u}while((l=f<<1|s)==(h=(c>=u)<<1|a>=o));return i[h]=p,i[l]=d,t}function Re(t){return t[0]}function Le(t){return t[1]}function qe(t,n,e){var r=new Ue(null==n?Re:n,null==e?Le:e,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Ue(t,n,e,r,i,o){this._x=t,this._y=n,this._x0=e,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function De(t){for(var n={data:t.data},e=n;t=t.next;)e=e.next={data:t.data};return n}function Oe(t){return t.x+t.vx}function Fe(t){return t.y+t.vy}function Ie(t){return t.index}function Ye(t,n){var e=t.get(n);if(!e)throw new Error("missing: "+n);return e}function Be(t){return t.x}function je(t){return t.y}function He(t){return new Xe(t)}function Xe(t){if(!(n=vp.exec(t)))throw new Error("invalid format: "+t);var n,e=n[1]||" ",r=n[2]||">",i=n[3]||"-",o=n[4]||"",u=!!n[5],a=n[6]&&+n[6],c=!!n[7],s=n[8]&&+n[8].slice(1),f=n[9]||"";"n"===f?(c=!0,f="g"):dp[f]||(f=""),(u||"0"===e&&"="===r)&&(u=!0,e="0",r="="),this.fill=e,this.align=r,this.sign=i,this.symbol=o,this.zero=u,this.width=a,this.comma=c,this.precision=s,this.type=f}function $e(n){return _p=mp(n),t.format=_p.format,t.formatPrefix=_p.formatPrefix,_p}function Ve(){this.reset()}function We(t,n,e){var r=t.s=n+e,i=r-n,o=r-i;t.t=n-o+(e-i)}function Ze(t){return t>1?0:t<-1?rd:Math.acos(t)}function Ge(t){return t>1?id:t<-1?-id:Math.asin(t)}function Je(t){return(t=yd(t/2))*t}function Qe(){}function Ke(t,n){t&&wd.hasOwnProperty(t.type)&&wd[t.type](t,n)}function tr(t,n,e){var r,i=-1,o=t.length-e;for(n.lineStart();++i=0?1:-1,i=r*e,o=hd(n),u=yd(n),a=Ep*u,c=Sp*o+a*hd(i),s=a*r*yd(i);Td.add(ld(s,c)),Np=t,Sp=o,Ep=u}function ur(t){return[ld(t[1],t[0]),Ge(t[2])]}function ar(t){var n=t[0],e=t[1],r=hd(e);return[r*hd(n),r*yd(n),yd(e)]}function cr(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function sr(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function fr(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function lr(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function hr(t){var n=md(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}function pr(t,n){Dp.push(Op=[Ap=t,zp=t]),nPp&&(Pp=n)}function dr(t,n){var e=ar([t*cd,n*cd]);if(Up){var r=sr(Up,e),i=sr([r[1],-r[0],0],r);hr(i),i=ur(i);var o,u=t-Rp,a=u>0?1:-1,c=i[0]*ad*a,s=sd(u)>180;s^(a*RpPp&&(Pp=o):(c=(c+360)%360-180,s^(a*RpPp&&(Pp=n))),s?txr(Ap,zp)&&(zp=t):xr(t,zp)>xr(Ap,zp)&&(Ap=t):zp>=Ap?(tzp&&(zp=t)):t>Rp?xr(Ap,t)>xr(Ap,zp)&&(zp=t):xr(t,zp)>xr(Ap,zp)&&(Ap=t)}else Dp.push(Op=[Ap=t,zp=t]);nPp&&(Pp=n),Up=e,Rp=t}function vr(){Ed.point=dr}function _r(){Op[0]=Ap,Op[1]=zp,Ed.point=pr,Up=null}function yr(t,n){if(Up){var e=t-Rp;Sd.add(sd(e)>180?e+(e>0?360:-360):e)}else Lp=t,qp=n;Nd.point(t,n),dr(t,n)}function gr(){Nd.lineStart()}function mr(){yr(Lp,qp),Nd.lineEnd(),sd(Sd)>ed&&(Ap=-(zp=180)),Op[0]=Ap,Op[1]=zp,Up=null}function xr(t,n){return(n-=t)<0?n+360:n}function br(t,n){return t[0]-n[0]}function wr(t,n){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nrd?t-ud:t<-rd?t+ud:t,n]}function Lr(t,n,e){return(t%=ud)?n||e?zd(Ur(t),Dr(n,e)):Ur(t):n||e?Dr(n,e):Rr}function qr(t){return function(n,e){return n+=t,[n>rd?n-ud:n<-rd?n+ud:n,e]}}function Ur(t){var n=qr(t);return n.invert=qr(-t),n}function Dr(t,n){function e(t,n){var e=hd(n),a=hd(t)*e,c=yd(t)*e,s=yd(n),f=s*r+a*i;return[ld(c*o-f*u,a*r-s*i),Ge(f*o+c*u)]}var r=hd(t),i=yd(t),o=hd(n),u=yd(n);return e.invert=function(t,n){var e=hd(n),a=hd(t)*e,c=yd(t)*e,s=yd(n),f=s*o-c*u;return[ld(c*o+s*u,a*r+f*i),Ge(f*r-a*i)]},e}function Or(t,n,e,r,i,o){if(e){var u=hd(n),a=yd(n),c=r*e;null==i?(i=n+r*ud,o=n-c/2):(i=Fr(u,i),o=Fr(u,o),(r>0?io)&&(i+=r*ud));for(var s,f=i;r>0?f>o:f0)do{s.point(0===f||3===f?t:e,f>1?r:n)}while((f=(f+a+4)%4)!==l);else s.point(o[0],o[1])}function u(r,i){return sd(r[0]-t)0?0:3:sd(r[0]-e)0?2:1:sd(r[1]-n)0?1:0:i>0?3:2}function a(t,n){return c(t.x,n.x)}function c(t,n){var e=u(t,1),r=u(n,1);return e!==r?e-r:0===e?n[1]-t[1]:1===e?t[0]-n[0]:2===e?t[1]-n[1]:n[0]-t[0]}return function(u){function c(t,n){i(t,n)&&w.point(t,n)}function s(){for(var n=0,e=0,i=h.length;er&&(l-o)*(r-u)>(p-u)*(t-o)&&++n:p<=r&&(l-o)*(r-u)<(p-u)*(t-o)&&--n;return n}function f(o,u){var a=i(o,u);if(h&&p.push([o,u]),x)d=o,v=u,_=a,x=!1,a&&(w.lineStart(),w.point(o,u));else if(a&&m)w.point(o,u);else{var c=[y=Math.max(Zd,Math.min(Wd,y)),g=Math.max(Zd,Math.min(Wd,g))],s=[o=Math.max(Zd,Math.min(Wd,o)),u=Math.max(Zd,Math.min(Wd,u))];Xd(c,s,t,n,e,r)?(m||(w.lineStart(),w.point(c[0],c[1])),w.point(s[0],s[1]),a||w.lineEnd(),b=!1):a&&(w.lineStart(),w.point(o,u),b=!1)}y=o,g=u,m=a}var l,h,p,d,v,_,y,g,m,x,b,w=u,M=Hd(),T={point:c,lineStart:function(){T.point=f,h&&h.push(p=[]),x=!0,m=!1,y=g=NaN},lineEnd:function(){l&&(f(d,v),_&&m&&M.rejoin(),l.push(M.result())),T.point=c,m&&w.lineEnd()},polygonStart:function(){w=M,l=[],h=[],b=!0},polygonEnd:function(){var t=s(),n=b&&t,e=(l=Cs(l)).length;(n||e)&&(u.polygonStart(),n&&(u.lineStart(),o(null,null,1,u),u.lineEnd()),e&&Vd(l,a,t,o,u),u.polygonEnd()),w=u,l=h=p=null}};return T}}function jr(){Kd.point=Kd.lineEnd=Qe}function Hr(t,n){Pd=t*=cd,Rd=yd(n*=cd),Ld=hd(n),Kd.point=Xr}function Xr(t,n){t*=cd;var e=yd(n*=cd),r=hd(n),i=sd(t-Pd),o=hd(i),u=r*yd(i),a=Ld*e-Rd*r*o,c=Rd*e+Ld*r*o;Qd.add(ld(md(u*u+a*a),c)),Pd=t,Rd=e,Ld=r}function $r(t,n){return!(!t||!ov.hasOwnProperty(t.type))&&ov[t.type](t,n)}function Vr(t,n){return 0===rv(t,n)}function Wr(t,n){var e=rv(t[0],t[1]);return rv(t[0],n)+rv(n,t[1])<=e+ed}function Zr(t,n){return!!Jd(t.map(Gr),Jr(n))}function Gr(t){return(t=t.map(Jr)).pop(),t}function Jr(t){return[t[0]*cd,t[1]*cd]}function Qr(t,n,e){var r=Ms(t,n-ed,e).concat(n);return function(t){return r.map(function(n){return[t,n]})}}function Kr(t,n,e){var r=Ms(t,n-ed,e).concat(n);return function(t){return r.map(function(n){return[n,t]})}}function ti(){function t(){return{type:"MultiLineString",coordinates:n()}}function n(){return Ms(pd(o/_)*_,i,_).map(h).concat(Ms(pd(s/y)*y,c,y).map(p)).concat(Ms(pd(r/d)*d,e,d).filter(function(t){return sd(t%_)>ed}).map(f)).concat(Ms(pd(a/v)*v,u,v).filter(function(t){return sd(t%y)>ed}).map(l))}var e,r,i,o,u,a,c,s,f,l,h,p,d=10,v=d,_=90,y=360,g=2.5;return t.lines=function(){return n().map(function(t){return{type:"LineString",coordinates:t}})},t.outline=function(){return{type:"Polygon",coordinates:[h(o).concat(p(c).slice(1),h(i).reverse().slice(1),p(s).reverse().slice(1))]}},t.extent=function(n){return arguments.length?t.extentMajor(n).extentMinor(n):t.extentMinor()},t.extentMajor=function(n){return arguments.length?(o=+n[0][0],i=+n[1][0],s=+n[0][1],c=+n[1][1],o>i&&(n=o,o=i,i=n),s>c&&(n=s,s=c,c=n),t.precision(g)):[[o,s],[i,c]]},t.extentMinor=function(n){return arguments.length?(r=+n[0][0],e=+n[1][0],a=+n[0][1],u=+n[1][1],r>e&&(n=r,r=e,e=n),a>u&&(n=a,a=u,u=n),t.precision(g)):[[r,a],[e,u]]},t.step=function(n){return arguments.length?t.stepMajor(n).stepMinor(n):t.stepMinor()},t.stepMajor=function(n){return arguments.length?(_=+n[0],y=+n[1],t):[_,y]},t.stepMinor=function(n){return arguments.length?(d=+n[0],v=+n[1],t):[d,v]},t.precision=function(n){return arguments.length?(g=+n,f=Qr(a,u,90),l=Kr(r,e,g),h=Qr(s,c,90),p=Kr(o,i,g),t):g},t.extentMajor([[-180,-90+ed],[180,90-ed]]).extentMinor([[-180,-80-ed],[180,80+ed]])}function ni(){sv.point=ei}function ei(t,n){sv.point=ri,qd=Dd=t,Ud=Od=n}function ri(t,n){cv.add(Od*t-Dd*n),Dd=t,Od=n}function ii(){ri(qd,Ud)}function oi(t,n){vv+=t,_v+=n,++yv}function ui(){Tv.point=ai}function ai(t,n){Tv.point=ci,oi(Yd=t,Bd=n)}function ci(t,n){var e=t-Yd,r=n-Bd,i=md(e*e+r*r);gv+=i*(Yd+t)/2,mv+=i*(Bd+n)/2,xv+=i,oi(Yd=t,Bd=n)}function si(){Tv.point=oi}function fi(){Tv.point=hi}function li(){pi(Fd,Id)}function hi(t,n){Tv.point=pi,oi(Fd=Yd=t,Id=Bd=n)}function pi(t,n){var e=t-Yd,r=n-Bd,i=md(e*e+r*r);gv+=i*(Yd+t)/2,mv+=i*(Bd+n)/2,xv+=i,bv+=(i=Bd*t-Yd*n)*(Yd+t),wv+=i*(Bd+n),Mv+=3*i,oi(Yd=t,Bd=n)}function di(t){this._context=t}function vi(t,n){zv.point=_i,Nv=Ev=t,Sv=Av=n}function _i(t,n){Ev-=t,Av-=n,Cv.add(md(Ev*Ev+Av*Av)),Ev=t,Av=n}function yi(){this._string=[]}function gi(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function mi(t){return t.length>1}function xi(t,n){return((t=t.x)[0]<0?t[1]-id-ed:id-t[1])-((n=n.x)[0]<0?n[1]-id-ed:id-n[1])}function bi(t,n,e,r){var i,o,u=yd(t-e);return sd(u)>ed?fd((yd(n)*(o=hd(r))*yd(e)-yd(r)*(i=hd(n))*yd(t))/(i*o*u)):(n+r)/2}function wi(t){return function(n){var e=new Mi;for(var r in t)e[r]=t[r];return e.stream=n,e}}function Mi(){}function Ti(t,n,e){var r=n[1][0]-n[0][0],i=n[1][1]-n[0][1],o=t.clipExtent&&t.clipExtent();t.scale(150).translate([0,0]),null!=o&&t.clipExtent(null),Md(e,t.stream(dv));var u=dv.result(),a=Math.min(r/(u[1][0]-u[0][0]),i/(u[1][1]-u[0][1])),c=+n[0][0]+(r-a*(u[1][0]+u[0][0]))/2,s=+n[0][1]+(i-a*(u[1][1]+u[0][1]))/2;return null!=o&&t.clipExtent(o),t.scale(150*a).translate([c,s])}function ki(t,n,e){return Ti(t,[[0,0],n],e)}function Ni(t){return wi({point:function(n,e){n=t(n,e),this.stream.point(n[0],n[1])}})}function Si(t,n){function e(r,i,o,u,a,c,s,f,l,h,p,d,v,_){var y=s-r,g=f-i,m=y*y+g*g;if(m>4*n&&v--){var x=u+h,b=a+p,w=c+d,M=md(x*x+b*b+w*w),T=Ge(w/=M),k=sd(sd(w)-1)n||sd((y*A+g*C)/m-.5)>.3||u*h+a*p+c*d2?t[2]%360*cd:0,i()):[b*ad,w*ad,M*ad]},n.precision=function(t){return arguments.length?(A=Dv(r,E=t*t),o()):md(E)},n.fitExtent=function(t,e){return Ti(n,t,e)},n.fitSize=function(t,e){return ki(n,t,e)},function(){return u=t.apply(this,arguments),n.invert=u.invert&&e,i()}}function Ci(t){var n=0,e=rd/3,r=Ai(t),i=r(n,e);return i.parallels=function(t){return arguments.length?r(n=t[0]*cd,e=t[1]*cd):[n*ad,e*ad]},i}function zi(t){function n(t,n){return[t*e,yd(n)/e]}var e=hd(t);return n.invert=function(t,n){return[t/e,Ge(n*e)]},n}function Pi(t,n){function e(t,n){var e=md(o-2*i*yd(n))/i;return[e*yd(t*=i),u-e*hd(t)]}var r=yd(t),i=(r+yd(n))/2;if(sd(i)0?n<-id+ed&&(n=-id+ed):n>id-ed&&(n=id-ed);var e=o/_d(Oi(n),i);return[e*yd(i*t),o-e*hd(i*t)]}var r=hd(t),i=t===n?yd(t):vd(r/hd(n))/vd(Oi(n)/Oi(t)),o=r*_d(Oi(t),i)/i;return i?(e.invert=function(t,n){var e=o-n,r=gd(i)*md(t*t+e*e);return[ld(t,sd(e))/i*gd(e),2*fd(_d(o/r,1/i))-id]},e):Ui}function Ii(t,n){return[t,n]}function Yi(t,n){function e(t,n){var e=o-n,r=i*t;return[e*yd(r),o-e*hd(r)]}var r=hd(t),i=t===n?yd(t):(r-hd(n))/(n-t),o=r/i+t;return sd(i)=0;)n+=e[r].value;else n=1;t.value=n}function no(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;for(t=e.pop(),n=r.pop();t===n;)i=t,t=e.pop(),n=r.pop();return i}function eo(t,n){var e,r,i,o,u,a=new uo(t),c=+t.value&&(a.value=t.value),s=[a];for(null==n&&(n=ro);e=s.pop();)if(c&&(e.value=+e.data.value),(i=n(e.data))&&(u=i.length))for(e.children=new Array(u),o=u-1;o>=0;--o)s.push(r=e.children[o]=new uo(i[o])),r.parent=e,r.depth=e.depth+1;return a.eachBefore(oo)}function ro(t){return t.children}function io(t){t.data=t.data.data}function oo(t){var n=0;do{t.height=n}while((t=t.parent)&&t.height<++n)}function uo(t){this.data=t,this.depth=this.height=0,this.parent=null}function ao(t){for(var n,e,r=t.length;r;)e=Math.random()*r--|0,n=t[r],t[r]=t[e],t[e]=n;return t}function co(t,n){var e,r;if(lo(n,t))return[n];for(e=0;e0&&e*e>r*r+i*i}function lo(t,n){for(var e=0;ee*e+r*r}function mo(t){var n=t._,e=t.next._,r=n.r+e.r,i=(n.x*e.r+e.x*n.r)/r,o=(n.y*e.r+e.y*n.r)/r;return i*i+o*o}function xo(t){this._=t,this.next=null,this.previous=null}function bo(t){if(!(i=t.length))return 0;var n,e,r,i,o,u,a,c,s,f,l;if(n=t[0],n.x=0,n.y=0,!(i>1))return n.r;if(e=t[1],n.x=-e.r,e.x=n.r,e.y=0,!(i>2))return n.r+e.r;yo(e,n,r=t[2]),n=new xo(n),e=new xo(e),r=new xo(r),n.next=r.previous=e,e.next=n.previous=r,r.next=e.previous=n;t:for(a=3;a=0;)(n=i[o]).z+=e,n.m+=e,e+=n.s+(r+=n.c)}function Uo(t,n,e){return t.a.parent===n.parent?t.a:e}function Do(t,n){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=n}function Oo(t){for(var n,e,r,i,o,u=new Do(t,0),a=[u];n=a.pop();)if(r=n._.children)for(n.children=new Array(o=r.length),i=o-1;i>=0;--i)a.push(e=n.children[i]=new Do(r[i],i)),e.parent=n;return(u.parent=new Do(null,0)).children=[u],u}function Fo(t,n,e,r,i,o){for(var u,a,c,s,f,l,h,p,d,v,_,y=[],g=n.children,m=0,x=0,b=g.length,w=n.value;mh&&(h=a),_=f*f*v,(p=Math.max(h/_,_/l))>d){f-=a;break}d=p}y.push(u={value:f,dice:c1&&n_(t[e[r-2]],t[e[r-1]],t[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function Bo(t){this._size=t,this._call=this._error=null,this._tasks=[],this._data=[],this._waiting=this._active=this._ended=this._start=0}function jo(t){if(!t._start)try{Ho(t)}catch(n){if(t._tasks[t._ended+t._active-1])$o(t,n);else if(!t._data)throw n}}function Ho(t){for(;t._start=t._waiting&&t._active=0;)if((e=t._tasks[r])&&(t._tasks[r]=null,e.abort))try{e.abort()}catch(n){}t._active=NaN,Vo(t)}function Vo(t){if(!t._active&&t._call){var n=t._data;t._data=void 0,t._call(t._error,n)}}function Wo(t){if(null==t)t=1/0;else if(!((t=+t)>=1))throw new Error("invalid concurrency");return new Bo(t)}function Zo(t){return function(n,e){t(null==n?e:null)}}function Go(t){var n=t.responseType;return n&&"text"!==n?t.response:t.responseText}function Jo(t,n){return function(e){return t(e.responseText,n)}}function Qo(t){function n(n){var o=n+"",u=e.get(o);if(!u){if(i!==M_)return i;e.set(o,u=r.push(n))}return t[(u-1)%t.length]}var e=we(),r=[],i=M_;return t=null==t?[]:w_.call(t),n.domain=function(t){if(!arguments.length)return r.slice();r=[],e=we();for(var i,o,u=-1,a=t.length;++u=e?1:r(t)}}}function ru(t){return function(n,e){var r=t(n=+n,e=+e);return function(t){return t<=0?n:t>=1?e:r(t)}}}function iu(t,n,e,r){var i=t[0],o=t[1],u=n[0],a=n[1];return o2?ou:iu,o=u=null,r}function r(n){return(o||(o=i(a,c,f?eu(t):t,s)))(+n)}var i,o,u,a=N_,c=N_,s=cl,f=!1;return r.invert=function(t){return(u||(u=i(c,a,nu,f?ru(n):n)))(+t)},r.domain=function(t){return arguments.length?(a=b_.call(t,k_),e()):a.slice()},r.range=function(t){return arguments.length?(c=w_.call(t),e()):c.slice()},r.rangeRound=function(t){return c=w_.call(t),s=sl,e()},r.clamp=function(t){return arguments.length?(f=!!t,e()):f},r.interpolate=function(t){return arguments.length?(s=t,e()):s},e()}function cu(t){var n=t.domain;return t.ticks=function(t){var e=n();return Ss(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){return S_(n(),t,e)},t.nice=function(e){null==e&&(e=10);var i,o=n(),u=0,a=o.length-1,c=o[u],s=o[a];return s0?i=r(c=Math.floor(c/i)*i,s=Math.ceil(s/i)*i,e):i<0&&(i=r(c=Math.ceil(c*i)/i,s=Math.floor(s*i)/i,e)),i>0?(o[u]=Math.floor(c/i)*i,o[a]=Math.ceil(s/i)*i,n(o)):i<0&&(o[u]=Math.ceil(c*i)/i,o[a]=Math.floor(s*i)/i,n(o)),t},t}function su(){var t=au(nu,rl);return t.copy=function(){return uu(t,su())},cu(t)}function fu(){function t(t){return+t}var n=[0,1];return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=b_.call(e,k_),t):n.slice()},t.copy=function(){return fu().domain(n)},cu(t)}function lu(t,n){return(n=Math.log(n/t))?function(e){return Math.log(e/t)/n}:T_(n)}function hu(t,n){return t<0?function(e){return-Math.pow(-n,e)*Math.pow(-t,1-e)}:function(e){return Math.pow(n,e)*Math.pow(t,1-e)}}function pu(t){return isFinite(t)?+("1e"+t):t<0?0:t}function du(t){return 10===t?pu:t===Math.E?Math.exp:function(n){return Math.pow(t,n)}}function vu(t){return t===Math.E?Math.log:10===t&&Math.log10||2===t&&Math.log2||(t=Math.log(t),function(n){return Math.log(n)/t})}function _u(t){return function(n){return-t(-n)}}function yu(){function n(){return o=vu(i),u=du(i),r()[0]<0&&(o=_u(o),u=_u(u)),e}var e=au(lu,hu).domain([1,10]),r=e.domain,i=10,o=vu(10),u=du(10);return e.base=function(t){return arguments.length?(i=+t,n()):i},e.domain=function(t){return arguments.length?(r(t),n()):r()},e.ticks=function(t){var n,e=r(),a=e[0],c=e[e.length-1];(n=c0){for(;hc)break;v.push(l)}}else for(;h=1;--f)if(!((l=s*f)c)break;v.push(l)}}else v=Ss(h,p,Math.min(p-h,d)).map(u);return n?v.reverse():v},e.tickFormat=function(n,r){if(null==r&&(r=10===i?".0e":","),"function"!=typeof r&&(r=t.format(r)),n===1/0)return r;null==n&&(n=10);var a=Math.max(1,i*n/e.ticks().length);return function(t){var n=t/u(Math.round(o(t)));return n*i0?i[n-1]:e[0],n=i?[o[i-1],r]:[o[n-1],o[n]]},t.copy=function(){return bu().domain([e,r]).range(u)},cu(t)}function wu(){function t(t){if(t<=t)return e[hs(n,t,0,r)]}var n=[.5],e=[0,1],r=1;return t.domain=function(i){return arguments.length?(n=w_.call(i),r=Math.min(n.length,e.length-1),t):n.slice()},t.range=function(i){return arguments.length?(e=w_.call(i),r=Math.min(n.length,e.length-1),t):e.slice()},t.invertExtent=function(t){var r=e.indexOf(t);return[n[r-1],n[r]]},t.copy=function(){return wu().domain(n).range(e)},t}function Mu(t,n,e,r){function i(n){return t(n=new Date(+n)),n}return i.floor=i,i.ceil=function(e){return t(e=new Date(e-1)),n(e,1),t(e),e},i.round=function(t){var n=i(t),e=i.ceil(t);return t-n0))return u;do{u.push(new Date(+e))}while(n(e,o),t(e),e=n)for(;t(n),!e(n);)n.setTime(n-1)},function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););})},e&&(i.count=function(n,r){return A_.setTime(+n),C_.setTime(+r),t(A_),t(C_),Math.floor(e(A_,C_))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(n){return r(n)%t==0}:function(n){return i.count(0,n)%t==0}):i:null}),i}function Tu(t){return Mu(function(n){n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+7*n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*R_)/L_})}function ku(t){return Mu(function(n){n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+7*n)},function(t,n){return(n-t)/L_})}function Nu(t){if(0<=t.y&&t.y<100){var n=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return n.setFullYear(t.y),n}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function Su(t){if(0<=t.y&&t.y<100){var n=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return n.setUTCFullYear(t.y),n}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Eu(t){return{y:t,m:0,d:1,H:0,M:0,S:0,L:0}}function Au(t){function n(t,n){return function(e){var r,i,o,u=[],a=-1,c=0,s=t.length;for(e instanceof Date||(e=new Date(+e));++a=c)return-1;if(37===(i=n.charCodeAt(u++))){if(i=n.charAt(u++),!(o=T[i in Py?n.charAt(u++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}var i=t.dateTime,o=t.date,u=t.time,a=t.periods,c=t.days,s=t.shortDays,f=t.months,l=t.shortMonths,h=Pu(a),p=Ru(a),d=Pu(c),v=Ru(c),_=Pu(s),y=Ru(s),g=Pu(f),m=Ru(f),x=Pu(l),b=Ru(l),w={a:function(t){return s[t.getDay()]},A:function(t){return c[t.getDay()]},b:function(t){return l[t.getMonth()]},B:function(t){return f[t.getMonth()]},c:null,d:Wu,e:Wu,H:Zu,I:Gu,j:Ju,L:Qu,m:Ku,M:ta,p:function(t){return a[+(t.getHours()>=12)]},S:na,U:ea,w:ra,W:ia,x:null,X:null,y:oa,Y:ua,Z:aa,"%":wa},M={a:function(t){return s[t.getUTCDay()]},A:function(t){return c[t.getUTCDay()]},b:function(t){return l[t.getUTCMonth()]},B:function(t){return f[t.getUTCMonth()]},c:null,d:ca,e:ca,H:sa,I:fa,j:la,L:ha,m:pa,M:da,p:function(t){return a[+(t.getUTCHours()>=12)]},S:va,U:_a,w:ya,W:ga,x:null,X:null,y:ma,Y:xa,Z:ba,"%":wa},T={a:function(t,n,e){var r=_.exec(n.slice(e));return r?(t.w=y[r[0].toLowerCase()],e+r[0].length):-1},A:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.w=v[r[0].toLowerCase()],e+r[0].length):-1},b:function(t,n,e){var r=x.exec(n.slice(e));return r?(t.m=b[r[0].toLowerCase()],e+r[0].length):-1},B:function(t,n,e){var r=g.exec(n.slice(e));return r?(t.m=m[r[0].toLowerCase()],e+r[0].length):-1},c:function(t,n,e){return r(t,i,n,e)},d:Yu,e:Yu,H:ju,I:ju,j:Bu,L:$u,m:Iu,M:Hu,p:function(t,n,e){var r=h.exec(n.slice(e));return r?(t.p=p[r[0].toLowerCase()],e+r[0].length):-1},S:Xu,U:qu,w:Lu,W:Uu,x:function(t,n,e){return r(t,o,n,e)},X:function(t,n,e){return r(t,u,n,e)},y:Ou,Y:Du,Z:Fu,"%":Vu};return w.x=n(o,w),w.X=n(u,w),w.c=n(i,w),M.x=n(o,M),M.X=n(u,M),M.c=n(i,M),{format:function(t){var e=n(t+="",w);return e.toString=function(){return t},e},parse:function(t){var n=e(t+="",Nu);return n.toString=function(){return t},n},utcFormat:function(t){var e=n(t+="",M);return e.toString=function(){return t},e},utcParse:function(t){var n=e(t,Su);return n.toString=function(){return t},n}}}function Cu(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o68?1900:2e3),e+r[0].length):-1}function Fu(t,n,e){var r=/^(Z)|([+-]\d\d)(?:\:?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function Iu(t,n,e){var r=Ry.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function Yu(t,n,e){var r=Ry.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function Bu(t,n,e){var r=Ry.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function ju(t,n,e){var r=Ry.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function Hu(t,n,e){var r=Ry.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function Xu(t,n,e){var r=Ry.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function $u(t,n,e){var r=Ry.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function Vu(t,n,e){var r=Ly.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function Wu(t,n){return Cu(t.getDate(),n,2)}function Zu(t,n){return Cu(t.getHours(),n,2)}function Gu(t,n){return Cu(t.getHours()%12||12,n,2)}function Ju(t,n){return Cu(1+Y_.count(oy(t),t),n,3)}function Qu(t,n){return Cu(t.getMilliseconds(),n,3)}function Ku(t,n){return Cu(t.getMonth()+1,n,2)}function ta(t,n){return Cu(t.getMinutes(),n,2)}function na(t,n){return Cu(t.getSeconds(),n,2)}function ea(t,n){return Cu(j_.count(oy(t),t),n,2)}function ra(t){return t.getDay()}function ia(t,n){return Cu(H_.count(oy(t),t),n,2)}function oa(t,n){return Cu(t.getFullYear()%100,n,2)}function ua(t,n){return Cu(t.getFullYear()%1e4,n,4)}function aa(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+Cu(n/60|0,"0",2)+Cu(n%60,"0",2)}function ca(t,n){return Cu(t.getUTCDate(),n,2)}function sa(t,n){return Cu(t.getUTCHours(),n,2)}function fa(t,n){return Cu(t.getUTCHours()%12||12,n,2)}function la(t,n){return Cu(1+ly.count(Ay(t),t),n,3)}function ha(t,n){return Cu(t.getUTCMilliseconds(),n,3)}function pa(t,n){return Cu(t.getUTCMonth()+1,n,2)}function da(t,n){return Cu(t.getUTCMinutes(),n,2)}function va(t,n){return Cu(t.getUTCSeconds(),n,2)}function _a(t,n){return Cu(py.count(Ay(t),t),n,2)}function ya(t){return t.getUTCDay()}function ga(t,n){return Cu(dy.count(Ay(t),t),n,2)}function ma(t,n){return Cu(t.getUTCFullYear()%100,n,2)}function xa(t,n){return Cu(t.getUTCFullYear()%1e4,n,4)}function ba(){return"+0000"}function wa(){return"%"}function Ma(n){return Cy=Au(n),t.timeFormat=Cy.format,t.timeParse=Cy.parse,t.utcFormat=Cy.utcFormat,t.utcParse=Cy.utcParse,Cy}function Ta(t){return new Date(t)}function ka(t){return t instanceof Date?+t:+new Date(+t)}function Na(t,n,e,r,o,u,a,c,s){function f(i){return(a(i)1?0:t<-1?pg:Math.acos(t)}function Ca(t){return t>=1?dg:t<=-1?-dg:Math.asin(t)}function za(t){return t.innerRadius}function Pa(t){return t.outerRadius}function Ra(t){return t.startAngle}function La(t){return t.endAngle}function qa(t){return t&&t.padAngle}function Ua(t,n,e,r,i,o,u,a){var c=e-t,s=r-n,f=u-i,l=a-o,h=(f*(n-o)-l*(t-i))/(l*c-f*s);return[t+h*c,n+h*s]}function Da(t,n,e,r,i,o,u){var a=t-e,c=n-r,s=(u?o:-o)/lg(a*a+c*c),f=s*c,l=-s*a,h=t+f,p=n+l,d=e+f,v=r+l,_=(h+d)/2,y=(p+v)/2,g=d-h,m=v-p,x=g*g+m*m,b=i-o,w=h*v-d*p,M=(m<0?-1:1)*lg(cg(0,b*b*x-w*w)),T=(w*m-g*M)/x,k=(-w*g-m*M)/x,N=(w*m+g*M)/x,S=(-w*g+m*M)/x,E=T-_,A=k-y,C=N-_,z=S-y;return E*E+A*A>C*C+z*z&&(T=N,k=S),{cx:T,cy:k,x01:-f,y01:-l,x11:T*(i/b-1),y11:k*(i/b-1)}}function Oa(t){this._context=t}function Fa(t){return t[0]}function Ia(t){return t[1]}function Ya(t){this._curve=t}function Ba(t){function n(n){return new Ya(t(n))}return n._curve=t,n}function ja(t){var n=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?n(Ba(t)):n()._curve},t}function Ha(t){return t.source}function Xa(t){return t.target}function $a(t){function n(){var n,a=kg.call(arguments),c=e.apply(this,a),s=r.apply(this,a);if(u||(u=n=ve()),t(u,+i.apply(this,(a[0]=c,a)),+o.apply(this,a),+i.apply(this,(a[0]=s,a)),+o.apply(this,a)),n)return u=null,n+""||null}var e=Ha,r=Xa,i=Fa,o=Ia,u=null;return n.source=function(t){return arguments.length?(e=t,n):e},n.target=function(t){return arguments.length?(r=t,n):r},n.x=function(t){return arguments.length?(i="function"==typeof t?t:ig(+t),n):i},n.y=function(t){return arguments.length?(o="function"==typeof t?t:ig(+t),n):o},n.context=function(t){return arguments.length?(u=null==t?null:t,n):u},n}function Va(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n=(n+r)/2,e,n,i,r,i)}function Wa(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n,e=(e+i)/2,r,e,r,i)}function Za(t,n,e,r,i){var o=Tg(n,e),u=Tg(n,e=(e+i)/2),a=Tg(r,e),c=Tg(r,i);t.moveTo(o[0],o[1]),t.bezierCurveTo(u[0],u[1],a[0],a[1],c[0],c[1])}function Ga(t,n,e){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+n)/6,(t._y0+4*t._y1+e)/6)}function Ja(t){this._context=t}function Qa(t){this._context=t}function Ka(t){this._context=t}function tc(t,n){this._basis=new Ja(t),this._beta=n}function nc(t,n,e){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-n),t._y2+t._k*(t._y1-e),t._x2,t._y2)}function ec(t,n){this._context=t,this._k=(1-n)/6}function rc(t,n){this._context=t,this._k=(1-n)/6}function ic(t,n){this._context=t,this._k=(1-n)/6}function oc(t,n,e){var r=t._x1,i=t._y1,o=t._x2,u=t._y2;if(t._l01_a>hg){var a=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*a-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*a-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>hg){var s=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,f=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*s+t._x1*t._l23_2a-n*t._l12_2a)/f,u=(u*s+t._y1*t._l23_2a-e*t._l12_2a)/f}t._context.bezierCurveTo(r,i,o,u,t._x2,t._y2)}function uc(t,n){this._context=t,this._alpha=n}function ac(t,n){this._context=t,this._alpha=n}function cc(t,n){this._context=t,this._alpha=n}function sc(t){this._context=t}function fc(t){return t<0?-1:1}function lc(t,n,e){var r=t._x1-t._x0,i=n-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),u=(e-t._y1)/(i||r<0&&-0),a=(o*i+u*r)/(r+i);return(fc(o)+fc(u))*Math.min(Math.abs(o),Math.abs(u),.5*Math.abs(a))||0}function hc(t,n){var e=t._x1-t._x0;return e?(3*(t._y1-t._y0)/e-n)/2:n}function pc(t,n,e){var r=t._x0,i=t._y0,o=t._x1,u=t._y1,a=(o-r)/3;t._context.bezierCurveTo(r+a,i+a*n,o-a,u-a*e,o,u)}function dc(t){this._context=t}function vc(t){this._context=new _c(t)}function _c(t){this._context=t}function yc(t){this._context=t}function gc(t){var n,e,r=t.length-1,i=new Array(r),o=new Array(r),u=new Array(r);for(i[0]=0,o[0]=2,u[0]=t[0]+2*t[1],n=1;n=0;--n)i[n]=(u[n]-i[n+1])/o[n];for(o[r-1]=(t[r]+i[r-1])/2,n=0;n0)){if(o/=h,h<0){if(o0){if(o>l)return;o>f&&(f=o)}if(o=r-c,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>f&&(f=o)}else if(h>0){if(o0)){if(o/=p,p<0){if(o0){if(o>l)return;o>f&&(f=o)}if(o=i-s,p||!(o<0)){if(o/=p,p<0){if(o>l)return;o>f&&(f=o)}else if(p>0){if(o0||l<1)||(f>0&&(t[0]=[c+f*h,s+f*p]),l<1&&(t[1]=[c+l*h,s+l*p]),!0)}}}}}function Rc(t,n,e,r,i){var o=t[1];if(o)return!0;var u,a,c=t[0],s=t.left,f=t.right,l=s[0],h=s[1],p=f[0],d=f[1],v=(l+p)/2,_=(h+d)/2;if(d===h){if(v=r)return;if(l>p){if(c){if(c[1]>=i)return}else c=[v,e];o=[v,i]}else{if(c){if(c[1]1)if(l>p){if(c){if(c[1]>=i)return}else c=[(e-a)/u,e];o=[(i-a)/u,i]}else{if(c){if(c[1]=r)return}else c=[n,u*n+a];o=[r,u*r+a]}else{if(c){if(c[0]sm||Math.abs(i[0][1]-i[1][1])>sm)||delete um[o]}function qc(t){return im[t.index]={site:t,halfedges:[]}}function Uc(t,n){var e=t.site,r=n.left,i=n.right;return e===i&&(i=r,r=e),i?Math.atan2(i[1]-r[1],i[0]-r[0]):(e===r?(r=n[1],i=n[0]):(r=n[0],i=n[1]),Math.atan2(r[0]-i[0],i[1]-r[1]))}function Dc(t,n){return n[+(n.left!==t.site)]}function Oc(t,n){return n[+(n.left===t.site)]}function Fc(){for(var t,n,e,r,i=0,o=im.length;ism||Math.abs(v-h)>sm)&&(c.splice(a,0,um.push(Cc(u,p,Math.abs(d-t)sm?[t,Math.abs(l-t)sm?[Math.abs(h-r)sm?[e,Math.abs(l-e)sm?[Math.abs(h-n)=-fm)){var p=c*c+s*s,d=f*f+l*l,v=(l*p-s*d)/h,_=(c*d-f*p)/h,y=am.pop()||new Yc;y.arc=t,y.site=i,y.x=v+u,y.y=(y.cy=_+a)+Math.sqrt(v*v+_*_),t.circle=y;for(var g=null,m=om._;m;)if(y.ysm)a=a.L;else{if(!((i=o-Gc(a,u))>sm)){r>-sm?(n=a.P,e=a):i>-sm?(n=a,e=a.N):n=e=a;break}if(!a.R){n=a;break}a=a.R}qc(t);var c=Xc(t);if(rm.insert(n,c),n||e){if(n===e)return jc(n),e=Xc(n.site),rm.insert(c,e),c.edge=e.edge=Ac(n.site,c.site),Bc(n),void Bc(e);if(e){jc(n),jc(e);var s=n.site,f=s[0],l=s[1],h=t[0]-f,p=t[1]-l,d=e.site,v=d[0]-f,_=d[1]-l,y=2*(h*_-p*v),g=h*h+p*p,m=v*v+_*_,x=[(_*g-p*m)/y+f,(h*m-v*g)/y+l];zc(e.edge,s,d,x),c.edge=Ac(s,t,null,x),e.edge=Ac(t,d,null,x),Bc(n),Bc(e)}else c.edge=Ac(n.site,c.site)}}function Zc(t,n){var e=t.site,r=e[0],i=e[1],o=i-n;if(!o)return r;var u=t.P;if(!u)return-1/0;var a=(e=u.site)[0],c=e[1],s=c-n;if(!s)return a;var f=a-r,l=1/o-1/s,h=f/s;return l?(-h+Math.sqrt(h*h-2*l*(f*f/(-2*s)-c+s/2+i-o/2)))/l+r:(r+a)/2}function Gc(t,n){var e=t.N;if(e)return Zc(e,n);var r=t.site;return r[1]===n?r[0]:1/0}function Jc(t,n,e){return(t[0]-e[0])*(n[1]-t[1])-(t[0]-n[0])*(e[1]-t[1])}function Qc(t,n){return n[1]-t[1]||n[0]-t[0]}function Kc(t,n){var e,r,i,o=t.sort(Qc).pop();for(um=[],im=new Array(t.length),rm=new Tc,om=new Tc;;)if(i=em,o&&(!i||o[1]n?1:t>=n?0:NaN},fs=function(t){return 1===t.length&&(t=n(t)),{left:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)<0?r=o+1:i=o}return r},right:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)>0?i=o:r=o+1}return r}}},ls=fs(ss),hs=ls.right,ps=ls.left,ds=function(t){return null===t?NaN:+t},vs=function(t,n){var e,r,i=t.length,o=0,u=-1,a=0,c=0;if(null==n)for(;++u1)return c/(o-1)},_s=function(t,n){var e=vs(t,n);return e?Math.sqrt(e):e},ys=function(t,n){var e,r,i,o=t.length,u=-1;if(null==n){for(;++u=e)for(r=i=e;++ue&&(r=e),i=e)for(r=i=e;++ue&&(r=e),i0)for(t=Math.ceil(t/u),n=Math.floor(n/u),o=new Array(i=Math.ceil(n-t+1));++c=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,o=Math.floor(i),u=+e(t[o],o,t);return u+(+e(t[o+1],o+1,t)-u)*(i-o)}},Cs=function(t){for(var n,e,r,i=t.length,o=-1,u=0;++o=0;)for(n=(r=t[i]).length;--n>=0;)e[--u]=r[n];return e},zs=function(t,n){var e,r,i=t.length,o=-1;if(null==n){for(;++o=e)for(r=e;++oe&&(r=e)}else for(;++o=e)for(r=e;++oe&&(r=e);return r},Ps=function(t){if(!(i=t.length))return[];for(var n=-1,e=zs(t,o),r=new Array(e);++n0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),Bs.hasOwnProperty(n)?{space:Bs[n],local:t}:t},Hs=function(t){var n=js(t);return(n.local?g:y)(n)},Xs=0;x.prototype=m.prototype={constructor:x,get:function(t){for(var n=this._;!(n in t);)if(!(t=t.parentNode))return;return t[n]},set:function(t,n){return t[this._]=n},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};var $s=function(t){return function(){return this.matches(t)}};if("undefined"!=typeof document){var Vs=document.documentElement;if(!Vs.matches){var Ws=Vs.webkitMatchesSelector||Vs.msMatchesSelector||Vs.mozMatchesSelector||Vs.oMatchesSelector;$s=function(t){return function(){return Ws.call(this,t)}}}}var Zs=$s,Gs={};t.event=null,"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(Gs={mouseenter:"mouseover",mouseleave:"mouseout"}));var Js=function(){for(var n,e=t.event;n=e.sourceEvent;)e=n;return e},Qs=function(t,n){var e=t.ownerSVGElement||t;if(e.createSVGPoint){var r=e.createSVGPoint();return r.x=n.clientX,r.y=n.clientY,r=r.matrixTransform(t.getScreenCTM().inverse()),[r.x,r.y]}var i=t.getBoundingClientRect();return[n.clientX-i.left-t.clientLeft,n.clientY-i.top-t.clientTop]},Ks=function(t){var n=Js();return n.changedTouches&&(n=n.changedTouches[0]),Qs(t,n)},tf=function(t){return null==t?S:function(){return this.querySelector(t)}},nf=function(t){return null==t?E:function(){return this.querySelectorAll(t)}},ef=function(t){return new Array(t.length)};A.prototype={constructor:A,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,n){return this._parent.insertBefore(t,n)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var rf=function(t){return function(){return t}},of="$",uf=function(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView};W.prototype={add:function(t){this._names.indexOf(t)<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var n=this._names.indexOf(t);n>=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var af=[null];pt.prototype=dt.prototype={constructor:pt,select:function(t){"function"!=typeof t&&(t=tf(t));for(var n=this._groups,e=n.length,r=new Array(e),i=0;i=x&&(x=m+1);!(g=_[x])&&++x=0;)(r=i[o])&&(u&&u!==r.nextSibling&&u.parentNode.insertBefore(r,u),u=r);return this},sort:function(t){t||(t=P);for(var n=this._groups,e=n.length,r=new Array(e),i=0;i1?this.each((null==n?F:"function"==typeof n?Y:I)(t,n,null==e?"":e)):B(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?j:"function"==typeof n?X:H)(t,n)):this.node()[t]},classed:function(t,n){var e=$(t+"");if(arguments.length<2){for(var r=V(this.node()),i=-1,o=e.length;++i=240?t-240:t+120,i,r),Lt(t,i,r),Lt(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1}}));var Nf=Math.PI/180,Sf=180/Math.PI,Ef=.95047,Af=1,Cf=1.08883,zf=4/29,Pf=6/29,Rf=3*Pf*Pf,Lf=Pf*Pf*Pf;pf(Dt,Ut,wt(Mt,{brighter:function(t){return new Dt(this.l+18*(null==t?1:t),this.a,this.b,this.opacity)},darker:function(t){return new Dt(this.l-18*(null==t?1:t),this.a,this.b,this.opacity)},rgb:function(){var t=(this.l+16)/116,n=isNaN(this.a)?t:t+this.a/500,e=isNaN(this.b)?t:t-this.b/200;return t=Af*Ft(t),n=Ef*Ft(n),e=Cf*Ft(e),new At(It(3.2404542*n-1.5371385*t-.4985314*e),It(-.969266*n+1.8760108*t+.041556*e),It(.0556434*n-.2040259*t+1.0572252*e),this.opacity)}})),pf(Ht,jt,wt(Mt,{brighter:function(t){return new Ht(this.h,this.c,this.l+18*(null==t?1:t),this.opacity)},darker:function(t){return new Ht(this.h,this.c,this.l-18*(null==t?1:t),this.opacity)},rgb:function(){return qt(this).rgb()}}));var qf=-.14861,Uf=1.78277,Df=-.29227,Of=-.90649,Ff=1.97294,If=Ff*Of,Yf=Ff*Uf,Bf=Uf*Df-Of*qf;pf(Vt,$t,wt(Mt,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new Vt(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new Vt(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*Nf,n=+this.l,e=isNaN(this.s)?0:this.s*n*(1-n),r=Math.cos(t),i=Math.sin(t);return new At(255*(n+e*(qf*r+Uf*i)),255*(n+e*(Df*r+Of*i)),255*(n+e*(Ff*r)),this.opacity)}}));var jf,Hf,Xf,$f,Vf,Wf,Zf=function(t){var n=t.length-1;return function(e){var r=e<=0?e=0:e>=1?(e=1,n-1):Math.floor(e*n),i=t[r],o=t[r+1],u=r>0?t[r-1]:2*i-o,a=ro&&(i=n.slice(o,i),a[u]?a[u]+=i:a[++u]=i),(e=e[0])===(r=r[0])?a[u]?a[u]+=r:a[++u]=r:(a[++u]=null,c.push({i:u,x:rl(e,r)})),o=ul.lastIndex;return oDl&&e.state1e-6)if(Math.abs(f*a-c*s)>1e-6&&i){var h=e-o,p=r-u,d=a*a+c*c,v=h*h+p*p,_=Math.sqrt(d),y=Math.sqrt(l),g=i*Math.tan((Yh-Math.acos((d+l-v)/(2*_*y)))/2),m=g/y,x=g/_;Math.abs(m-1)>1e-6&&(this._+="L"+(t+m*s)+","+(n+m*f)),this._+="A"+i+","+i+",0,0,"+ +(f*h>s*p)+","+(this._x1=t+x*a)+","+(this._y1=n+x*c)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n;var u=(e=+e)*Math.cos(r),a=e*Math.sin(r),c=t+u,s=n+a,f=1^o,l=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+c+","+s:(Math.abs(this._x1-c)>1e-6||Math.abs(this._y1-s)>1e-6)&&(this._+="L"+c+","+s),e&&(l<0&&(l=l%Bh+Bh),l>jh?this._+="A"+e+","+e+",0,1,"+f+","+(t-u)+","+(n-a)+"A"+e+","+e+",0,1,"+f+","+(this._x1=c)+","+(this._y1=s):l>1e-6&&(this._+="A"+e+","+e+",0,"+ +(l>=Yh)+","+f+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))))},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z"},toString:function(){return this._}};be.prototype=we.prototype={constructor:be,has:function(t){return"$"+t in this},get:function(t){return this["$"+t]},set:function(t,n){return this["$"+t]=n,this},remove:function(t){var n="$"+t;return n in this&&delete this[n]},clear:function(){for(var t in this)"$"===t[0]&&delete this[t]},keys:function(){var t=[];for(var n in this)"$"===n[0]&&t.push(n.slice(1));return t},values:function(){var t=[];for(var n in this)"$"===n[0]&&t.push(this[n]);return t},entries:function(){var t=[];for(var n in this)"$"===n[0]&&t.push({key:n.slice(1),value:this[n]});return t},size:function(){var t=0;for(var n in this)"$"===n[0]&&++t;return t},empty:function(){for(var t in this)if("$"===t[0])return!1;return!0},each:function(t){for(var n in this)"$"===n[0]&&t(this[n],n.slice(1),this)}};var Hh=we.prototype;Se.prototype=Ee.prototype={constructor:Se,has:Hh.has,add:function(t){return t+="",this["$"+t]=t,this},remove:Hh.remove,clear:Hh.clear,values:Hh.keys,size:Hh.size,empty:Hh.empty,each:Hh.each};var Xh=function(t){function n(t,n){function e(){if(f>=s)return a;if(i)return i=!1,u;var n,e=f;if(34===t.charCodeAt(e)){for(var r=e;r++f&&(f=r),il&&(l=i));for(ft||t>i||r>n||n>o))return this;var u,a,c=i-e,s=this._root;switch(a=(n<(r+o)/2)<<1|t<(e+i)/2){case 0:do{u=new Array(4),u[a]=s,s=u}while(c*=2,i=e+c,o=r+c,t>i||n>o);break;case 1:do{u=new Array(4),u[a]=s,s=u}while(c*=2,e=i-c,o=r+c,e>t||n>o);break;case 2:do{u=new Array(4),u[a]=s,s=u}while(c*=2,i=e+c,r=o-c,t>i||r>n);break;case 3:do{u=new Array(4),u[a]=s,s=u}while(c*=2,e=i-c,r=o-c,e>t||r>n)}this._root&&this._root.length&&(this._root=s)}return this._x0=e,this._y0=r,this._x1=i,this._y1=o,this},op.data=function(){var t=[];return this.visit(function(n){if(!n.length)do{t.push(n.data)}while(n=n.next)}),t},op.extent=function(t){return arguments.length?this.cover(+t[0][0],+t[0][1]).cover(+t[1][0],+t[1][1]):isNaN(this._x0)?void 0:[[this._x0,this._y0],[this._x1,this._y1]]},op.find=function(t,n,e){var r,i,o,u,a,c,s,f=this._x0,l=this._y0,h=this._x1,p=this._y1,d=[],v=this._root;for(v&&d.push(new ip(v,f,l,h,p)),null==e?e=1/0:(f=t-e,l=n-e,h=t+e,p=n+e,e*=e);c=d.pop();)if(!(!(v=c.node)||(i=c.x0)>h||(o=c.y0)>p||(u=c.x1)=y)<<1|t>=_)&&(c=d[d.length-1],d[d.length-1]=d[d.length-1-s],d[d.length-1-s]=c)}else{var g=t-+this._x.call(null,v.data),m=n-+this._y.call(null,v.data),x=g*g+m*m;if(x=(a=(d+_)/2))?d=a:_=a,(f=u>=(c=(v+y)/2))?v=c:y=c,n=p,!(p=p[l=f<<1|s]))return this;if(!p.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,h=l)}for(;p.data!==t;)if(r=p,!(p=p.next))return this;return(i=p.next)&&delete p.next,r?(i?r.next=i:delete r.next,this):n?(i?n[l]=i:delete n[l],(p=n[0]||n[1]||n[2]||n[3])&&p===(n[3]||n[2]||n[1]||n[0])&&!p.length&&(e?e[h]=p:this._root=p),this):(this._root=i,this)},op.removeAll=function(t){for(var n=0,e=t.length;n1?r[0]+r.slice(2):r,+t.slice(e+1)]},fp=function(t){return(t=sp(Math.abs(t)))?t[1]:NaN},lp=function(t,n){return function(e,r){for(var i=e.length,o=[],u=0,a=t[0],c=0;i>0&&a>0&&(c+a+1>r&&(a=Math.max(1,r-c)),o.push(e.substring(i-=a,i+a)),!((c+=a+1)>r));)a=t[u=(u+1)%t.length];return o.reverse().join(n)}},hp=function(t){return function(n){return n.replace(/[0-9]/g,function(n){return t[+n]})}},pp=function(t,n){var e=sp(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")},dp={"":function(t,n){t:for(var e,r=(t=t.toPrecision(n)).length,i=1,o=-1;i0&&(o=0)}return o>0?t.slice(0,o)+t.slice(e+1):t},"%":function(t,n){return(100*t).toFixed(n)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,n){return t.toExponential(n)},f:function(t,n){return t.toFixed(n)},g:function(t,n){return t.toPrecision(n)},o:function(t){return Math.round(t).toString(8)},p:function(t,n){return pp(100*t,n)},r:pp,s:function(t,n){var e=sp(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(up=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,u=r.length;return o===u?r:o>u?r+new Array(o-u+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+sp(t,Math.max(0,n+o-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},vp=/^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([a-z%])?$/i;He.prototype=Xe.prototype,Xe.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(null==this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(null==this.precision?"":"."+Math.max(0,0|this.precision))+this.type};var _p,yp=function(t){return t},gp=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],mp=function(t){function n(t){function n(t){var n,r,u,f=_,x=y;if("c"===v)x=g(t)+x,t="";else{var b=(t=+t)<0;if(t=g(Math.abs(t),d),b&&0==+t&&(b=!1),f=(b?"("===s?s:"-":"-"===s||"("===s?"":s)+f,x=x+("s"===v?gp[8+up/3]:"")+(b&&"("===s?")":""),m)for(n=-1,r=t.length;++n(u=t.charCodeAt(n))||u>57){x=(46===u?i+t.slice(n+1):t.slice(n))+x,t=t.slice(0,n);break}}p&&!l&&(t=e(t,1/0));var w=f.length+t.length+x.length,M=w>1)+f+t+x+M.slice(w);break;default:t=M+f+t+x}return o(t)}var a=(t=He(t)).fill,c=t.align,s=t.sign,f=t.symbol,l=t.zero,h=t.width,p=t.comma,d=t.precision,v=t.type,_="$"===f?r[0]:"#"===f&&/[boxX]/.test(v)?"0"+v.toLowerCase():"",y="$"===f?r[1]:/[%p]/.test(v)?u:"",g=dp[v],m=!v||/[defgprs%]/.test(v);return d=null==d?v?6:12:/[gprs]/.test(v)?Math.max(1,Math.min(21,d)):Math.max(0,Math.min(20,d)),n.toString=function(){return t+""},n}var e=t.grouping&&t.thousands?lp(t.grouping,t.thousands):yp,r=t.currency,i=t.decimal,o=t.numerals?hp(t.numerals):yp,u=t.percent||"%";return{format:n,formatPrefix:function(t,e){var r=n((t=He(t),t.type="f",t)),i=3*Math.max(-8,Math.min(8,Math.floor(fp(e)/3))),o=Math.pow(10,-i),u=gp[8+i/3];return function(t){return r(o*t)+u}}}};$e({decimal:".",thousands:",",grouping:[3],currency:["$",""]});var xp=function(t){return Math.max(0,-fp(Math.abs(t)))},bp=function(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(fp(n)/3)))-fp(Math.abs(t)))},wp=function(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,fp(n)-fp(t))+1},Mp=function(){return new Ve};Ve.prototype={constructor:Ve,reset:function(){this.s=this.t=0},add:function(t){We(nd,t,this.t),We(this,nd.s,this.s),this.s?this.t+=nd.t:this.s=nd.t},valueOf:function(){return this.s}};var Tp,kp,Np,Sp,Ep,Ap,Cp,zp,Pp,Rp,Lp,qp,Up,Dp,Op,Fp,Ip,Yp,Bp,jp,Hp,Xp,$p,Vp,Wp,Zp,Gp,Jp,Qp,Kp,td,nd=new Ve,ed=1e-6,rd=Math.PI,id=rd/2,od=rd/4,ud=2*rd,ad=180/rd,cd=rd/180,sd=Math.abs,fd=Math.atan,ld=Math.atan2,hd=Math.cos,pd=Math.ceil,dd=Math.exp,vd=Math.log,_d=Math.pow,yd=Math.sin,gd=Math.sign||function(t){return t>0?1:t<0?-1:0},md=Math.sqrt,xd=Math.tan,bd={Feature:function(t,n){Ke(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++red?Pp=90:Sd<-ed&&(Cp=-90),Op[0]=Ap,Op[1]=zp}},Ad={sphere:Qe,point:Mr,lineStart:kr,lineEnd:Er,polygonStart:function(){Ad.lineStart=Ar,Ad.lineEnd=Cr},polygonEnd:function(){Ad.lineStart=kr,Ad.lineEnd=Er}},Cd=function(t){return function(){return t}},zd=function(t,n){function e(e,r){return e=t(e,r),n(e[0],e[1])}return t.invert&&n.invert&&(e.invert=function(e,r){return(e=n.invert(e,r))&&t.invert(e[0],e[1])}),e};Rr.invert=Rr;var Pd,Rd,Ld,qd,Ud,Dd,Od,Fd,Id,Yd,Bd,jd=function(t){function n(n){return n=t(n[0]*cd,n[1]*cd),n[0]*=ad,n[1]*=ad,n}return t=Lr(t[0]*cd,t[1]*cd,t.length>2?t[2]*cd:0),n.invert=function(n){return n=t.invert(n[0]*cd,n[1]*cd),n[0]*=ad,n[1]*=ad,n},n},Hd=function(){var t,n=[];return{point:function(n,e){t.push([n,e])},lineStart:function(){n.push(t=[])},lineEnd:Qe,rejoin:function(){n.length>1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}},Xd=function(t,n,e,r,i,o){var u,a=t[0],c=t[1],s=0,f=1,l=n[0]-a,h=n[1]-c;if(u=e-a,l||!(u>0)){if(u/=l,l<0){if(u0){if(u>f)return;u>s&&(s=u)}if(u=i-a,l||!(u<0)){if(u/=l,l<0){if(u>f)return;u>s&&(s=u)}else if(l>0){if(u0)){if(u/=h,h<0){if(u0){if(u>f)return;u>s&&(s=u)}if(u=o-c,h||!(u<0)){if(u/=h,h<0){if(u>f)return;u>s&&(s=u)}else if(h>0){if(u0&&(t[0]=a+s*l,t[1]=c+s*h),f<1&&(n[0]=a+f*l,n[1]=c+f*h),!0}}}}},$d=function(t,n){return sd(t[0]-n[0])=0;--o)i.point((f=s[o])[0],f[1]);else r(h.x,h.p.x,-1,i);h=h.p}s=(h=h.o).z,p=!p}while(!h.v);i.lineEnd()}}},Wd=1e9,Zd=-Wd,Gd=Mp(),Jd=function(t,n){var e=n[0],r=n[1],i=[yd(e),-hd(e),0],o=0,u=0;Gd.reset();for(var a=0,c=t.length;a=0?1:-1,T=M*w,k=T>rd,N=d*x;if(Gd.add(ld(N*M*yd(T),v*b+N*hd(T))),o+=k?w+M*ud:w,k^h>=e^g>=e){var S=sr(ar(l),ar(y));hr(S);var E=sr(i,S);hr(E);var A=(k^w>=0?-1:1)*Ge(E[2]);(r>A||r===A&&(S[0]||S[1]))&&(u+=k^w>=0?1:-1)}}return(o<-ed||ohv&&(hv=t),npv&&(pv=n)},lineStart:Qe,lineEnd:Qe,polygonStart:Qe,polygonEnd:Qe,result:function(){var t=[[fv,lv],[hv,pv]];return hv=pv=-(lv=fv=1/0),t}},vv=0,_v=0,yv=0,gv=0,mv=0,xv=0,bv=0,wv=0,Mv=0,Tv={point:oi,lineStart:ui,lineEnd:si,polygonStart:function(){Tv.lineStart=fi,Tv.lineEnd=li},polygonEnd:function(){Tv.point=oi,Tv.lineStart=ui,Tv.lineEnd=si},result:function(){var t=Mv?[bv/Mv,wv/Mv]:xv?[gv/xv,mv/xv]:yv?[vv/yv,_v/yv]:[NaN,NaN];return vv=_v=yv=gv=mv=xv=bv=wv=Mv=0,t}};di.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,ud)}},result:Qe};var kv,Nv,Sv,Ev,Av,Cv=Mp(),zv={point:Qe,lineStart:function(){zv.point=vi},lineEnd:function(){kv&&_i(Nv,Sv),zv.point=Qe},polygonStart:function(){kv=!0},polygonEnd:function(){kv=null},result:function(){var t=+Cv;return Cv.reset(),t}};yi.prototype={_radius:4.5,_circle:gi(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._string.push("M",t,",",n),this._point=1;break;case 1:this._string.push("L",t,",",n);break;default:null==this._circle&&(this._circle=gi(this._radius)),this._string.push("M",t,",",n,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}};var Pv=function(t,n,e,r){return function(i,o){function u(n,e){var r=i(n,e);t(n=r[0],e=r[1])&&o.point(n,e)}function a(t,n){var e=i(t,n);_.point(e[0],e[1])}function c(){b.point=a,_.lineStart()}function s(){b.point=u,_.lineEnd()}function f(t,n){v.push([t,n]);var e=i(t,n);m.point(e[0],e[1])}function l(){m.lineStart(),v=[]}function h(){f(v[0][0],v[0][1]),m.lineEnd();var t,n,e,r,i=m.clean(),u=g.result(),a=u.length;if(v.pop(),p.push(v),v=null,a)if(1&i){if(e=u[0],(n=e.length-1)>0){for(x||(o.polygonStart(),x=!0),o.lineStart(),t=0;t1&&2&i&&u.push(u.pop().concat(u.shift())),d.push(u.filter(mi))}var p,d,v,_=n(o),y=i.invert(r[0],r[1]),g=Hd(),m=n(g),x=!1,b={point:u,lineStart:c,lineEnd:s,polygonStart:function(){b.point=f,b.lineStart=l,b.lineEnd=h,d=[],p=[]},polygonEnd:function(){b.point=u,b.lineStart=c,b.lineEnd=s,d=Cs(d);var t=Jd(p,y);d.length?(x||(o.polygonStart(),x=!0),Vd(d,xi,t,e,o)):t&&(x||(o.polygonStart(),x=!0),o.lineStart(),e(null,null,1,o),o.lineEnd()),x&&(o.polygonEnd(),x=!1),d=p=null},sphere:function(){o.polygonStart(),o.lineStart(),e(null,null,1,o),o.lineEnd(),o.polygonEnd()}};return b}},Rv=Pv(function(){return!0},function(t){var n,e=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),n=1},point:function(o,u){var a=o>0?rd:-rd,c=sd(o-e);sd(c-rd)0?id:-id),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(a,r),t.point(o,r),n=0):i!==a&&c>=rd&&(sd(e-i)ed){var o=t[0]o}function r(t,n,e){var r=[1,0,0],i=sr(ar(t),ar(n)),u=cr(i,i),a=i[0],c=u-a*a;if(!c)return!e&&t;var s=o*u/c,f=-o*a/c,l=sr(r,i),h=lr(r,s);fr(h,lr(i,f));var p=l,d=cr(h,p),v=cr(p,p),_=d*d-v*(cr(h,h)-1);if(!(_<0)){var y=md(_),g=lr(p,(-d-y)/v);if(fr(g,h),g=ur(g),!e)return g;var m,x=t[0],b=n[0],w=t[1],M=n[1];b0^g[1]<(sd(g[0]-x)rd^(x<=g[0]&&g[0]<=b)){var S=lr(p,(-d+y)/v);return fr(S,h),[g,ur(S)]}}}function i(n,e){var r=u?t:rd-t,i=0;return n<-r?i|=1:n>r&&(i|=2),e<-r?i|=4:e>r&&(i|=8),i}var o=hd(t),u=o>0,a=sd(o)>ed;return Pv(e,function(t){var n,o,c,s,f;return{lineStart:function(){s=c=!1,f=1},point:function(l,h){var p,d=[l,h],v=e(l,h),_=u?v?0:i(l,h):v?i(l+(l<0?rd:-rd),h):0;if(!n&&(s=c=v)&&t.lineStart(),v!==c&&(!(p=r(n,d))||$d(n,p)||$d(d,p))&&(d[0]+=ed,d[1]+=ed,v=e(d[0],d[1])),v!==c)f=0,v?(t.lineStart(),p=r(d,n),t.point(p[0],p[1])):(p=r(n,d),t.point(p[0],p[1]),t.lineEnd()),n=p;else if(a&&n&&u^v){var y;_&o||!(y=r(d,n,!0))||(f=0,u?(t.lineStart(),t.point(y[0][0],y[0][1]),t.point(y[1][0],y[1][1]),t.lineEnd()):(t.point(y[1][0],y[1][1]),t.lineEnd(),t.lineStart(),t.point(y[0][0],y[0][1])))}!v||n&&$d(n,d)||t.point(d[0],d[1]),n=d,c=v,o=_},lineEnd:function(){c&&t.lineEnd(),n=null},clean:function(){return f|(s&&c)<<1}}},function(e,r,i,o){Or(o,t,n,i,e,r)},u?[0,-t]:[-rd,t-rd])};Mi.prototype={constructor:Mi,point:function(t,n){this.stream.point(t,n)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var qv=16,Uv=hd(30*cd),Dv=function(t,n){return+n?Si(t,n):Ni(t)},Ov=wi({point:function(t,n){this.stream.point(t*cd,n*cd)}}),Fv=function(){return Ci(Pi).scale(155.424).center([0,33.6442])},Iv=function(){return Fv().parallels([29.5,45.5]).scale(1070).translate([480,250]).rotate([96,0]).center([-.6,38.7])},Yv=Li(function(t){return md(2/(1+t))});Yv.invert=qi(function(t){return 2*Ge(t/2)});var Bv=Li(function(t){return(t=Ze(t))&&t/yd(t)});Bv.invert=qi(function(t){return t});Ui.invert=function(t,n){return[t,2*fd(dd(n))-id]};Ii.invert=Ii;Bi.invert=qi(fd);Hi.invert=qi(Ge);Xi.invert=qi(function(t){return 2*fd(t)});$i.invert=function(t,n){return[-n,2*fd(dd(t))-id]};uo.prototype=eo.prototype={constructor:uo,count:function(){return this.eachAfter(to)},each:function(t){var n,e,r,i,o=this,u=[o];do{for(n=u.reverse(),u=[];o=n.pop();)if(t(o),e=o.children)for(r=0,i=e.length;r=0;--e)i.push(n[e]);return this},sum:function(t){return this.eachAfter(function(n){for(var e=+t(n.data)||0,r=n.children,i=r&&r.length;--i>=0;)e+=r[i].value;n.value=e})},sort:function(t){return this.eachBefore(function(n){n.children&&n.children.sort(t)})},path:function(t){for(var n=this,e=no(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},descendants:function(){var t=[];return this.each(function(n){t.push(n)}),t},leaves:function(){var t=[];return this.eachBefore(function(n){n.children||t.push(n)}),t},links:function(){var t=this,n=[];return t.each(function(e){e!==t&&n.push({source:e.parent,target:e})}),n},copy:function(){return eo(this).eachBefore(io)}};var jv=Array.prototype.slice,Hv=function(t){for(var n,e,r=0,i=(t=ao(jv.call(t))).length,o=[];r1?n:1)},e}(Qv),t_=function t(n){function e(t,e,r,i,o){if((u=t._squarify)&&u.ratio===n)for(var u,a,c,s,f,l=-1,h=u.length,p=t.value;++l1?n:1)},e}(Qv),n_=function(t,n,e){return(n[0]-t[0])*(e[1]-t[1])-(n[1]-t[1])*(e[0]-t[0])},e_=[].slice,r_={};Bo.prototype=Wo.prototype={constructor:Bo,defer:function(t){if("function"!=typeof t)throw new Error("invalid callback");if(this._call)throw new Error("defer after await");if(null!=this._error)return this;var n=e_.call(arguments,1);return n.push(t),++this._waiting,this._tasks.push(n),jo(this),this},abort:function(){return null==this._error&&$o(this,new Error("abort")),this},await:function(t){if("function"!=typeof t)throw new Error("invalid callback");if(this._call)throw new Error("multiple await");return this._call=function(n,e){t.apply(null,[n].concat(e))},Vo(this),this},awaitAll:function(t){if("function"!=typeof t)throw new Error("invalid callback");if(this._call)throw new Error("multiple await");return this._call=t,Vo(this),this}};var i_=function(){return Math.random()},o_=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,1===arguments.length?(e=t,t=0):e-=t,function(){return n()*e+t}}return e.source=t,e}(i_),u_=function t(n){function e(t,e){var r,i;return t=null==t?0:+t,e=null==e?1:+e,function(){var o;if(null!=r)o=r,r=null;else do{r=2*n()-1,o=2*n()-1,i=r*r+o*o}while(!i||i>1);return t+e*o*Math.sqrt(-2*Math.log(i)/i)}}return e.source=t,e}(i_),a_=function t(n){function e(){var t=u_.source(n).apply(this,arguments);return function(){return Math.exp(t())}}return e.source=t,e}(i_),c_=function t(n){function e(t){return function(){for(var e=0,r=0;r=200&&e<300||304===e){if(o)try{n=o.call(r,s)}catch(t){return void a.call("error",r,t)}else n=s;a.call("load",r,n)}else a.call("error",r,t)}var r,i,o,u,a=h("beforesend","progress","load","error"),c=we(),s=new XMLHttpRequest,f=null,l=null,p=0;if("undefined"==typeof XDomainRequest||"withCredentials"in s||!/^(http(s)?:)?\/\//.test(t)||(s=new XDomainRequest),"onload"in s?s.onload=s.onerror=s.ontimeout=e:s.onreadystatechange=function(t){s.readyState>3&&e(t)},s.onprogress=function(t){a.call("progress",r,t)},r={header:function(t,n){return t=(t+"").toLowerCase(),arguments.length<2?c.get(t):(null==n?c.remove(t):c.set(t,n+""),r)},mimeType:function(t){return arguments.length?(i=null==t?null:t+"",r):i},responseType:function(t){return arguments.length?(u=t,r):u},timeout:function(t){return arguments.length?(p=+t,r):p},user:function(t){return arguments.length<1?f:(f=null==t?null:t+"",r)},password:function(t){return arguments.length<1?l:(l=null==t?null:t+"",r)},response:function(t){return o=t,r},get:function(t,n){return r.send("GET",t,n)},post:function(t,n){return r.send("POST",t,n)},send:function(n,e,o){return s.open(n,t,!0,f,l),null==i||c.has("accept")||c.set("accept",i+",*/*"),s.setRequestHeader&&c.each(function(t,n){s.setRequestHeader(n,t)}),null!=i&&s.overrideMimeType&&s.overrideMimeType(i),null!=u&&(s.responseType=u),p>0&&(s.timeout=p),null==o&&"function"==typeof e&&(o=e,e=null),null!=o&&1===o.length&&(o=Zo(o)),null!=o&&r.on("error",o).on("load",function(t){o(null,t)}),a.call("beforesend",r,s),s.send(null==e?null:e),r},abort:function(){return s.abort(),r},on:function(){var t=a.on.apply(a,arguments);return t===a?r:t}},null!=n){if("function"!=typeof n)throw new Error("invalid callback: "+n);return r.get(n)}return r},h_=function(t,n){return function(e,r){var i=l_(e).mimeType(t).response(n);if(null!=r){if("function"!=typeof r)throw new Error("invalid callback: "+r);return i.get(r)}return i}},p_=h_("text/html",function(t){return document.createRange().createContextualFragment(t.responseText)}),d_=h_("application/json",function(t){return JSON.parse(t.responseText)}),v_=h_("text/plain",function(t){return t.responseText}),__=h_("application/xml",function(t){var n=t.responseXML;if(!n)throw new Error("parse error");return n}),y_=function(t,n){return function(e,r,i){arguments.length<3&&(i=r,r=null);var o=l_(e).mimeType(t);return o.row=function(t){return arguments.length?o.response(Jo(n,r=t)):r},o.row(r),i?o.get(i):o}},g_=y_("text/csv",Vh),m_=y_("text/tab-separated-values",Qh),x_=Array.prototype,b_=x_.map,w_=x_.slice,M_={name:"implicit"},T_=function(t){return function(){return t}},k_=function(t){return+t},N_=[0,1],S_=function(n,e,r){var o,u=n[0],a=n[n.length-1],c=i(u,a,null==e?10:e);switch((r=He(null==r?",f":r)).type){case"s":var s=Math.max(Math.abs(u),Math.abs(a));return null!=r.precision||isNaN(o=bp(c,s))||(r.precision=o),t.formatPrefix(r,s);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(o=wp(c,Math.max(Math.abs(u),Math.abs(a))))||(r.precision=o-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(o=xp(c))||(r.precision=o-2*("%"===r.type))}return t.format(r)},E_=function(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],u=t[i];return u0?t>1?Mu(function(n){n.setTime(Math.floor(n/t)*t)},function(n,e){n.setTime(+n+e*t)},function(n,e){return(e-n)/t}):z_:null};var P_=z_.range,R_=6e4,L_=6048e5,q_=Mu(function(t){t.setTime(1e3*Math.floor(t/1e3))},function(t,n){t.setTime(+t+1e3*n)},function(t,n){return(n-t)/1e3},function(t){return t.getUTCSeconds()}),U_=q_.range,D_=Mu(function(t){t.setTime(Math.floor(t/R_)*R_)},function(t,n){t.setTime(+t+n*R_)},function(t,n){return(n-t)/R_},function(t){return t.getMinutes()}),O_=D_.range,F_=Mu(function(t){var n=t.getTimezoneOffset()*R_%36e5;n<0&&(n+=36e5),t.setTime(36e5*Math.floor((+t-n)/36e5)+n)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getHours()}),I_=F_.range,Y_=Mu(function(t){t.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*R_)/864e5},function(t){return t.getDate()-1}),B_=Y_.range,j_=Tu(0),H_=Tu(1),X_=Tu(2),$_=Tu(3),V_=Tu(4),W_=Tu(5),Z_=Tu(6),G_=j_.range,J_=H_.range,Q_=X_.range,K_=$_.range,ty=V_.range,ny=W_.range,ey=Z_.range,ry=Mu(function(t){t.setDate(1),t.setHours(0,0,0,0)},function(t,n){t.setMonth(t.getMonth()+n)},function(t,n){return n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())},function(t){return t.getMonth()}),iy=ry.range,oy=Mu(function(t){t.setMonth(0,1),t.setHours(0,0,0,0)},function(t,n){t.setFullYear(t.getFullYear()+n)},function(t,n){return n.getFullYear()-t.getFullYear()},function(t){return t.getFullYear()});oy.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Mu(function(n){n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)},function(n,e){n.setFullYear(n.getFullYear()+e*t)}):null};var uy=oy.range,ay=Mu(function(t){t.setUTCSeconds(0,0)},function(t,n){t.setTime(+t+n*R_)},function(t,n){return(n-t)/R_},function(t){return t.getUTCMinutes()}),cy=ay.range,sy=Mu(function(t){t.setUTCMinutes(0,0,0)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getUTCHours()}),fy=sy.range,ly=Mu(function(t){t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+n)},function(t,n){return(n-t)/864e5},function(t){return t.getUTCDate()-1}),hy=ly.range,py=ku(0),dy=ku(1),vy=ku(2),_y=ku(3),yy=ku(4),gy=ku(5),my=ku(6),xy=py.range,by=dy.range,wy=vy.range,My=_y.range,Ty=yy.range,ky=gy.range,Ny=my.range,Sy=Mu(function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCMonth(t.getUTCMonth()+n)},function(t,n){return n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())},function(t){return t.getUTCMonth()}),Ey=Sy.range,Ay=Mu(function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n)},function(t,n){return n.getUTCFullYear()-t.getUTCFullYear()},function(t){return t.getUTCFullYear()});Ay.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Mu(function(n){n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)},function(n,e){n.setUTCFullYear(n.getUTCFullYear()+e*t)}):null};var Cy,zy=Ay.range,Py={"-":"",_:" ",0:"0"},Ry=/^\s*\d+/,Ly=/^%/,qy=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;Ma({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Uy=Date.prototype.toISOString?function(t){return t.toISOString()}:t.utcFormat("%Y-%m-%dT%H:%M:%S.%LZ"),Dy=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:t.utcParse("%Y-%m-%dT%H:%M:%S.%LZ"),Oy=1e3,Fy=60*Oy,Iy=60*Fy,Yy=24*Iy,By=7*Yy,jy=30*Yy,Hy=365*Yy,Xy=function(t){return t.match(/.{6}/g).map(function(t){return"#"+t})},$y=Xy("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf"),Vy=Xy("393b795254a36b6ecf9c9ede6379398ca252b5cf6bcedb9c8c6d31bd9e39e7ba52e7cb94843c39ad494ad6616be7969c7b4173a55194ce6dbdde9ed6"),Wy=Xy("3182bd6baed69ecae1c6dbefe6550dfd8d3cfdae6bfdd0a231a35474c476a1d99bc7e9c0756bb19e9ac8bcbddcdadaeb636363969696bdbdbdd9d9d9"),Zy=Xy("1f77b4aec7e8ff7f0effbb782ca02c98df8ad62728ff98969467bdc5b0d58c564bc49c94e377c2f7b6d27f7f7fc7c7c7bcbd22dbdb8d17becf9edae5"),Gy=wl($t(300,.5,0),$t(-240,.5,1)),Jy=wl($t(-100,.75,.35),$t(80,1.5,.8)),Qy=wl($t(260,.75,.35),$t(80,1.5,.8)),Ky=$t(),tg=Sa(Xy("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),ng=Sa(Xy("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),eg=Sa(Xy("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),rg=Sa(Xy("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")),ig=function(t){return function(){return t}},og=Math.abs,ug=Math.atan2,ag=Math.cos,cg=Math.max,sg=Math.min,fg=Math.sin,lg=Math.sqrt,hg=1e-12,pg=Math.PI,dg=pg/2,vg=2*pg;Oa.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._context.lineTo(t,n)}}};var _g=function(t){return new Oa(t)},yg=function(){function t(t){var a,c,s,f=t.length,l=!1;for(null==i&&(u=o(s=ve())),a=0;a<=f;++a)!(a=f;--l)s.point(_[l],y[l]);s.lineEnd(),s.areaEnd()}v&&(_[n]=+e(h,n,t),y[n]=+i(h,n,t),s.point(r?+r(h,n,t):_[n],o?+o(h,n,t):y[n]))}if(p)return s=null,p+""||null}function n(){return yg().defined(u).curve(c).context(a)}var e=Fa,r=null,i=ig(0),o=Ia,u=ig(!0),a=null,c=_g,s=null;return t.x=function(n){return arguments.length?(e="function"==typeof n?n:ig(+n),r=null,t):e},t.x0=function(n){return arguments.length?(e="function"==typeof n?n:ig(+n),t):e},t.x1=function(n){return arguments.length?(r=null==n?null:"function"==typeof n?n:ig(+n),t):r},t.y=function(n){return arguments.length?(i="function"==typeof n?n:ig(+n),o=null,t):i},t.y0=function(n){return arguments.length?(i="function"==typeof n?n:ig(+n),t):i},t.y1=function(n){return arguments.length?(o=null==n?null:"function"==typeof n?n:ig(+n),t):o},t.lineX0=t.lineY0=function(){return n().x(e).y(i)},t.lineY1=function(){return n().x(e).y(o)},t.lineX1=function(){return n().x(r).y(i)},t.defined=function(n){return arguments.length?(u="function"==typeof n?n:ig(!!n),t):u},t.curve=function(n){return arguments.length?(c=n,null!=a&&(s=c(a)),t):c},t.context=function(n){return arguments.length?(null==n?a=s=null:s=c(a=n),t):a},t},mg=function(t,n){return nt?1:n>=t?0:NaN},xg=function(t){return t},bg=Ba(_g);Ya.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,n){this._curve.point(n*Math.sin(t),n*-Math.cos(t))}};var wg=function(){return ja(yg().curve(bg))},Mg=function(){var t=gg().curve(bg),n=t.curve,e=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return ja(e())},delete t.lineX0,t.lineEndAngle=function(){return ja(r())},delete t.lineX1,t.lineInnerRadius=function(){return ja(i())},delete t.lineY0,t.lineOuterRadius=function(){return ja(o())},delete t.lineY1,t.curve=function(t){return arguments.length?n(Ba(t)):n()._curve},t},Tg=function(t,n){return[(n=+n)*Math.cos(t-=Math.PI/2),n*Math.sin(t)]},kg=Array.prototype.slice,Ng={draw:function(t,n){var e=Math.sqrt(n/pg);t.moveTo(e,0),t.arc(0,0,e,0,vg)}},Sg={draw:function(t,n){var e=Math.sqrt(n/5)/2;t.moveTo(-3*e,-e),t.lineTo(-e,-e),t.lineTo(-e,-3*e),t.lineTo(e,-3*e),t.lineTo(e,-e),t.lineTo(3*e,-e),t.lineTo(3*e,e),t.lineTo(e,e),t.lineTo(e,3*e),t.lineTo(-e,3*e),t.lineTo(-e,e),t.lineTo(-3*e,e),t.closePath()}},Eg=Math.sqrt(1/3),Ag=2*Eg,Cg={draw:function(t,n){var e=Math.sqrt(n/Ag),r=e*Eg;t.moveTo(0,-e),t.lineTo(r,0),t.lineTo(0,e),t.lineTo(-r,0),t.closePath()}},zg=Math.sin(pg/10)/Math.sin(7*pg/10),Pg=Math.sin(vg/10)*zg,Rg=-Math.cos(vg/10)*zg,Lg={draw:function(t,n){var e=Math.sqrt(.8908130915292852*n),r=Pg*e,i=Rg*e;t.moveTo(0,-e),t.lineTo(r,i);for(var o=1;o<5;++o){var u=vg*o/5,a=Math.cos(u),c=Math.sin(u);t.lineTo(c*e,-a*e),t.lineTo(a*r-c*i,c*r+a*i)}t.closePath()}},qg={draw:function(t,n){var e=Math.sqrt(n),r=-e/2;t.rect(r,r,e,e)}},Ug=Math.sqrt(3),Dg={draw:function(t,n){var e=-Math.sqrt(n/(3*Ug));t.moveTo(0,2*e),t.lineTo(-Ug*e,-e),t.lineTo(Ug*e,-e),t.closePath()}},Og=-.5,Fg=Math.sqrt(3)/2,Ig=1/Math.sqrt(12),Yg=3*(Ig/2+1),Bg={draw:function(t,n){var e=Math.sqrt(n/Yg),r=e/2,i=e*Ig,o=r,u=e*Ig+e,a=-o,c=u;t.moveTo(r,i),t.lineTo(o,u),t.lineTo(a,c),t.lineTo(Og*r-Fg*i,Fg*r+Og*i),t.lineTo(Og*o-Fg*u,Fg*o+Og*u),t.lineTo(Og*a-Fg*c,Fg*a+Og*c),t.lineTo(Og*r+Fg*i,Og*i-Fg*r),t.lineTo(Og*o+Fg*u,Og*u-Fg*o),t.lineTo(Og*a+Fg*c,Og*c-Fg*a),t.closePath()}},jg=[Ng,Sg,Cg,qg,Lg,Dg,Bg],Hg=function(){};Ja.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Ga(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Ga(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};Qa.prototype={areaStart:Hg,areaEnd:Hg,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x2=t,this._y2=n;break;case 1:this._point=2,this._x3=t,this._y3=n;break;case 2:this._point=3,this._x4=t,this._y4=n,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+n)/6);break;default:Ga(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};Ka.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var e=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+n)/6;this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break;case 3:this._point=4;default:Ga(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};tc.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,n=this._y,e=t.length-1;if(e>0)for(var r,i=t[0],o=n[0],u=t[e]-i,a=n[e]-o,c=-1;++c<=e;)r=c/e,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*u),this._beta*n[c]+(1-this._beta)*(o+r*a));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}};var Xg=function t(n){function e(t){return 1===n?new Ja(t):new tc(t,n)}return e.beta=function(n){return t(+n)},e}(.85);ec.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:nc(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:nc(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var $g=function t(n){function e(t){return new ec(t,n)}return e.tension=function(n){return t(+n)},e}(0);rc.prototype={areaStart:Hg,areaEnd:Hg,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:nc(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Vg=function t(n){function e(t){return new rc(t,n)}return e.tension=function(n){return t(+n)},e}(0);ic.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:nc(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Wg=function t(n){function e(t){return new ic(t,n)}return e.tension=function(n){return t(+n)},e}(0);uc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:oc(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Zg=function t(n){function e(t){return n?new uc(t,n):new ec(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);ac.prototype={areaStart:Hg,areaEnd:Hg,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:oc(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Gg=function t(n){function e(t){return n?new ac(t,n):new rc(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);cc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:oc(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Jg=function t(n){function e(t){return n?new cc(t,n):new ic(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);sc.prototype={areaStart:Hg,areaEnd:Hg,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,n){t=+t,n=+n,this._point?this._context.lineTo(t,n):(this._point=1,this._context.moveTo(t,n))}};dc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:pc(this,this._t0,hc(this,this._t0))}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){var e=NaN;if(t=+t,n=+n,t!==this._x1||n!==this._y1){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,pc(this,hc(this,e=lc(this,t,n)),e);break;default:pc(this,this._t0,e=lc(this,t,n))}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n,this._t0=e}}},(vc.prototype=Object.create(dc.prototype)).point=function(t,n){dc.prototype.point.call(this,n,t)},_c.prototype={moveTo:function(t,n){this._context.moveTo(n,t)},closePath:function(){this._context.closePath()},lineTo:function(t,n){this._context.lineTo(n,t)},bezierCurveTo:function(t,n,e,r,i,o){this._context.bezierCurveTo(n,t,r,e,o,i)}},yc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var t=this._x,n=this._y,e=t.length;if(e)if(this._line?this._context.lineTo(t[0],n[0]):this._context.moveTo(t[0],n[0]),2===e)this._context.lineTo(t[1],n[1]);else for(var r=gc(t),i=gc(n),o=0,u=1;u=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,n),this._context.lineTo(t,n);else{var e=this._x*(1-this._t)+t*this._t;this._context.lineTo(e,this._y),this._context.lineTo(e,n)}}this._x=t,this._y=n}};var Qg=function(t,n){if((i=t.length)>1)for(var e,r,i,o=1,u=t[n[0]],a=u.length;o=0;)e[n]=n;return e},tm=function(t){var n=t.map(bc);return Kg(t).sort(function(t,e){return n[t]-n[e]})},nm=function(t){return function(){return t}};Tc.prototype={constructor:Tc,insert:function(t,n){var e,r,i;if(t){if(n.P=t,n.N=t.N,t.N&&(t.N.P=n),t.N=n,t.R){for(t=t.R;t.L;)t=t.L;t.L=n}else t.R=n;e=t}else this._?(t=Ec(this._),n.P=null,n.N=t,t.P=t.L=n,e=t):(n.P=n.N=null,this._=n,e=null);for(n.L=n.R=null,n.U=e,n.C=!0,t=n;e&&e.C;)e===(r=e.U).L?(i=r.R)&&i.C?(e.C=i.C=!1,r.C=!0,t=r):(t===e.R&&(Nc(this,e),e=(t=e).U),e.C=!1,r.C=!0,Sc(this,r)):(i=r.L)&&i.C?(e.C=i.C=!1,r.C=!0,t=r):(t===e.L&&(Sc(this,e),e=(t=e).U),e.C=!1,r.C=!0,Nc(this,r)),e=t.U;this._.C=!1},remove:function(t){t.N&&(t.N.P=t.P),t.P&&(t.P.N=t.N),t.N=t.P=null;var n,e,r,i=t.U,o=t.L,u=t.R;if(e=o?u?Ec(u):o:u,i?i.L===t?i.L=e:i.R=e:this._=e,o&&u?(r=e.C,e.C=t.C,e.L=o,o.U=e,e!==u?(i=e.U,e.U=t.U,t=e.R,i.L=t,e.R=u,u.U=e):(e.U=i,i=e,t=e.R)):(r=t.C,t=e),t&&(t.U=i),!r)if(t&&t.C)t.C=!1;else{do{if(t===this._)break;if(t===i.L){if((n=i.R).C&&(n.C=!1,i.C=!0,Nc(this,i),n=i.R),n.L&&n.L.C||n.R&&n.R.C){n.R&&n.R.C||(n.L.C=!1,n.C=!0,Sc(this,n),n=i.R),n.C=i.C,i.C=n.R.C=!1,Nc(this,i),t=this._;break}}else if((n=i.L).C&&(n.C=!1,i.C=!0,Sc(this,i),n=i.L),n.L&&n.L.C||n.R&&n.R.C){n.L&&n.L.C||(n.R.C=!1,n.C=!0,Nc(this,n),n=i.L),n.C=i.C,i.C=n.L.C=!1,Sc(this,i),t=this._;break}n.C=!0,t=i,i=i.U}while(!t.C);t&&(t.C=!1)}}};var em,rm,im,om,um,am=[],cm=[],sm=1e-6,fm=1e-12;Kc.prototype={constructor:Kc,polygons:function(){var t=this.edges;return this.cells.map(function(n){var e=n.halfedges.map(function(e){return Dc(n,t[e])});return e.data=n.site.data,e})},triangles:function(){var t=[],n=this.edges;return this.cells.forEach(function(e,r){if(o=(i=e.halfedges).length)for(var i,o,u,a=e.site,c=-1,s=n[i[o-1]],f=s.left===a?s.right:s.left;++c=a)return null;var c=t-i.site[0],s=n-i.site[1],f=c*c+s*s;do{i=o.cells[r=u],u=null,i.halfedges.forEach(function(e){var r=o.edges[e],a=r.left;if(a!==i.site&&a||(a=r.right)){var c=t-a[0],s=n-a[1],l=c*c+s*s;lt?1:n>=t?0:NaN},t.deviation=_s,t.extent=ys,t.histogram=function(){function t(t){var o,u,a=t.length,c=new Array(a);for(o=0;ol;)h.pop(),--p;var d,v=new Array(p+1);for(o=0;o<=p;++o)(d=v[o]=[]).x0=o>0?h[o-1]:f,d.x1=o=e)for(r=e;++or&&(r=e)}else for(;++o=e)for(r=e;++or&&(r=e);return r},t.mean=function(t,n){var e,r=t.length,i=r,o=-1,u=0;if(null==n)for(;++o=o.length)return null!=e&&n.sort(e),null!=r?r(n):n;for(var c,s,f,l=-1,h=n.length,p=o[i++],d=we(),v=u();++lo.length)return t;var i,a=u[e-1];return null!=r&&e>=o.length?i=t.entries():(i=[],t.each(function(t,r){i.push({key:r,values:n(t,e)})})),null!=a?i.sort(function(t,n){return a(t.key,n.key)}):i}var e,r,i,o=[],u=[];return i={object:function(n){return t(n,0,Me,Te)},map:function(n){return t(n,0,ke,Ne)},entries:function(e){return n(t(e,0,ke,Ne),0)},key:function(t){return o.push(t),i},sortKeys:function(t){return u[o.length-1]=t,i},sortValues:function(t){return e=t,i},rollup:function(t){return r=t,i}}},t.set=Ee,t.map=we,t.keys=function(t){var n=[];for(var e in t)n.push(e);return n},t.values=function(t){var n=[];for(var e in t)n.push(t[e]);return n},t.entries=function(t){var n=[];for(var e in t)n.push({key:e,value:t[e]});return n},t.color=Tt,t.rgb=Et,t.hsl=Pt,t.lab=Ut,t.hcl=jt,t.cubehelix=$t,t.dispatch=h,t.drag=function(){function n(t){t.on("mousedown.drag",e).filter(bt).on("touchstart.drag",o).on("touchmove.drag",u).on("touchend.drag touchcancel.drag",a).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function e(){if(!p&&d.apply(this,arguments)){var n=c("mouse",v.apply(this,arguments),Ks,this,arguments);n&&(cf(t.event.view).on("mousemove.drag",r,!0).on("mouseup.drag",i,!0),lf(t.event.view),vt(),l=!1,s=t.event.clientX,f=t.event.clientY,n("start"))}}function r(){if(ff(),!l){var n=t.event.clientX-s,e=t.event.clientY-f;l=n*n+e*e>x}y.mouse("drag")}function i(){cf(t.event.view).on("mousemove.drag mouseup.drag",null),_t(t.event.view,l),ff(),y.mouse("end")}function o(){if(d.apply(this,arguments)){var n,e,r=t.event.changedTouches,i=v.apply(this,arguments),o=r.length;for(n=0;nc+p||is+p||or.index){var d=c-a.x-a.vx,v=s-a.y-a.vy,_=d*d+v*v;_t.r&&(t.r=t[n].r)}function r(){if(i){var n,e,r=i.length;for(o=new Array(r),n=0;n=f)){(t.data!==o||t.next)&&(0===i&&(i=rp(),p+=i*i),0===c&&(c=rp(),p+=c*c),p1?(null==n?l.remove(t):l.set(t,i(n)),o):l.get(t)},find:function(n,e,r){var i,o,u,a,c,s=0,f=t.length;for(null==r?r=1/0:r*=r,s=0;s1?(d.on(t,n),o):d.on(t)}}},t.forceX=function(t){function n(t){for(var n,e=0,u=r.length;exr(r[0],r[1])&&(r[1]=i[1]),xr(i[0],r[1])>xr(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(u=-1/0,n=0,r=o[e=o.length-1];n<=e;r=i,++n)i=o[n],(a=xr(r[1],i[0]))>u&&(u=a,Ap=i[0],zp=r[1])}return Dp=Op=null,Ap===1/0||Cp===1/0?[[NaN,NaN],[NaN,NaN]]:[[Ap,Cp],[zp,Pp]]},t.geoCentroid=function(t){Fp=Ip=Yp=Bp=jp=Hp=Xp=$p=Vp=Wp=Zp=0,Md(t,Ad);var n=Vp,e=Wp,r=Zp,i=n*n+e*e+r*r;return i<1e-12&&(n=Hp,e=Xp,r=$p,Ip=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?f:c).invert(t)},t.stream=function(t){return e&&r===t?e:e=Ri([c.stream(r=t),s.stream(t),f.stream(t)])},t.precision=function(t){return arguments.length?(c.precision(t),s.precision(t),f.precision(t),n()):c.precision()},t.scale=function(n){return arguments.length?(c.scale(n),s.scale(.35*n),f.scale(n),t.translate(c.translate())):c.scale()},t.translate=function(t){if(!arguments.length)return c.translate();var e=c.scale(),r=+t[0],a=+t[1];return i=c.translate(t).clipExtent([[r-.455*e,a-.238*e],[r+.455*e,a+.238*e]]).stream(l),o=s.translate([r-.307*e,a+.201*e]).clipExtent([[r-.425*e+ed,a+.12*e+ed],[r-.214*e-ed,a+.234*e-ed]]).stream(l),u=f.translate([r-.205*e,a+.212*e]).clipExtent([[r-.214*e+ed,a+.166*e+ed],[r-.115*e-ed,a+.234*e-ed]]).stream(l),n()},t.fitExtent=function(n,e){return Ti(t,n,e)},t.fitSize=function(n,e){return ki(t,n,e)},t.scale(1070)},t.geoAzimuthalEqualArea=function(){return Ei(Yv).scale(124.75).clipAngle(179.999)},t.geoAzimuthalEqualAreaRaw=Yv,t.geoAzimuthalEquidistant=function(){return Ei(Bv).scale(79.4188).clipAngle(179.999)},t.geoAzimuthalEquidistantRaw=Bv,t.geoConicConformal=function(){return Ci(Fi).scale(109.5).parallels([30,30])},t.geoConicConformalRaw=Fi,t.geoConicEqualArea=Fv,t.geoConicEqualAreaRaw=Pi,t.geoConicEquidistant=function(){return Ci(Yi).scale(131.154).center([0,13.9389])},t.geoConicEquidistantRaw=Yi,t.geoEquirectangular=function(){return Ei(Ii).scale(152.63)},t.geoEquirectangularRaw=Ii,t.geoGnomonic=function(){return Ei(Bi).scale(144.049).clipAngle(60)},t.geoGnomonicRaw=Bi,t.geoIdentity=function(){function t(){return i=o=null,u}var n,e,r,i,o,u,a=1,c=0,s=0,f=1,l=1,h=uv,p=null,d=uv;return u={stream:function(t){return i&&o===t?i:i=h(d(o=t))},clipExtent:function(i){return arguments.length?(d=null==i?(p=n=e=r=null,uv):Br(p=+i[0][0],n=+i[0][1],e=+i[1][0],r=+i[1][1]),t()):null==p?null:[[p,n],[e,r]]},scale:function(n){return arguments.length?(h=ji((a=+n)*f,a*l,c,s),t()):a},translate:function(n){return arguments.length?(h=ji(a*f,a*l,c=+n[0],s=+n[1]),t()):[c,s]},reflectX:function(n){return arguments.length?(h=ji(a*(f=n?-1:1),a*l,c,s),t()):f<0},reflectY:function(n){return arguments.length?(h=ji(a*f,a*(l=n?-1:1),c,s),t()):l<0},fitExtent:function(t,n){return Ti(u,t,n)},fitSize:function(t,n){return ki(u,t,n)}}},t.geoProjection=Ei,t.geoProjectionMutator=Ai,t.geoMercator=function(){return Di(Ui).scale(961/ud)},t.geoMercatorRaw=Ui,t.geoOrthographic=function(){return Ei(Hi).scale(249.5).clipAngle(90+ed)},t.geoOrthographicRaw=Hi,t.geoStereographic=function(){return Ei(Xi).scale(250).clipAngle(142)},t.geoStereographicRaw=Xi,t.geoTransverseMercator=function(){var t=Di($i),n=t.center,e=t.rotate;return t.center=function(t){return arguments.length?n([-t[1],t[0]]):(t=n(),[t[1],-t[0]])},t.rotate=function(t){return arguments.length?e([t[0],t[1],t.length>2?t[2]+90:90]):(t=e(),[t[0],t[1],t[2]-90])},e([0,0,90]).scale(159.155)},t.geoTransverseMercatorRaw=$i,t.geoRotation=jd,t.geoStream=Md,t.geoTransform=function(t){return{stream:wi(t)}},t.cluster=function(){function t(t){var o,u=0;t.eachAfter(function(t){var e=t.children;e?(t.x=Wi(e),t.y=Gi(e)):(t.x=o?u+=n(t,o):0,t.y=0,o=t)});var a=Qi(t),c=Ki(t),s=a.x-n(a,c)/2,f=c.x+n(c,a)/2;return t.eachAfter(i?function(n){n.x=(n.x-t.x)*e,n.y=(t.y-n.y)*r}:function(n){n.x=(n.x-s)/(f-s)*e,n.y=(1-(t.y?n.y/t.y:1))*r})}var n=Vi,e=1,r=1,i=!1;return t.separation=function(e){return arguments.length?(n=e,t):n},t.size=function(n){return arguments.length?(i=!1,e=+n[0],r=+n[1],t):i?null:[e,r]},t.nodeSize=function(n){return arguments.length?(i=!0,e=+n[0],r=+n[1],t):i?[e,r]:null},t},t.hierarchy=eo,t.pack=function(){function t(t){return t.x=e/2,t.y=r/2,n?t.eachBefore(No(n)).eachAfter(So(i,.5)).eachBefore(Eo(1)):t.eachBefore(No(ko)).eachAfter(So(To,1)).eachAfter(So(i,t.r/Math.min(e,r))).eachBefore(Eo(Math.min(e,r)/(2*t.r))),t}var n=null,e=1,r=1,i=To;return t.radius=function(e){return arguments.length?(n=wo(e),t):n},t.size=function(n){return arguments.length?(e=+n[0],r=+n[1],t):[e,r]},t.padding=function(n){return arguments.length?(i="function"==typeof n?n:Xv(+n),t):i},t},t.packSiblings=function(t){return bo(t),t},t.packEnclose=Hv,t.partition=function(){function t(t){var u=t.height+1;return t.x0=t.y0=i,t.x1=e,t.y1=r/u,t.eachBefore(n(r,u)),o&&t.eachBefore($v),t}function n(t,n){return function(e){e.children&&Vv(e,e.x0,t*(e.depth+1)/n,e.x1,t*(e.depth+2)/n);var r=e.x0,o=e.y0,u=e.x1-i,a=e.y1-i;u0)throw new Error("cycle");return o}var n=Ao,e=Co;return t.id=function(e){return arguments.length?(n=Mo(e),t):n},t.parentId=function(n){return arguments.length?(e=Mo(n),t):e},t},t.tree=function(){function t(t){var r=Oo(t);if(r.eachAfter(n),r.parent.m=-r.z,r.eachBefore(e),c)t.eachBefore(i);else{var s=t,f=t,l=t;t.eachBefore(function(t){t.xf.x&&(f=t),t.depth>l.depth&&(l=t)});var h=s===f?1:o(s,f)/2,p=h-s.x,d=u/(f.x+h+p),v=a/(l.depth||1);t.eachBefore(function(t){t.x=(t.x+p)*d,t.y=t.depth*v})}return t}function n(t){var n=t.children,e=t.parent.children,i=t.i?e[t.i-1]:null;if(n){qo(t);var u=(n[0].z+n[n.length-1].z)/2;i?(t.z=i.z+o(t._,i._),t.m=t.z-u):t.z=u}else i&&(t.z=i.z+o(t._,i._));t.parent.A=r(t,i,t.parent.A||e[0])}function e(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function r(t,n,e){if(n){for(var r,i=t,u=t,a=n,c=i.parent.children[0],s=i.m,f=u.m,l=a.m,h=c.m;a=Ro(a),i=Po(i),a&&i;)c=Po(c),(u=Ro(u)).a=t,(r=a.z+l-i.z-s+o(a._,i._))>0&&(Lo(Uo(a,t,e),t,r),s+=r,f+=r),l+=a.m,s+=i.m,h+=c.m,f+=u.m;a&&!Ro(u)&&(u.t=a,u.m+=l-f),i&&!Po(c)&&(c.t=i,c.m+=s-h,e=t)}return e}function i(t){t.x*=u,t.y=t.depth*a}var o=zo,u=1,a=1,c=null;return t.separation=function(n){return arguments.length?(o=n,t):o},t.size=function(n){return arguments.length?(c=!1,u=+n[0],a=+n[1],t):c?null:[u,a]},t.nodeSize=function(n){return arguments.length?(c=!0,u=+n[0],a=+n[1],t):c?[u,a]:null},t},t.treemap=function(){function t(t){return t.x0=t.y0=0,t.x1=i,t.y1=o,t.eachBefore(n),u=[0],r&&t.eachBefore($v),t}function n(t){var n=u[t.depth],r=t.x0+n,i=t.y0+n,o=t.x1-n,h=t.y1-n;o=n-1){var s=c[t];return s.x0=r,s.y0=i,s.x1=u,void(s.y1=a)}for(var l=f[t],h=e/2+l,p=t+1,d=n-1;p>>1;f[v]a-i){var g=(r*y+u*_)/e;o(t,p,_,r,i,g,a),o(p,n,y,g,i,u,a)}else{var m=(i*y+a*_)/e;o(t,p,_,r,i,u,m),o(p,n,y,r,m,u,a)}}var u,a,c=t.children,s=c.length,f=new Array(s+1);for(f[0]=a=u=0;u=0;--n)s.push(t[r[o[n]][2]]);for(n=+a;na!=s>a&&u<(c-e)*(a-r)/(s-r)+e&&(f=!f),c=e,s=r;return f},t.polygonLength=function(t){for(var n,e,r=-1,i=t.length,o=t[i-1],u=o[0],a=o[1],c=0;++r1)&&(t-=Math.floor(t));var n=Math.abs(t-.5);return Ky.h=360*t-100,Ky.s=1.5-1.5*n,Ky.l=.8-.9*n,Ky+""},t.interpolateWarm=Jy,t.interpolateCool=Qy,t.interpolateViridis=tg,t.interpolateMagma=ng,t.interpolateInferno=eg,t.interpolatePlasma=rg,t.scaleSequential=Ea,t.creator=Hs,t.local=m,t.matcher=Zs,t.mouse=Ks,t.namespace=js,t.namespaces=Bs,t.select=cf,t.selectAll=function(t){return"string"==typeof t?new pt([document.querySelectorAll(t)],[document.documentElement]):new pt([null==t?[]:t],af)},t.selection=dt,t.selector=tf,t.selectorAll=nf,t.style=B,t.touch=sf,t.touches=function(t,n){null==n&&(n=Js().touches);for(var e=0,r=n?n.length:0,i=new Array(r);eh;if(c||(c=t=ve()),lhg)if(d>vg-hg)c.moveTo(l*ag(h),l*fg(h)),c.arc(0,0,l,h,p,!v),f>hg&&(c.moveTo(f*ag(p),f*fg(p)),c.arc(0,0,f,p,h,v));else{var _,y,g=h,m=p,x=h,b=p,w=d,M=d,T=a.apply(this,arguments)/2,k=T>hg&&(i?+i.apply(this,arguments):lg(f*f+l*l)),N=sg(og(l-f)/2,+r.apply(this,arguments)),S=N,E=N;if(k>hg){var A=Ca(k/f*fg(T)),C=Ca(k/l*fg(T));(w-=2*A)>hg?(A*=v?1:-1,x+=A,b-=A):(w=0,x=b=(h+p)/2),(M-=2*C)>hg?(C*=v?1:-1,g+=C,m-=C):(M=0,g=m=(h+p)/2)}var z=l*ag(g),P=l*fg(g),R=f*ag(b),L=f*fg(b);if(N>hg){var q=l*ag(m),U=l*fg(m),D=f*ag(x),O=f*fg(x);if(dhg?Ua(z,P,D,O,q,U,R,L):[R,L],I=z-F[0],Y=P-F[1],B=q-F[0],j=U-F[1],H=1/fg(Aa((I*B+Y*j)/(lg(I*I+Y*Y)*lg(B*B+j*j)))/2),X=lg(F[0]*F[0]+F[1]*F[1]);S=sg(N,(f-X)/(H-1)),E=sg(N,(l-X)/(H+1))}}M>hg?E>hg?(_=Da(D,O,z,P,l,E,v),y=Da(q,U,R,L,l,E,v),c.moveTo(_.cx+_.x01,_.cy+_.y01),Ehg&&w>hg?S>hg?(_=Da(R,L,q,U,f,-S,v),y=Da(z,P,D,O,f,-S,v),c.lineTo(_.cx+_.x01,_.cy+_.y01),S0&&(p+=l);for(null!=e?d.sort(function(t,n){return e(v[t],v[n])}):null!=r&&d.sort(function(n,e){return r(t[n],t[e])}),a=0,s=p?(y-h*m)/p:0;a0?l*s:0)+m,v[c]={data:t[c],index:a,value:l,startAngle:_,endAngle:f,padAngle:g};return v}var n=xg,e=mg,r=null,i=ig(0),o=ig(vg),u=ig(0);return t.value=function(e){return arguments.length?(n="function"==typeof e?e:ig(+e),t):n},t.sortValues=function(n){return arguments.length?(e=n,r=null,t):e},t.sort=function(n){return arguments.length?(r=n,e=null,t):r},t.startAngle=function(n){return arguments.length?(i="function"==typeof n?n:ig(+n),t):i},t.endAngle=function(n){return arguments.length?(o="function"==typeof n?n:ig(+n),t):o},t.padAngle=function(n){return arguments.length?(u="function"==typeof n?n:ig(+n),t):u},t},t.areaRadial=Mg,t.radialArea=Mg,t.lineRadial=wg,t.radialLine=wg,t.pointRadial=Tg,t.linkHorizontal=function(){return $a(Va)},t.linkVertical=function(){return $a(Wa)},t.linkRadial=function(){var t=$a(Za);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t},t.symbol=function(){function t(){var t;if(r||(r=t=ve()),n.apply(this,arguments).draw(r,+e.apply(this,arguments)),t)return r=null,t+""||null}var n=ig(Ng),e=ig(64),r=null;return t.type=function(e){return arguments.length?(n="function"==typeof e?e:ig(e),t):n},t.size=function(n){return arguments.length?(e="function"==typeof n?n:ig(+n),t):e},t.context=function(n){return arguments.length?(r=null==n?null:n,t):r},t},t.symbols=jg,t.symbolCircle=Ng,t.symbolCross=Sg,t.symbolDiamond=Cg,t.symbolSquare=qg,t.symbolStar=Lg,t.symbolTriangle=Dg,t.symbolWye=Bg,t.curveBasisClosed=function(t){return new Qa(t)},t.curveBasisOpen=function(t){return new Ka(t)},t.curveBasis=function(t){return new Ja(t)},t.curveBundle=Xg,t.curveCardinalClosed=Vg,t.curveCardinalOpen=Wg,t.curveCardinal=$g,t.curveCatmullRomClosed=Gg,t.curveCatmullRomOpen=Jg,t.curveCatmullRom=Zg,t.curveLinearClosed=function(t){return new sc(t)},t.curveLinear=_g,t.curveMonotoneX=function(t){return new dc(t)},t.curveMonotoneY=function(t){return new vc(t)},t.curveNatural=function(t){return new yc(t)},t.curveStep=function(t){return new mc(t,.5)},t.curveStepAfter=function(t){return new mc(t,1)},t.curveStepBefore=function(t){return new mc(t,0)},t.stack=function(){function t(t){var o,u,a=n.apply(this,arguments),c=t.length,s=a.length,f=new Array(s);for(o=0;o0){for(var e,r,i,o=0,u=t[0].length;o1)for(var e,r,i,o,u,a,c=0,s=t[n[0]].length;c=0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=u,r[0]=u+=i):r[0]=o},t.stackOffsetNone=Qg,t.stackOffsetSilhouette=function(t,n){if((e=t.length)>0){for(var e,r=0,i=t[n[0]],o=i.length;r0&&(r=(e=t[n[0]]).length)>0){for(var e,r,i,o=0,u=1;uUl&&e.name===n)return new Gn([[t]],yh,n,+r)}return null},t.interrupt=jl,t.voronoi=function(){function t(t){return new Kc(t.map(function(r,i){var o=[Math.round(n(r,i,t)/sm)*sm,Math.round(e(r,i,t)/sm)*sm];return o.index=i,o.data=r,o}),r)}var n=wc,e=Mc,r=null;return t.polygons=function(n){return t(n).polygons()},t.links=function(n){return t(n).links()},t.triangles=function(n){return t(n).triangles()},t.x=function(e){return arguments.length?(n="function"==typeof e?e:nm(+e),t):n},t.y=function(n){return arguments.length?(e="function"==typeof n?n:nm(+n),t):e},t.extent=function(n){return arguments.length?(r=null==n?null:[[+n[0][0],+n[0][1]],[+n[1][0],+n[1][1]]],t):r&&[[r[0][0],r[0][1]],[r[1][0],r[1][1]]]},t.size=function(n){return arguments.length?(r=null==n?null:[[0,0],[+n[0],+n[1]]],t):r&&[r[1][0]-r[0][0],r[1][1]-r[0][1]]},t},t.zoom=function(){function n(t){t.property("__zoom",us).on("wheel.zoom",s).on("mousedown.zoom",f).on("dblclick.zoom",l).filter(cs).on("touchstart.zoom",p).on("touchmove.zoom",d).on("touchend.zoom touchcancel.zoom",v).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function e(t,n){return(n=Math.max(b,Math.min(w,n)))===t.k?t:new ns(n,t.x,t.y)}function r(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new ns(t.k,r,i)}function i(t,n){var e=t.invertX(n[0][0])-M,r=t.invertX(n[1][0])-T,i=t.invertY(n[0][1])-k,o=t.invertY(n[1][1])-S;return t.translate(r>e?(e+r)/2:Math.min(0,e)||Math.max(0,r),o>i?(i+o)/2:Math.min(0,i)||Math.max(0,o))}function o(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function u(t,n,e){t.on("start.zoom",function(){a(this,arguments).start()}).on("interrupt.zoom end.zoom",function(){a(this,arguments).end()}).tween("zoom",function(){var t=this,r=arguments,i=a(t,r),u=m.apply(t,r),c=e||o(u),s=Math.max(u[1][0]-u[0][0],u[1][1]-u[0][1]),f=t.__zoom,l="function"==typeof n?n.apply(t,r):n,h=A(f.invert(c).concat(s/f.k),l.invert(c).concat(s/l.k));return function(t){if(1===t)t=l;else{var n=h(t),e=s/n[2];t=new ns(e,c[0]-n[0]*e,c[1]-n[1]*e)}i.zoom(null,t)}})}function a(t,n){for(var e,r=0,i=C.length;rL}n.zoom("mouse",i(r(n.that.__zoom,n.mouse[0]=Ks(n.that),n.mouse[1]),n.extent))},!0).on("mouseup.zoom",function(){e.on("mousemove.zoom mouseup.zoom",null),_t(t.event.view,n.moved),pm(),n.end()},!0),o=Ks(this),u=t.event.clientX,c=t.event.clientY;lf(t.event.view),rs(),n.mouse=[o,this.__zoom.invert(o)],jl(this),n.start()}}function l(){if(g.apply(this,arguments)){var o=this.__zoom,a=Ks(this),c=o.invert(a),s=i(r(e(o,o.k*(t.event.shiftKey?.5:2)),a,c),m.apply(this,arguments));pm(),E>0?cf(this).transition().duration(E).call(u,s,a):cf(this).call(n.transform,s)}}function p(){if(g.apply(this,arguments)){var n,e,r,i,o=a(this,arguments),u=t.event.changedTouches,c=u.length;for(rs(),e=0;e)[^>]*$|#([\w\-]*)$)/,rsingleTag=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,rvalidchars=/^[\],:{}\s]*$/,rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g,rvalidescape=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,rvalidtokens=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,rmsPrefix=/^-ms-/,rdashAlpha=/-([\da-z])/gi,fcamelCase=function(all,letter){return(letter+"").toUpperCase()},DOMContentLoaded=function(){if(document.addEventListener){document.removeEventListener("DOMContentLoaded",DOMContentLoaded,false);jQuery.ready()}else if(document.readyState==="complete"){document.detachEvent("onreadystatechange",DOMContentLoaded);jQuery.ready()}},class2type={};jQuery.fn=jQuery.prototype={constructor:jQuery,init:function(selector,context,rootjQuery){var match,elem,ret,doc;if(!selector){return this}if(selector.nodeType){this.context=this[0]=selector;this.length=1;return this}if(typeof selector==="string"){if(selector.charAt(0)==="<"&&selector.charAt(selector.length-1)===">"&&selector.length>=3){match=[null,selector,null]}else{match=rquickExpr.exec(selector)}if(match&&(match[1]||!context)){if(match[1]){context=context instanceof jQuery?context[0]:context;doc=context&&context.nodeType?context.ownerDocument||context:document;selector=jQuery.parseHTML(match[1],doc,true);if(rsingleTag.test(match[1])&&jQuery.isPlainObject(context)){this.attr.call(selector,context,true)}return jQuery.merge(this,selector)}else{elem=document.getElementById(match[2]);if(elem&&elem.parentNode){if(elem.id!==match[2]){return rootjQuery.find(selector)}this.length=1;this[0]=elem}this.context=document;this.selector=selector;return this}}else if(!context||context.jquery){return(context||rootjQuery).find(selector)}else{return this.constructor(context).find(selector)}}else if(jQuery.isFunction(selector)){return rootjQuery.ready(selector)}if(selector.selector!==undefined){this.selector=selector.selector;this.context=selector.context}return jQuery.makeArray(selector,this)},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return core_slice.call(this)},get:function(num){return num==null?this.toArray():num<0?this[this.length+num]:this[num]},pushStack:function(elems,name,selector){var ret=jQuery.merge(this.constructor(),elems);ret.prevObject=this;ret.context=this.context;if(name==="find"){ret.selector=this.selector+(this.selector?" ":"")+selector}else if(name){ret.selector=this.selector+"."+name+"("+selector+")"}return ret},each:function(callback,args){return jQuery.each(this,callback,args)},ready:function(fn){jQuery.ready.promise().done(fn);return this},eq:function(i){i=+i;return i===-1?this.slice(i):this.slice(i,i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(core_slice.apply(this,arguments),"slice",core_slice.call(arguments).join(","))},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem)}))},end:function(){return this.prevObject||this.constructor(null)},push:core_push,sort:[].sort,splice:[].splice};jQuery.fn.init.prototype=jQuery.fn;jQuery.extend=jQuery.fn.extend=function(){var options,name,src,copy,copyIsArray,clone,target=arguments[0]||{},i=1,length=arguments.length,deep=false;if(typeof target==="boolean"){deep=target;target=arguments[1]||{};i=2}if(typeof target!=="object"&&!jQuery.isFunction(target)){target={}}if(length===i){target=this;--i}for(;i0){return}readyList.resolveWith(document,[jQuery]);if(jQuery.fn.trigger){jQuery(document).trigger("ready").off("ready")}},isFunction:function(obj){return jQuery.type(obj)==="function"},isArray:Array.isArray||function(obj){return jQuery.type(obj)==="array"},isWindow:function(obj){return obj!=null&&obj==obj.window},isNumeric:function(obj){return!isNaN(parseFloat(obj))&&isFinite(obj)},type:function(obj){return obj==null?String(obj):class2type[core_toString.call(obj)]||"object"},isPlainObject:function(obj){if(!obj||jQuery.type(obj)!=="object"||obj.nodeType||jQuery.isWindow(obj)){return false}try{if(obj.constructor&&!core_hasOwn.call(obj,"constructor")&&!core_hasOwn.call(obj.constructor.prototype,"isPrototypeOf")){return false}}catch(e){return false}var key;for(key in obj){}return key===undefined||core_hasOwn.call(obj,key)},isEmptyObject:function(obj){var name;for(name in obj){return false}return true},error:function(msg){throw new Error(msg)},parseHTML:function(data,context,scripts){var parsed;if(!data||typeof data!=="string"){return null}if(typeof context==="boolean"){scripts=context;context=0}context=context||document;if(parsed=rsingleTag.exec(data)){return[context.createElement(parsed[1])]}parsed=jQuery.buildFragment([data],context,scripts?null:[]);return jQuery.merge([],(parsed.cacheable?jQuery.clone(parsed.fragment):parsed.fragment).childNodes)},parseJSON:function(data){if(!data||typeof data!=="string"){return null}data=jQuery.trim(data);if(window.JSON&&window.JSON.parse){return window.JSON.parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}jQuery.error("Invalid JSON: "+data)},parseXML:function(data){var xml,tmp;if(!data||typeof data!=="string"){return null}try{if(window.DOMParser){tmp=new DOMParser;xml=tmp.parseFromString(data,"text/xml")}else{xml=new ActiveXObject("Microsoft.XMLDOM");xml.async="false";xml.loadXML(data)}}catch(e){xml=undefined}if(!xml||!xml.documentElement||xml.getElementsByTagName("parsererror").length){jQuery.error("Invalid XML: "+data)}return xml},noop:function(){},globalEval:function(data){if(data&&core_rnotwhite.test(data)){(window.execScript||function(data){window["eval"].call(window,data)})(data)}},camelCase:function(string){return string.replace(rmsPrefix,"ms-").replace(rdashAlpha,fcamelCase)},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toLowerCase()===name.toLowerCase()},each:function(obj,callback,args){var name,i=0,length=obj.length,isObj=length===undefined||jQuery.isFunction(obj);if(args){if(isObj){for(name in obj){if(callback.apply(obj[name],args)===false){break}}}else{for(;i0&&elems[0]&&elems[length-1]||length===0||jQuery.isArray(elems));if(isArray){for(;i-1){list.splice(index,1);if(firing){if(index<=firingLength){firingLength--}if(index<=firingIndex){firingIndex--}}}})}return this},has:function(fn){return jQuery.inArray(fn,list)>-1},empty:function(){list=[];return this},disable:function(){list=stack=memory=undefined;return this},disabled:function(){return!list},lock:function(){stack=undefined;if(!memory){self.disable()}return this},locked:function(){return!stack},fireWith:function(context,args){args=args||[];args=[context,args.slice?args.slice():args];if(list&&(!fired||stack)){if(firing){stack.push(args)}else{fire(args)}}return this},fire:function(){self.fireWith(this,arguments);return this},fired:function(){return!!fired}};return self};jQuery.extend({Deferred:function(func){var tuples=[["resolve","done",jQuery.Callbacks("once memory"),"resolved"],["reject","fail",jQuery.Callbacks("once memory"),"rejected"],["notify","progress",jQuery.Callbacks("memory")]],state="pending",promise={state:function(){return state},always:function(){deferred.done(arguments).fail(arguments);return this},then:function(){var fns=arguments;return jQuery.Deferred(function(newDefer){jQuery.each(tuples,function(i,tuple){var action=tuple[0],fn=fns[i];deferred[tuple[1]](jQuery.isFunction(fn)?function(){var returned=fn.apply(this,arguments);if(returned&&jQuery.isFunction(returned.promise)){returned.promise().done(newDefer.resolve).fail(newDefer.reject).progress(newDefer.notify)}else{newDefer[action+"With"](this===deferred?newDefer:this,[returned])}}:newDefer[action])});fns=null}).promise()},promise:function(obj){return obj!=null?jQuery.extend(obj,promise):promise}},deferred={};promise.pipe=promise.then;jQuery.each(tuples,function(i,tuple){var list=tuple[2],stateString=tuple[3];promise[tuple[1]]=list.add;if(stateString){list.add(function(){state=stateString},tuples[i^1][2].disable,tuples[2][2].lock)}deferred[tuple[0]]=list.fire;deferred[tuple[0]+"With"]=list.fireWith});promise.promise(deferred);if(func){func.call(deferred,deferred)}return deferred},when:function(subordinate){var i=0,resolveValues=core_slice.call(arguments),length=resolveValues.length,remaining=length!==1||subordinate&&jQuery.isFunction(subordinate.promise)?length:0,deferred=remaining===1?subordinate:jQuery.Deferred(),updateFunc=function(i,contexts,values){return function(value){contexts[i]=this;values[i]=arguments.length>1?core_slice.call(arguments):value;if(values===progressValues){deferred.notifyWith(contexts,values)}else if(!--remaining){deferred.resolveWith(contexts,values)}}},progressValues,progressContexts,resolveContexts;if(length>1){progressValues=new Array(length);progressContexts=new Array(length);resolveContexts=new Array(length);for(;i
a";all=div.getElementsByTagName("*");a=div.getElementsByTagName("a")[0];if(!all||!a||!all.length){return{}}select=document.createElement("select");opt=select.appendChild(document.createElement("option"));input=div.getElementsByTagName("input")[0];a.style.cssText="top:1px;float:left;opacity:.5";support={leadingWhitespace:div.firstChild.nodeType===3,tbody:!div.getElementsByTagName("tbody").length,htmlSerialize:!!div.getElementsByTagName("link").length,style:/top/.test(a.getAttribute("style")),hrefNormalized:a.getAttribute("href")==="/a",opacity:/^0.5/.test(a.style.opacity),cssFloat:!!a.style.cssFloat,checkOn:input.value==="on",optSelected:opt.selected,getSetAttribute:div.className!=="t",enctype:!!document.createElement("form").enctype,html5Clone:document.createElement("nav").cloneNode(true).outerHTML!=="<:nav>",boxModel:document.compatMode==="CSS1Compat",submitBubbles:true,changeBubbles:true,focusinBubbles:false,deleteExpando:true,noCloneEvent:true,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableMarginRight:true,boxSizingReliable:true,pixelPosition:false};input.checked=true;support.noCloneChecked=input.cloneNode(true).checked;select.disabled=true;support.optDisabled=!opt.disabled;try{delete div.test}catch(e){support.deleteExpando=false}if(!div.addEventListener&&div.attachEvent&&div.fireEvent){div.attachEvent("onclick",clickFn=function(){support.noCloneEvent=false});div.cloneNode(true).fireEvent("onclick");div.detachEvent("onclick",clickFn)}input=document.createElement("input");input.value="t";input.setAttribute("type","radio");support.radioValue=input.value==="t";input.setAttribute("checked","checked");input.setAttribute("name","t");div.appendChild(input);fragment=document.createDocumentFragment();fragment.appendChild(div.lastChild);support.checkClone=fragment.cloneNode(true).cloneNode(true).lastChild.checked;support.appendChecked=input.checked;fragment.removeChild(input);fragment.appendChild(div);if(div.attachEvent){for(i in{submit:true,change:true,focusin:true}){eventName="on"+i;isSupported=eventName in div;if(!isSupported){div.setAttribute(eventName,"return;");isSupported=typeof div[eventName]==="function"}support[i+"Bubbles"]=isSupported}}jQuery(function(){var container,div,tds,marginDiv,divReset="padding:0;margin:0;border:0;display:block;overflow:hidden;",body=document.getElementsByTagName("body")[0];if(!body){return}container=document.createElement("div");container.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";body.insertBefore(container,body.firstChild);div=document.createElement("div");container.appendChild(div);div.innerHTML="
t
";tds=div.getElementsByTagName("td");tds[0].style.cssText="padding:0;margin:0;border:0;display:none";isSupported=tds[0].offsetHeight===0;tds[0].style.display="";tds[1].style.display="none";support.reliableHiddenOffsets=isSupported&&tds[0].offsetHeight===0;div.innerHTML="";div.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";support.boxSizing=div.offsetWidth===4;support.doesNotIncludeMarginInBodyOffset=body.offsetTop!==1;if(window.getComputedStyle){support.pixelPosition=(window.getComputedStyle(div,null)||{}).top!=="1%";support.boxSizingReliable=(window.getComputedStyle(div,null)||{width:"4px"}).width==="4px";marginDiv=document.createElement("div");marginDiv.style.cssText=div.style.cssText=divReset;marginDiv.style.marginRight=marginDiv.style.width="0";div.style.width="1px";div.appendChild(marginDiv);support.reliableMarginRight=!parseFloat((window.getComputedStyle(marginDiv,null)||{}).marginRight)}if(typeof div.style.zoom!=="undefined"){div.innerHTML="";div.style.cssText=divReset+"width:1px;padding:1px;display:inline;zoom:1";support.inlineBlockNeedsLayout=div.offsetWidth===3;div.style.display="block";div.style.overflow="visible";div.innerHTML="
";div.firstChild.style.width="5px";support.shrinkWrapBlocks=div.offsetWidth!==3;container.style.zoom=1}body.removeChild(container);container=div=tds=marginDiv=null});fragment.removeChild(div);all=a=select=opt=input=fragment=div=null;return support}();var rbrace=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,rmultiDash=/([A-Z])/g;jQuery.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(jQuery.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},hasData:function(elem){elem=elem.nodeType?jQuery.cache[elem[jQuery.expando]]:elem[jQuery.expando];return!!elem&&!isEmptyDataObject(elem)},data:function(elem,name,data,pvt){if(!jQuery.acceptData(elem)){return}var thisCache,ret,internalKey=jQuery.expando,getByName=typeof name==="string",isNode=elem.nodeType,cache=isNode?jQuery.cache:elem,id=isNode?elem[internalKey]:elem[internalKey]&&internalKey;if((!id||!cache[id]||!pvt&&!cache[id].data)&&getByName&&data===undefined){return}if(!id){if(isNode){elem[internalKey]=id=jQuery.deletedIds.pop()||jQuery.guid++}else{id=internalKey}}if(!cache[id]){cache[id]={};if(!isNode){cache[id].toJSON=jQuery.noop}}if(typeof name==="object"||typeof name==="function"){if(pvt){cache[id]=jQuery.extend(cache[id],name)}else{cache[id].data=jQuery.extend(cache[id].data,name)}}thisCache=cache[id];if(!pvt){if(!thisCache.data){thisCache.data={}}thisCache=thisCache.data}if(data!==undefined){thisCache[jQuery.camelCase(name)]=data}if(getByName){ret=thisCache[name];if(ret==null){ret=thisCache[jQuery.camelCase(name)]}}else{ret=thisCache}return ret},removeData:function(elem,name,pvt){if(!jQuery.acceptData(elem)){return}var thisCache,i,l,isNode=elem.nodeType,cache=isNode?jQuery.cache:elem,id=isNode?elem[jQuery.expando]:jQuery.expando;if(!cache[id]){return}if(name){thisCache=pvt?cache[id]:cache[id].data;if(thisCache){if(!jQuery.isArray(name)){if(name in thisCache){name=[name]}else{name=jQuery.camelCase(name);if(name in thisCache){name=[name]}else{name=name.split(" ")}}}for(i=0,l=name.length;i1,null,false)},removeData:function(key){return this.each(function(){jQuery.removeData(this,key)})}});function dataAttr(elem,key,data){if(data===undefined&&elem.nodeType===1){var name="data-"+key.replace(rmultiDash,"-$1").toLowerCase();data=elem.getAttribute(name);if(typeof data==="string"){try{data=data==="true"?true:data==="false"?false:data==="null"?null:+data+""===data?+data:rbrace.test(data)?jQuery.parseJSON(data):data}catch(e){}jQuery.data(elem,key,data)}else{data=undefined}}return data}function isEmptyDataObject(obj){var name;for(name in obj){if(name==="data"&&jQuery.isEmptyObject(obj[name])){continue}if(name!=="toJSON"){return false}}return true}jQuery.extend({queue:function(elem,type,data){var queue;if(elem){type=(type||"fx")+"queue";queue=jQuery._data(elem,type);if(data){if(!queue||jQuery.isArray(data)){queue=jQuery._data(elem,type,jQuery.makeArray(data))}else{queue.push(data)}}return queue||[]}},dequeue:function(elem,type){type=type||"fx";var queue=jQuery.queue(elem,type),startLength=queue.length,fn=queue.shift(),hooks=jQuery._queueHooks(elem,type),next=function(){jQuery.dequeue(elem,type)};if(fn==="inprogress"){fn=queue.shift();startLength--}if(fn){if(type==="fx"){queue.unshift("inprogress")}delete hooks.stop;fn.call(elem,next,hooks)}if(!startLength&&hooks){hooks.empty.fire()}},_queueHooks:function(elem,type){var key=type+"queueHooks";return jQuery._data(elem,key)||jQuery._data(elem,key,{empty:jQuery.Callbacks("once memory").add(function(){jQuery.removeData(elem,type+"queue",true);jQuery.removeData(elem,key,true)})})}});jQuery.fn.extend({queue:function(type,data){var setter=2;if(typeof type!=="string"){data=type;type="fx";setter--}if(arguments.length1)},removeAttr:function(name){return this.each(function(){jQuery.removeAttr(this,name)})},prop:function(name,value){return jQuery.access(this,jQuery.prop,name,value,arguments.length>1)},removeProp:function(name){name=jQuery.propFix[name]||name;return this.each(function(){try{this[name]=undefined;delete this[name]}catch(e){}})},addClass:function(value){var classNames,i,l,elem,setClass,c,cl;if(jQuery.isFunction(value)){return this.each(function(j){jQuery(this).addClass(value.call(this,j,this.className))})}if(value&&typeof value==="string"){classNames=value.split(core_rspace);for(i=0,l=this.length;i=0){className=className.replace(" "+removes[c]+" "," ")}}elem.className=value?jQuery.trim(className):""}}}return this},toggleClass:function(value,stateVal){var type=typeof value,isBool=typeof stateVal==="boolean";if(jQuery.isFunction(value)){return this.each(function(i){jQuery(this).toggleClass(value.call(this,i,this.className,stateVal),stateVal)})}return this.each(function(){if(type==="string"){var className,i=0,self=jQuery(this),state=stateVal,classNames=value.split(core_rspace);while(className=classNames[i++]){state=isBool?state:!self.hasClass(className);self[state?"addClass":"removeClass"](className)}}else if(type==="undefined"||type==="boolean"){if(this.className){jQuery._data(this,"__className__",this.className)}this.className=this.className||value===false?"":jQuery._data(this,"__className__")||""}})},hasClass:function(selector){var className=" "+selector+" ",i=0,l=this.length;for(;i=0){return true}}return false},val:function(value){var hooks,ret,isFunction,elem=this[0];if(!arguments.length){if(elem){hooks=jQuery.valHooks[elem.type]||jQuery.valHooks[elem.nodeName.toLowerCase()];if(hooks&&"get"in hooks&&(ret=hooks.get(elem,"value"))!==undefined){return ret}ret=elem.value;return typeof ret==="string"?ret.replace(rreturn,""):ret==null?"":ret}return}isFunction=jQuery.isFunction(value);return this.each(function(i){var val,self=jQuery(this);if(this.nodeType!==1){return}if(isFunction){val=value.call(this,i,self.val())}else{val=value}if(val==null){val=""}else if(typeof val==="number"){val+=""}else if(jQuery.isArray(val)){val=jQuery.map(val,function(value){return value==null?"":value+""})}hooks=jQuery.valHooks[this.type]||jQuery.valHooks[this.nodeName.toLowerCase()];if(!hooks||!("set"in hooks)||hooks.set(this,val,"value")===undefined){this.value=val}})}});jQuery.extend({valHooks:{option:{get:function(elem){var val=elem.attributes.value;return!val||val.specified?elem.value:elem.text}},select:{get:function(elem){var value,option,options=elem.options,index=elem.selectedIndex,one=elem.type==="select-one"||index<0,values=one?null:[],max=one?index+1:options.length,i=index<0?max:one?index:0;for(;i=0});if(!values.length){elem.selectedIndex=-1}return values}}},attrFn:{},attr:function(elem,name,value,pass){var ret,hooks,notxml,nType=elem.nodeType;if(!elem||nType===3||nType===8||nType===2){return}if(pass&&jQuery.isFunction(jQuery.fn[name])){return jQuery(elem)[name](value)}if(typeof elem.getAttribute==="undefined"){return jQuery.prop(elem,name,value)}notxml=nType!==1||!jQuery.isXMLDoc(elem);if(notxml){name=name.toLowerCase();hooks=jQuery.attrHooks[name]||(rboolean.test(name)?boolHook:nodeHook)}if(value!==undefined){if(value===null){jQuery.removeAttr(elem,name);return}else if(hooks&&"set"in hooks&¬xml&&(ret=hooks.set(elem,value,name))!==undefined){return ret}else{elem.setAttribute(name,value+"");return value}}else if(hooks&&"get"in hooks&¬xml&&(ret=hooks.get(elem,name))!==null){return ret}else{ret=elem.getAttribute(name);return ret===null?undefined:ret}},removeAttr:function(elem,value){var propName,attrNames,name,isBool,i=0;if(value&&elem.nodeType===1){attrNames=value.split(core_rspace);for(;i=0}}})});var rformElems=/^(?:textarea|input|select)$/i,rtypenamespace=/^([^\.]*|)(?:\.(.+)|)$/,rhoverHack=/(?:^|\s)hover(\.\S+|)\b/,rkeyEvent=/^key/,rmouseEvent=/^(?:mouse|contextmenu)|click/,rfocusMorph=/^(?:focusinfocus|focusoutblur)$/,hoverHack=function(events){return jQuery.event.special.hover?events:events.replace(rhoverHack,"mouseenter$1 mouseleave$1")};jQuery.event={add:function(elem,types,handler,data,selector){var elemData,eventHandle,events,t,tns,type,namespaces,handleObj,handleObjIn,handlers,special;if(elem.nodeType===3||elem.nodeType===8||!types||!handler||!(elemData=jQuery._data(elem))){return}if(handler.handler){handleObjIn=handler;handler=handleObjIn.handler;selector=handleObjIn.selector}if(!handler.guid){handler.guid=jQuery.guid++}events=elemData.events;if(!events){elemData.events=events={}}eventHandle=elemData.handle;if(!eventHandle){elemData.handle=eventHandle=function(e){return typeof jQuery!=="undefined"&&(!e||jQuery.event.triggered!==e.type)?jQuery.event.dispatch.apply(eventHandle.elem,arguments):undefined};eventHandle.elem=elem}types=jQuery.trim(hoverHack(types)).split(" ");for(t=0;t=0){type=type.slice(0,-1);exclusive=true}if(type.indexOf(".")>=0){namespaces=type.split(".");type=namespaces.shift();namespaces.sort()}if((!elem||jQuery.event.customEvent[type])&&!jQuery.event.global[type]){return}event=typeof event==="object"?event[jQuery.expando]?event:new jQuery.Event(type,event):new jQuery.Event(type);event.type=type;event.isTrigger=true;event.exclusive=exclusive;event.namespace=namespaces.join(".");event.namespace_re=event.namespace?new RegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)"):null;ontype=type.indexOf(":")<0?"on"+type:"";if(!elem){cache=jQuery.cache;for(i in cache){if(cache[i].events&&cache[i].events[type]){jQuery.event.trigger(event,data,cache[i].handle.elem,true)}}return}event.result=undefined;if(!event.target){event.target=elem}data=data!=null?jQuery.makeArray(data):[];data.unshift(event);special=jQuery.event.special[type]||{};if(special.trigger&&special.trigger.apply(elem,data)===false){return}eventPath=[[elem,special.bindType||type]];if(!onlyHandlers&&!special.noBubble&&!jQuery.isWindow(elem)){bubbleType=special.delegateType||type;cur=rfocusMorph.test(bubbleType+type)?elem:elem.parentNode;for(old=elem;cur;cur=cur.parentNode){eventPath.push([cur,bubbleType]);old=cur}if(old===(elem.ownerDocument||document)){eventPath.push([old.defaultView||old.parentWindow||window,bubbleType])}}for(i=0;i=0:jQuery.find(sel,this,null,[cur]).length}if(selMatch[sel]){matches.push(handleObj)}}if(matches.length){handlerQueue.push({elem:cur,matches:matches})}}}}if(handlers.length>delegateCount){handlerQueue.push({elem:this,matches:handlers.slice(delegateCount)})}for(i=0;i0?this.on(name,null,data,fn):this.trigger(name)};if(rkeyEvent.test(name)){jQuery.event.fixHooks[name]=jQuery.event.keyHooks}if(rmouseEvent.test(name)){jQuery.event.fixHooks[name]=jQuery.event.mouseHooks}});(function(window,undefined){var cachedruns,assertGetIdNotName,Expr,getText,isXML,contains,compile,sortOrder,hasDuplicate,outermostContext,baseHasDuplicate=true,strundefined="undefined",expando=("sizcache"+Math.random()).replace(".",""),Token=String,document=window.document,docElem=document.documentElement,dirruns=0,done=0,pop=[].pop,push=[].push,slice=[].slice,indexOf=[].indexOf||function(elem){var i=0,len=this.length;for(;iExpr.cacheLength){delete cache[keys.shift()]}return cache[key+" "]=value},cache)},classCache=createCache(),tokenCache=createCache(),compilerCache=createCache(),whitespace="[\\x20\\t\\r\\n\\f]",characterEncoding="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",identifier=characterEncoding.replace("w","w#"),operators="([*^$|!~]?=)",attributes="\\["+whitespace+"*("+characterEncoding+")"+whitespace+"*(?:"+operators+whitespace+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+identifier+")|)|)"+whitespace+"*\\]",pseudos=":("+characterEncoding+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+attributes+")|[^:]|\\\\.)*|.*))\\)|)",pos=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+whitespace+"*((?:-\\d)?\\d*)"+whitespace+"*\\)|)(?=[^-]|$)",rtrim=new RegExp("^"+whitespace+"+|((?:^|[^\\\\])(?:\\\\.)*)"+whitespace+"+$","g"),rcomma=new RegExp("^"+whitespace+"*,"+whitespace+"*"),rcombinators=new RegExp("^"+whitespace+"*([\\x20\\t\\r\\n\\f>+~])"+whitespace+"*"),rpseudo=new RegExp(pseudos),rquickExpr=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,rnot=/^:not/,rsibling=/[\x20\t\r\n\f]*[+~]/,rendsWithNot=/:not\($/,rheader=/h\d/i,rinputs=/input|select|textarea|button/i,rbackslash=/\\(?!\\)/g,matchExpr={ID:new RegExp("^#("+characterEncoding+")"),CLASS:new RegExp("^\\.("+characterEncoding+")"),NAME:new RegExp("^\\[name=['\"]?("+characterEncoding+")['\"]?\\]"),TAG:new RegExp("^("+characterEncoding.replace("w","w*")+")"),ATTR:new RegExp("^"+attributes),PSEUDO:new RegExp("^"+pseudos),POS:new RegExp(pos,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+whitespace+"*(even|odd|(([+-]|)(\\d*)n|)"+whitespace+"*(?:([+-]|)"+whitespace+"*(\\d+)|))"+whitespace+"*\\)|)","i"),needsContext:new RegExp("^"+whitespace+"*[>+~]|"+pos,"i")},assert=function(fn){var div=document.createElement("div");try{return fn(div)}catch(e){return false}finally{div=null}},assertTagNameNoComments=assert(function(div){div.appendChild(document.createComment(""));return!div.getElementsByTagName("*").length}),assertHrefNotNormalized=assert(function(div){div.innerHTML="";return div.firstChild&&typeof div.firstChild.getAttribute!==strundefined&&div.firstChild.getAttribute("href")==="#"}),assertAttributes=assert(function(div){div.innerHTML="";var type=typeof div.lastChild.getAttribute("multiple");return type!=="boolean"&&type!=="string"}),assertUsableClassName=assert(function(div){div.innerHTML="";if(!div.getElementsByClassName||!div.getElementsByClassName("e").length){return false}div.lastChild.className="e";return div.getElementsByClassName("e").length===2}),assertUsableName=assert(function(div){div.id=expando+0;div.innerHTML="
";docElem.insertBefore(div,docElem.firstChild);var pass=document.getElementsByName&&document.getElementsByName(expando).length===2+document.getElementsByName(expando+0).length;assertGetIdNotName=!document.getElementById(expando);docElem.removeChild(div);return pass});try{slice.call(docElem.childNodes,0)[0].nodeType}catch(e){slice=function(i){var elem,results=[];for(;elem=this[i];i++){results.push(elem)}return results}}function Sizzle(selector,context,results,seed){results=results||[];context=context||document;var match,elem,xml,m,nodeType=context.nodeType;if(!selector||typeof selector!=="string"){return results}if(nodeType!==1&&nodeType!==9){return[]}xml=isXML(context);if(!xml&&!seed){if(match=rquickExpr.exec(selector)){if(m=match[1]){if(nodeType===9){elem=context.getElementById(m);if(elem&&elem.parentNode){if(elem.id===m){results.push(elem);return results}}else{return results}}else{if(context.ownerDocument&&(elem=context.ownerDocument.getElementById(m))&&contains(context,elem)&&elem.id===m){results.push(elem);return results}}}else if(match[2]){push.apply(results,slice.call(context.getElementsByTagName(selector),0));return results}else if((m=match[3])&&assertUsableClassName&&context.getElementsByClassName){push.apply(results,slice.call(context.getElementsByClassName(m),0));return results}}}return select(selector.replace(rtrim,"$1"),context,results,seed,xml)}Sizzle.matches=function(expr,elements){return Sizzle(expr,null,null,elements)};Sizzle.matchesSelector=function(elem,expr){return Sizzle(expr,null,null,[elem]).length>0};function createInputPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return name==="input"&&elem.type===type}}function createButtonPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return(name==="input"||name==="button")&&elem.type===type}}function createPositionalPseudo(fn){return markFunction(function(argument){argument=+argument;return markFunction(function(seed,matches){var j,matchIndexes=fn([],seed.length,argument),i=matchIndexes.length;while(i--){if(seed[j=matchIndexes[i]]){seed[j]=!(matches[j]=seed[j])}}})})}getText=Sizzle.getText=function(elem){var node,ret="",i=0,nodeType=elem.nodeType;if(nodeType){if(nodeType===1||nodeType===9||nodeType===11){if(typeof elem.textContent==="string"){return elem.textContent}else{for(elem=elem.firstChild;elem;elem=elem.nextSibling){ret+=getText(elem)}}}else if(nodeType===3||nodeType===4){return elem.nodeValue}}else{for(;node=elem[i];i++){ret+=getText(node)}}return ret};isXML=Sizzle.isXML=function(elem){var documentElement=elem&&(elem.ownerDocument||elem).documentElement;return documentElement?documentElement.nodeName!=="HTML":false};contains=Sizzle.contains=docElem.contains?function(a,b){var adown=a.nodeType===9?a.documentElement:a,bup=b&&b.parentNode;return a===bup||!!(bup&&bup.nodeType===1&&adown.contains&&adown.contains(bup))}:docElem.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode){if(b===a){return true}}return false};Sizzle.attr=function(elem,name){var val,xml=isXML(elem);if(!xml){name=name.toLowerCase()}if(val=Expr.attrHandle[name]){return val(elem)}if(xml||assertAttributes){return elem.getAttribute(name)}val=elem.getAttributeNode(name);return val?typeof elem[name]==="boolean"?elem[name]?name:null:val.specified?val.value:null:null};Expr=Sizzle.selectors={cacheLength:50,createPseudo:markFunction,match:matchExpr,attrHandle:assertHrefNotNormalized?{}:{href:function(elem){return elem.getAttribute("href",2)},type:function(elem){return elem.getAttribute("type")}},find:{ID:assertGetIdNotName?function(id,context,xml){if(typeof context.getElementById!==strundefined&&!xml){var m=context.getElementById(id);return m&&m.parentNode?[m]:[]}}:function(id,context,xml){if(typeof context.getElementById!==strundefined&&!xml){var m=context.getElementById(id);return m?m.id===id||typeof m.getAttributeNode!==strundefined&&m.getAttributeNode("id").value===id?[m]:undefined:[]}},TAG:assertTagNameNoComments?function(tag,context){if(typeof context.getElementsByTagName!==strundefined){return context.getElementsByTagName(tag)}}:function(tag,context){var results=context.getElementsByTagName(tag);if(tag==="*"){var elem,tmp=[],i=0;for(;elem=results[i];i++){if(elem.nodeType===1){tmp.push(elem)}}return tmp}return results},NAME:assertUsableName&&function(tag,context){if(typeof context.getElementsByName!==strundefined){return context.getElementsByName(name)}},CLASS:assertUsableClassName&&function(className,context,xml){if(typeof context.getElementsByClassName!==strundefined&&!xml){return context.getElementsByClassName(className)}}},relative:{">":{dir:"parentNode",first:true}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:true},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(match){match[1]=match[1].replace(rbackslash,"");match[3]=(match[4]||match[5]||"").replace(rbackslash,"");if(match[2]==="~="){match[3]=" "+match[3]+" "}return match.slice(0,4)},CHILD:function(match){match[1]=match[1].toLowerCase();if(match[1]==="nth"){if(!match[2]){Sizzle.error(match[0])}match[3]=+(match[3]?match[4]+(match[5]||1):2*(match[2]==="even"||match[2]==="odd"));match[4]=+(match[6]+match[7]||match[2]==="odd")}else if(match[2]){Sizzle.error(match[0])}return match},PSEUDO:function(match){var unquoted,excess;if(matchExpr["CHILD"].test(match[0])){return null}if(match[3]){match[2]=match[3]}else if(unquoted=match[4]){if(rpseudo.test(unquoted)&&(excess=tokenize(unquoted,true))&&(excess=unquoted.indexOf(")",unquoted.length-excess)-unquoted.length)){unquoted=unquoted.slice(0,excess);match[0]=match[0].slice(0,excess)}match[2]=unquoted}return match.slice(0,3)}},filter:{ID:assertGetIdNotName?function(id){id=id.replace(rbackslash,"");return function(elem){return elem.getAttribute("id")===id}}:function(id){id=id.replace(rbackslash,"");return function(elem){var node=typeof elem.getAttributeNode!==strundefined&&elem.getAttributeNode("id");return node&&node.value===id}},TAG:function(nodeName){if(nodeName==="*"){return function(){return true}}nodeName=nodeName.replace(rbackslash,"").toLowerCase();return function(elem){return elem.nodeName&&elem.nodeName.toLowerCase()===nodeName}},CLASS:function(className){var pattern=classCache[expando][className+" "];return pattern||(pattern=new RegExp("(^|"+whitespace+")"+className+"("+whitespace+"|$)"))&&classCache(className,function(elem){return pattern.test(elem.className||typeof elem.getAttribute!==strundefined&&elem.getAttribute("class")||"")})},ATTR:function(name,operator,check){return function(elem,context){var result=Sizzle.attr(elem,name);if(result==null){return operator==="!="}if(!operator){return true}result+="";return operator==="="?result===check:operator==="!="?result!==check:operator==="^="?check&&result.indexOf(check)===0:operator==="*="?check&&result.indexOf(check)>-1:operator==="$="?check&&result.substr(result.length-check.length)===check:operator==="~="?(" "+result+" ").indexOf(check)>-1:operator==="|="?result===check||result.substr(0,check.length+1)===check+"-":false}},CHILD:function(type,argument,first,last){if(type==="nth"){return function(elem){var node,diff,parent=elem.parentNode;if(first===1&&last===0){return true}if(parent){diff=0;for(node=parent.firstChild;node;node=node.nextSibling){if(node.nodeType===1){diff++; +if(elem===node){break}}}}diff-=last;return diff===first||diff%first===0&&diff/first>=0}}return function(elem){var node=elem;switch(type){case"only":case"first":while(node=node.previousSibling){if(node.nodeType===1){return false}}if(type==="first"){return true}node=elem;case"last":while(node=node.nextSibling){if(node.nodeType===1){return false}}return true}}},PSEUDO:function(pseudo,argument){var args,fn=Expr.pseudos[pseudo]||Expr.setFilters[pseudo.toLowerCase()]||Sizzle.error("unsupported pseudo: "+pseudo);if(fn[expando]){return fn(argument)}if(fn.length>1){args=[pseudo,pseudo,"",argument];return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase())?markFunction(function(seed,matches){var idx,matched=fn(seed,argument),i=matched.length;while(i--){idx=indexOf.call(seed,matched[i]);seed[idx]=!(matches[idx]=matched[i])}}):function(elem){return fn(elem,0,args)}}return fn}},pseudos:{not:markFunction(function(selector){var input=[],results=[],matcher=compile(selector.replace(rtrim,"$1"));return matcher[expando]?markFunction(function(seed,matches,context,xml){var elem,unmatched=matcher(seed,null,xml,[]),i=seed.length;while(i--){if(elem=unmatched[i]){seed[i]=!(matches[i]=elem)}}}):function(elem,context,xml){input[0]=elem;matcher(input,null,xml,results);return!results.pop()}}),has:markFunction(function(selector){return function(elem){return Sizzle(selector,elem).length>0}}),contains:markFunction(function(text){return function(elem){return(elem.textContent||elem.innerText||getText(elem)).indexOf(text)>-1}}),enabled:function(elem){return elem.disabled===false},disabled:function(elem){return elem.disabled===true},checked:function(elem){var nodeName=elem.nodeName.toLowerCase();return nodeName==="input"&&!!elem.checked||nodeName==="option"&&!!elem.selected},selected:function(elem){if(elem.parentNode){elem.parentNode.selectedIndex}return elem.selected===true},parent:function(elem){return!Expr.pseudos["empty"](elem)},empty:function(elem){var nodeType;elem=elem.firstChild;while(elem){if(elem.nodeName>"@"||(nodeType=elem.nodeType)===3||nodeType===4){return false}elem=elem.nextSibling}return true},header:function(elem){return rheader.test(elem.nodeName)},text:function(elem){var type,attr;return elem.nodeName.toLowerCase()==="input"&&(type=elem.type)==="text"&&((attr=elem.getAttribute("type"))==null||attr.toLowerCase()===type)},radio:createInputPseudo("radio"),checkbox:createInputPseudo("checkbox"),file:createInputPseudo("file"),password:createInputPseudo("password"),image:createInputPseudo("image"),submit:createButtonPseudo("submit"),reset:createButtonPseudo("reset"),button:function(elem){var name=elem.nodeName.toLowerCase();return name==="input"&&elem.type==="button"||name==="button"},input:function(elem){return rinputs.test(elem.nodeName)},focus:function(elem){var doc=elem.ownerDocument;return elem===doc.activeElement&&(!doc.hasFocus||doc.hasFocus())&&!!(elem.type||elem.href||~elem.tabIndex)},active:function(elem){return elem===elem.ownerDocument.activeElement},first:createPositionalPseudo(function(){return[0]}),last:createPositionalPseudo(function(matchIndexes,length){return[length-1]}),eq:createPositionalPseudo(function(matchIndexes,length,argument){return[argument<0?argument+length:argument]}),even:createPositionalPseudo(function(matchIndexes,length){for(var i=0;i=0;){matchIndexes.push(i)}return matchIndexes}),gt:createPositionalPseudo(function(matchIndexes,length,argument){for(var i=argument<0?argument+length:argument;++i1?function(elem,context,xml){var i=matchers.length;while(i--){if(!matchers[i](elem,context,xml)){return false}}return true}:matchers[0]}function condense(unmatched,map,filter,context,xml){var elem,newUnmatched=[],i=0,len=unmatched.length,mapped=map!=null;for(;i-1){seed[temp]=!(results[temp]=elem)}}}}else{matcherOut=condense(matcherOut===results?matcherOut.splice(preexisting,matcherOut.length):matcherOut);if(postFinder){postFinder(null,results,matcherOut,xml)}else{push.apply(results,matcherOut)}}})}function matcherFromTokens(tokens){var checkContext,matcher,j,len=tokens.length,leadingRelative=Expr.relative[tokens[0].type],implicitRelative=leadingRelative||Expr.relative[" "],i=leadingRelative?1:0,matchContext=addCombinator(function(elem){return elem===checkContext},implicitRelative,true),matchAnyContext=addCombinator(function(elem){return indexOf.call(checkContext,elem)>-1},implicitRelative,true),matchers=[function(elem,context,xml){return!leadingRelative&&(xml||context!==outermostContext)||((checkContext=context).nodeType?matchContext(elem,context,xml):matchAnyContext(elem,context,xml))}];for(;i1&&elementMatcher(matchers),i>1&&tokens.slice(0,i-1).join("").replace(rtrim,"$1"),matcher,i0,byElement=elementMatchers.length>0,superMatcher=function(seed,context,xml,results,expandContext){var elem,j,matcher,setMatched=[],matchedCount=0,i="0",unmatched=seed&&[],outermost=expandContext!=null,contextBackup=outermostContext,elems=seed||byElement&&Expr.find["TAG"]("*",expandContext&&context.parentNode||context),dirrunsUnique=dirruns+=contextBackup==null?1:Math.E;if(outermost){outermostContext=context!==document&&context;cachedruns=superMatcher.el}for(;(elem=elems[i])!=null;i++){if(byElement&&elem){for(j=0;matcher=elementMatchers[j];j++){if(matcher(elem,context,xml)){results.push(elem);break}}if(outermost){dirruns=dirrunsUnique;cachedruns=++superMatcher.el}}if(bySet){if(elem=!matcher&&elem){matchedCount--}if(seed){unmatched.push(elem)}}}matchedCount+=i;if(bySet&&i!==matchedCount){for(j=0;matcher=setMatchers[j];j++){matcher(unmatched,setMatched,context,xml)}if(seed){if(matchedCount>0){while(i--){if(!(unmatched[i]||setMatched[i])){setMatched[i]=pop.call(results)}}}setMatched=condense(setMatched)}push.apply(results,setMatched);if(outermost&&!seed&&setMatched.length>0&&matchedCount+setMatchers.length>1){Sizzle.uniqueSort(results)}}if(outermost){dirruns=dirrunsUnique;outermostContext=contextBackup}return unmatched};superMatcher.el=0;return bySet?markFunction(superMatcher):superMatcher}compile=Sizzle.compile=function(selector,group){var i,setMatchers=[],elementMatchers=[],cached=compilerCache[expando][selector+" "];if(!cached){if(!group){group=tokenize(selector)}i=group.length;while(i--){cached=matcherFromTokens(group[i]);if(cached[expando]){setMatchers.push(cached)}else{elementMatchers.push(cached)}}cached=compilerCache(selector,matcherFromGroupMatchers(elementMatchers,setMatchers))}return cached};function multipleContexts(selector,contexts,results){var i=0,len=contexts.length;for(;i2&&(token=tokens[0]).type==="ID"&&context.nodeType===9&&!xml&&Expr.relative[tokens[1].type]){context=Expr.find["ID"](token.matches[0].replace(rbackslash,""),context,xml)[0];if(!context){return results}selector=selector.slice(tokens.shift().length)}for(i=matchExpr["POS"].test(selector)?-1:tokens.length-1;i>=0;i--){token=tokens[i];if(Expr.relative[type=token.type]){break}if(find=Expr.find[type]){if(seed=find(token.matches[0].replace(rbackslash,""),rsibling.test(tokens[0].type)&&context.parentNode||context,xml)){tokens.splice(i,1);selector=seed.length&&tokens.join("");if(!selector){push.apply(results,slice.call(seed,0));return results}break}}}}}compile(selector,match)(seed,context,xml,results,rsibling.test(selector));return results}if(document.querySelectorAll){(function(){var disconnectedMatch,oldSelect=select,rescape=/'|\\/g,rattributeQuotes=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,rbuggyQSA=[":focus"],rbuggyMatches=[":active"],matches=docElem.matchesSelector||docElem.mozMatchesSelector||docElem.webkitMatchesSelector||docElem.oMatchesSelector||docElem.msMatchesSelector;assert(function(div){div.innerHTML="";if(!div.querySelectorAll("[selected]").length){rbuggyQSA.push("\\["+whitespace+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)")}if(!div.querySelectorAll(":checked").length){rbuggyQSA.push(":checked")}});assert(function(div){div.innerHTML="

";if(div.querySelectorAll("[test^='']").length){rbuggyQSA.push("[*^$]="+whitespace+"*(?:\"\"|'')")}div.innerHTML="";if(!div.querySelectorAll(":enabled").length){rbuggyQSA.push(":enabled",":disabled")}});rbuggyQSA=new RegExp(rbuggyQSA.join("|"));select=function(selector,context,results,seed,xml){if(!seed&&!xml&&!rbuggyQSA.test(selector)){var groups,i,old=true,nid=expando,newContext=context,newSelector=context.nodeType===9&&selector;if(context.nodeType===1&&context.nodeName.toLowerCase()!=="object"){groups=tokenize(selector);if(old=context.getAttribute("id")){nid=old.replace(rescape,"\\$&")}else{context.setAttribute("id",nid)}nid="[id='"+nid+"'] ";i=groups.length;while(i--){groups[i]=nid+groups[i].join("")}newContext=rsibling.test(selector)&&context.parentNode||context;newSelector=groups.join(",")}if(newSelector){try{push.apply(results,slice.call(newContext.querySelectorAll(newSelector),0));return results}catch(qsaError){}finally{if(!old){context.removeAttribute("id")}}}}return oldSelect(selector,context,results,seed,xml)};if(matches){assert(function(div){disconnectedMatch=matches.call(div,"div");try{matches.call(div,"[test!='']:sizzle");rbuggyMatches.push("!=",pseudos)}catch(e){}});rbuggyMatches=new RegExp(rbuggyMatches.join("|"));Sizzle.matchesSelector=function(elem,expr){expr=expr.replace(rattributeQuotes,"='$1']");if(!isXML(elem)&&!rbuggyMatches.test(expr)&&!rbuggyQSA.test(expr)){try{var ret=matches.call(elem,expr);if(ret||disconnectedMatch||elem.document&&elem.document.nodeType!==11){return ret}}catch(e){}}return Sizzle(expr,null,null,[elem]).length>0}}})()}Expr.pseudos["nth"]=Expr.pseudos["eq"];function setFilters(){}Expr.filters=setFilters.prototype=Expr.pseudos;Expr.setFilters=new setFilters;Sizzle.attr=jQuery.attr;jQuery.find=Sizzle;jQuery.expr=Sizzle.selectors;jQuery.expr[":"]=jQuery.expr.pseudos;jQuery.unique=Sizzle.uniqueSort;jQuery.text=Sizzle.getText;jQuery.isXMLDoc=Sizzle.isXML;jQuery.contains=Sizzle.contains})(window);var runtil=/Until$/,rparentsprev=/^(?:parents|prev(?:Until|All))/,isSimple=/^.[^:#\[\.,]*$/,rneedsContext=jQuery.expr.match.needsContext,guaranteedUnique={children:true,contents:true,next:true,prev:true};jQuery.fn.extend({find:function(selector){var i,l,length,n,r,ret,self=this;if(typeof selector!=="string"){return jQuery(selector).filter(function(){for(i=0,l=self.length;i0){for(n=length;n=0:jQuery.filter(selector,this).length>0:this.filter(selector).length>0)},closest:function(selectors,context){var cur,i=0,l=this.length,ret=[],pos=rneedsContext.test(selectors)||typeof selectors!=="string"?jQuery(selectors,context||this.context):0;for(;i-1:jQuery.find.matchesSelector(cur,selectors)){ret.push(cur);break}cur=cur.parentNode}}ret=ret.length>1?jQuery.unique(ret):ret;return this.pushStack(ret,"closest",selectors)},index:function(elem){if(!elem){return this[0]&&this[0].parentNode?this.prevAll().length:-1}if(typeof elem==="string"){return jQuery.inArray(this[0],jQuery(elem))}return jQuery.inArray(elem.jquery?elem[0]:elem,this)},add:function(selector,context){var set=typeof selector==="string"?jQuery(selector,context):jQuery.makeArray(selector&&selector.nodeType?[selector]:selector),all=jQuery.merge(this.get(),set);return this.pushStack(isDisconnected(set[0])||isDisconnected(all[0])?all:jQuery.unique(all))},addBack:function(selector){return this.add(selector==null?this.prevObject:this.prevObject.filter(selector))}});jQuery.fn.andSelf=jQuery.fn.addBack;function isDisconnected(node){return!node||!node.parentNode||node.parentNode.nodeType===11}function sibling(cur,dir){do{cur=cur[dir]}while(cur&&cur.nodeType!==1);return cur}jQuery.each({parent:function(elem){var parent=elem.parentNode;return parent&&parent.nodeType!==11?parent:null},parents:function(elem){return jQuery.dir(elem,"parentNode")},parentsUntil:function(elem,i,until){return jQuery.dir(elem,"parentNode",until)},next:function(elem){return sibling(elem,"nextSibling")},prev:function(elem){return sibling(elem,"previousSibling")},nextAll:function(elem){return jQuery.dir(elem,"nextSibling")},prevAll:function(elem){return jQuery.dir(elem,"previousSibling")},nextUntil:function(elem,i,until){return jQuery.dir(elem,"nextSibling",until)},prevUntil:function(elem,i,until){return jQuery.dir(elem,"previousSibling",until)},siblings:function(elem){return jQuery.sibling((elem.parentNode||{}).firstChild,elem)},children:function(elem){return jQuery.sibling(elem.firstChild)},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.merge([],elem.childNodes)}},function(name,fn){jQuery.fn[name]=function(until,selector){var ret=jQuery.map(this,fn,until);if(!runtil.test(name)){selector=until}if(selector&&typeof selector==="string"){ret=jQuery.filter(selector,ret)}ret=this.length>1&&!guaranteedUnique[name]?jQuery.unique(ret):ret;if(this.length>1&&rparentsprev.test(name)){ret=ret.reverse()}return this.pushStack(ret,name,core_slice.call(arguments).join(","))}});jQuery.extend({filter:function(expr,elems,not){if(not){expr=":not("+expr+")"}return elems.length===1?jQuery.find.matchesSelector(elems[0],expr)?[elems[0]]:[]:jQuery.find.matches(expr,elems)},dir:function(elem,dir,until){var matched=[],cur=elem[dir];while(cur&&cur.nodeType!==9&&(until===undefined||cur.nodeType!==1||!jQuery(cur).is(until))){if(cur.nodeType===1){matched.push(cur)}cur=cur[dir]}return matched},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType===1&&n!==elem){r.push(n)}}return r}});function winnow(elements,qualifier,keep){qualifier=qualifier||0;if(jQuery.isFunction(qualifier)){return jQuery.grep(elements,function(elem,i){var retVal=!!qualifier.call(elem,i,elem);return retVal===keep})}else if(qualifier.nodeType){return jQuery.grep(elements,function(elem,i){return elem===qualifier===keep})}else if(typeof qualifier==="string"){var filtered=jQuery.grep(elements,function(elem){return elem.nodeType===1});if(isSimple.test(qualifier)){return jQuery.filter(qualifier,filtered,!keep)}else{qualifier=jQuery.filter(qualifier,filtered)}}return jQuery.grep(elements,function(elem,i){return jQuery.inArray(elem,qualifier)>=0===keep})}function createSafeFragment(document){var list=nodeNames.split("|"),safeFrag=document.createDocumentFragment();if(safeFrag.createElement){while(list.length){safeFrag.createElement(list.pop())}}return safeFrag}var nodeNames="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|"+"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",rinlinejQuery=/ jQuery\d+="(?:null|\d+)"/g,rleadingWhitespace=/^\s+/,rxhtmlTag=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,rtagName=/<([\w:]+)/,rtbody=/]","i"),rcheckableType=/^(?:checkbox|radio)$/,rchecked=/checked\s*(?:[^=]|=\s*.checked.)/i,rscriptType=/\/(java|ecma)script/i,rcleanScript=/^\s*\s*$/g,wrapMap={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},safeFragment=createSafeFragment(document),fragmentDiv=safeFragment.appendChild(document.createElement("div"));wrapMap.optgroup=wrapMap.option;wrapMap.tbody=wrapMap.tfoot=wrapMap.colgroup=wrapMap.caption=wrapMap.thead;wrapMap.th=wrapMap.td;if(!jQuery.support.htmlSerialize){wrapMap._default=[1,"X
","
"]}jQuery.fn.extend({text:function(value){return jQuery.access(this,function(value){return value===undefined?jQuery.text(this):this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(value))},null,value,arguments.length)},wrapAll:function(html){if(jQuery.isFunction(html)){return this.each(function(i){jQuery(this).wrapAll(html.call(this,i))})}if(this[0]){var wrap=jQuery(html,this[0].ownerDocument).eq(0).clone(true);if(this[0].parentNode){wrap.insertBefore(this[0])}wrap.map(function(){var elem=this;while(elem.firstChild&&elem.firstChild.nodeType===1){elem=elem.firstChild}return elem}).append(this)}return this},wrapInner:function(html){if(jQuery.isFunction(html)){return this.each(function(i){jQuery(this).wrapInner(html.call(this,i))})}return this.each(function(){var self=jQuery(this),contents=self.contents();if(contents.length){contents.wrapAll(html)}else{self.append(html)}})},wrap:function(html){var isFunction=jQuery.isFunction(html);return this.each(function(i){jQuery(this).wrapAll(isFunction?html.call(this,i):html)})},unwrap:function(){return this.parent().each(function(){if(!jQuery.nodeName(this,"body")){jQuery(this).replaceWith(this.childNodes)}}).end()},append:function(){return this.domManip(arguments,true,function(elem){if(this.nodeType===1||this.nodeType===11){this.appendChild(elem)}})},prepend:function(){return this.domManip(arguments,true,function(elem){if(this.nodeType===1||this.nodeType===11){this.insertBefore(elem,this.firstChild)}})},before:function(){if(!isDisconnected(this[0])){return this.domManip(arguments,false,function(elem){this.parentNode.insertBefore(elem,this)})}if(arguments.length){var set=jQuery.clean(arguments);return this.pushStack(jQuery.merge(set,this),"before",this.selector)}},after:function(){if(!isDisconnected(this[0])){return this.domManip(arguments,false,function(elem){this.parentNode.insertBefore(elem,this.nextSibling)})}if(arguments.length){var set=jQuery.clean(arguments);return this.pushStack(jQuery.merge(this,set),"after",this.selector)}},remove:function(selector,keepData){var elem,i=0;for(;(elem=this[i])!=null;i++){if(!selector||jQuery.filter(selector,[elem]).length){if(!keepData&&elem.nodeType===1){jQuery.cleanData(elem.getElementsByTagName("*"));jQuery.cleanData([elem])}if(elem.parentNode){elem.parentNode.removeChild(elem)}}}return this},empty:function(){var elem,i=0;for(;(elem=this[i])!=null;i++){if(elem.nodeType===1){jQuery.cleanData(elem.getElementsByTagName("*"))}while(elem.firstChild){elem.removeChild(elem.firstChild)}}return this},clone:function(dataAndEvents,deepDataAndEvents){dataAndEvents=dataAndEvents==null?false:dataAndEvents;deepDataAndEvents=deepDataAndEvents==null?dataAndEvents:deepDataAndEvents;return this.map(function(){return jQuery.clone(this,dataAndEvents,deepDataAndEvents)})},html:function(value){return jQuery.access(this,function(value){var elem=this[0]||{},i=0,l=this.length;if(value===undefined){return elem.nodeType===1?elem.innerHTML.replace(rinlinejQuery,""):undefined}if(typeof value==="string"&&!rnoInnerhtml.test(value)&&(jQuery.support.htmlSerialize||!rnoshimcache.test(value))&&(jQuery.support.leadingWhitespace||!rleadingWhitespace.test(value))&&!wrapMap[(rtagName.exec(value)||["",""])[1].toLowerCase()]){value=value.replace(rxhtmlTag,"<$1>");try{for(;i1&&typeof value==="string"&&rchecked.test(value)){return this.each(function(){jQuery(this).domManip(args,table,callback)})}if(jQuery.isFunction(value)){return this.each(function(i){var self=jQuery(this);args[0]=value.call(this,i,table?self.html():undefined);self.domManip(args,table,callback)})}if(this[0]){results=jQuery.buildFragment(args,this,scripts);fragment=results.fragment;first=fragment.firstChild;if(fragment.childNodes.length===1){fragment=first}if(first){table=table&&jQuery.nodeName(first,"tr");for(iNoClone=results.cacheable||l-1;i0?this.clone(true):this).get();jQuery(insert[i])[original](elems);ret=ret.concat(elems)}return this.pushStack(ret,name,insert.selector)}}});function getAll(elem){if(typeof elem.getElementsByTagName!=="undefined"){return elem.getElementsByTagName("*")}else if(typeof elem.querySelectorAll!=="undefined"){return elem.querySelectorAll("*")}else{return[]}}function fixDefaultChecked(elem){if(rcheckableType.test(elem.type)){elem.defaultChecked=elem.checked}}jQuery.extend({clone:function(elem,dataAndEvents,deepDataAndEvents){var srcElements,destElements,i,clone;if(jQuery.support.html5Clone||jQuery.isXMLDoc(elem)||!rnoshimcache.test("<"+elem.nodeName+">")){clone=elem.cloneNode(true)}else{fragmentDiv.innerHTML=elem.outerHTML;fragmentDiv.removeChild(clone=fragmentDiv.firstChild)}if((!jQuery.support.noCloneEvent||!jQuery.support.noCloneChecked)&&(elem.nodeType===1||elem.nodeType===11)&&!jQuery.isXMLDoc(elem)){cloneFixAttributes(elem,clone);srcElements=getAll(elem);destElements=getAll(clone);for(i=0;srcElements[i];++i){if(destElements[i]){cloneFixAttributes(srcElements[i],destElements[i])}}}if(dataAndEvents){cloneCopyEvent(elem,clone);if(deepDataAndEvents){srcElements=getAll(elem);destElements=getAll(clone);for(i=0;srcElements[i];++i){cloneCopyEvent(srcElements[i],destElements[i])}}}srcElements=destElements=null;return clone},clean:function(elems,context,fragment,scripts){var i,j,elem,tag,wrap,depth,div,hasBody,tbody,len,handleScript,jsTags,safe=context===document&&safeFragment,ret=[];if(!context||typeof context.createDocumentFragment==="undefined"){context=document}for(i=0;(elem=elems[i])!=null;i++){if(typeof elem==="number"){elem+=""}if(!elem){continue}if(typeof elem==="string"){if(!rhtml.test(elem)){elem=context.createTextNode(elem)}else{safe=safe||createSafeFragment(context);div=context.createElement("div");safe.appendChild(div);elem=elem.replace(rxhtmlTag,"<$1>");tag=(rtagName.exec(elem)||["",""])[1].toLowerCase();wrap=wrapMap[tag]||wrapMap._default;depth=wrap[0];div.innerHTML=wrap[1]+elem+wrap[2];while(depth--){div=div.lastChild +}if(!jQuery.support.tbody){hasBody=rtbody.test(elem);tbody=tag==="table"&&!hasBody?div.firstChild&&div.firstChild.childNodes:wrap[1]===""&&!hasBody?div.childNodes:[];for(j=tbody.length-1;j>=0;--j){if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length){tbody[j].parentNode.removeChild(tbody[j])}}}if(!jQuery.support.leadingWhitespace&&rleadingWhitespace.test(elem)){div.insertBefore(context.createTextNode(rleadingWhitespace.exec(elem)[0]),div.firstChild)}elem=div.childNodes;div.parentNode.removeChild(div)}}if(elem.nodeType){ret.push(elem)}else{jQuery.merge(ret,elem)}}if(div){elem=div=safe=null}if(!jQuery.support.appendChecked){for(i=0;(elem=ret[i])!=null;i++){if(jQuery.nodeName(elem,"input")){fixDefaultChecked(elem)}else if(typeof elem.getElementsByTagName!=="undefined"){jQuery.grep(elem.getElementsByTagName("input"),fixDefaultChecked)}}}if(fragment){handleScript=function(elem){if(!elem.type||rscriptType.test(elem.type)){return scripts?scripts.push(elem.parentNode?elem.parentNode.removeChild(elem):elem):fragment.appendChild(elem)}};for(i=0;(elem=ret[i])!=null;i++){if(!(jQuery.nodeName(elem,"script")&&handleScript(elem))){fragment.appendChild(elem);if(typeof elem.getElementsByTagName!=="undefined"){jsTags=jQuery.grep(jQuery.merge([],elem.getElementsByTagName("script")),handleScript);ret.splice.apply(ret,[i+1,0].concat(jsTags));i+=jsTags.length}}}}return ret},cleanData:function(elems,acceptData){var data,id,elem,type,i=0,internalKey=jQuery.expando,cache=jQuery.cache,deleteExpando=jQuery.support.deleteExpando,special=jQuery.event.special;for(;(elem=elems[i])!=null;i++){if(acceptData||jQuery.acceptData(elem)){id=elem[internalKey];data=id&&cache[id];if(data){if(data.events){for(type in data.events){if(special[type]){jQuery.event.remove(elem,type)}else{jQuery.removeEvent(elem,type,data.handle)}}}if(cache[id]){delete cache[id];if(deleteExpando){delete elem[internalKey]}else if(elem.removeAttribute){elem.removeAttribute(internalKey)}else{elem[internalKey]=null}jQuery.deletedIds.push(id)}}}}}});(function(){var matched,browser;jQuery.uaMatch=function(ua){ua=ua.toLowerCase();var match=/(chrome)[ \/]([\w.]+)/.exec(ua)||/(webkit)[ \/]([\w.]+)/.exec(ua)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)||/(msie) ([\w.]+)/.exec(ua)||ua.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)||[];return{browser:match[1]||"",version:match[2]||"0"}};matched=jQuery.uaMatch(navigator.userAgent);browser={};if(matched.browser){browser[matched.browser]=true;browser.version=matched.version}if(browser.chrome){browser.webkit=true}else if(browser.webkit){browser.safari=true}jQuery.browser=browser;jQuery.sub=function(){function jQuerySub(selector,context){return new jQuerySub.fn.init(selector,context)}jQuery.extend(true,jQuerySub,this);jQuerySub.superclass=this;jQuerySub.fn=jQuerySub.prototype=this();jQuerySub.fn.constructor=jQuerySub;jQuerySub.sub=this.sub;jQuerySub.fn.init=function init(selector,context){if(context&&context instanceof jQuery&&!(context instanceof jQuerySub)){context=jQuerySub(context)}return jQuery.fn.init.call(this,selector,context,rootjQuerySub)};jQuerySub.fn.init.prototype=jQuerySub.fn;var rootjQuerySub=jQuerySub(document);return jQuerySub}})();var curCSS,iframe,iframeDoc,ralpha=/alpha\([^)]*\)/i,ropacity=/opacity=([^)]*)/,rposition=/^(top|right|bottom|left)$/,rdisplayswap=/^(none|table(?!-c[ea]).+)/,rmargin=/^margin/,rnumsplit=new RegExp("^("+core_pnum+")(.*)$","i"),rnumnonpx=new RegExp("^("+core_pnum+")(?!px)[a-z%]+$","i"),rrelNum=new RegExp("^([-+])=("+core_pnum+")","i"),elemdisplay={BODY:"block"},cssShow={position:"absolute",visibility:"hidden",display:"block"},cssNormalTransform={letterSpacing:0,fontWeight:400},cssExpand=["Top","Right","Bottom","Left"],cssPrefixes=["Webkit","O","Moz","ms"],eventsToggle=jQuery.fn.toggle;function vendorPropName(style,name){if(name in style){return name}var capName=name.charAt(0).toUpperCase()+name.slice(1),origName=name,i=cssPrefixes.length;while(i--){name=cssPrefixes[i]+capName;if(name in style){return name}}return origName}function isHidden(elem,el){elem=el||elem;return jQuery.css(elem,"display")==="none"||!jQuery.contains(elem.ownerDocument,elem)}function showHide(elements,show){var elem,display,values=[],index=0,length=elements.length;for(;index1)},show:function(){return showHide(this,true)},hide:function(){return showHide(this)},toggle:function(state,fn2){var bool=typeof state==="boolean";if(jQuery.isFunction(state)&&jQuery.isFunction(fn2)){return eventsToggle.apply(this,arguments)}return this.each(function(){if(bool?state:isHidden(this)){jQuery(this).show()}else{jQuery(this).hide()}})}});jQuery.extend({cssHooks:{opacity:{get:function(elem,computed){if(computed){var ret=curCSS(elem,"opacity");return ret===""?"1":ret}}}},cssNumber:{fillOpacity:true,fontWeight:true,lineHeight:true,opacity:true,orphans:true,widows:true,zIndex:true,zoom:true},cssProps:{"float":jQuery.support.cssFloat?"cssFloat":"styleFloat"},style:function(elem,name,value,extra){if(!elem||elem.nodeType===3||elem.nodeType===8||!elem.style){return}var ret,type,hooks,origName=jQuery.camelCase(name),style=elem.style;name=jQuery.cssProps[origName]||(jQuery.cssProps[origName]=vendorPropName(style,origName));hooks=jQuery.cssHooks[name]||jQuery.cssHooks[origName];if(value!==undefined){type=typeof value;if(type==="string"&&(ret=rrelNum.exec(value))){value=(ret[1]+1)*ret[2]+parseFloat(jQuery.css(elem,name));type="number"}if(value==null||type==="number"&&isNaN(value)){return}if(type==="number"&&!jQuery.cssNumber[origName]){value+="px"}if(!hooks||!("set"in hooks)||(value=hooks.set(elem,value,extra))!==undefined){try{style[name]=value}catch(e){}}}else{if(hooks&&"get"in hooks&&(ret=hooks.get(elem,false,extra))!==undefined){return ret}return style[name]}},css:function(elem,name,numeric,extra){var val,num,hooks,origName=jQuery.camelCase(name);name=jQuery.cssProps[origName]||(jQuery.cssProps[origName]=vendorPropName(elem.style,origName));hooks=jQuery.cssHooks[name]||jQuery.cssHooks[origName];if(hooks&&"get"in hooks){val=hooks.get(elem,true,extra)}if(val===undefined){val=curCSS(elem,name)}if(val==="normal"&&name in cssNormalTransform){val=cssNormalTransform[name]}if(numeric||extra!==undefined){num=parseFloat(val);return numeric||jQuery.isNumeric(num)?num||0:val}return val},swap:function(elem,options,callback){var ret,name,old={};for(name in options){old[name]=elem.style[name];elem.style[name]=options[name]}ret=callback.call(elem);for(name in options){elem.style[name]=old[name]}return ret}});if(window.getComputedStyle){curCSS=function(elem,name){var ret,width,minWidth,maxWidth,computed=window.getComputedStyle(elem,null),style=elem.style;if(computed){ret=computed.getPropertyValue(name)||computed[name];if(ret===""&&!jQuery.contains(elem.ownerDocument,elem)){ret=jQuery.style(elem,name)}if(rnumnonpx.test(ret)&&rmargin.test(name)){width=style.width;minWidth=style.minWidth;maxWidth=style.maxWidth;style.minWidth=style.maxWidth=style.width=ret;ret=computed.width;style.width=width;style.minWidth=minWidth;style.maxWidth=maxWidth}}return ret}}else if(document.documentElement.currentStyle){curCSS=function(elem,name){var left,rsLeft,ret=elem.currentStyle&&elem.currentStyle[name],style=elem.style;if(ret==null&&style&&style[name]){ret=style[name]}if(rnumnonpx.test(ret)&&!rposition.test(name)){left=style.left;rsLeft=elem.runtimeStyle&&elem.runtimeStyle.left;if(rsLeft){elem.runtimeStyle.left=elem.currentStyle.left}style.left=name==="fontSize"?"1em":ret;ret=style.pixelLeft+"px";style.left=left;if(rsLeft){elem.runtimeStyle.left=rsLeft}}return ret===""?"auto":ret}}function setPositiveNumber(elem,value,subtract){var matches=rnumsplit.exec(value);return matches?Math.max(0,matches[1]-(subtract||0))+(matches[2]||"px"):value}function augmentWidthOrHeight(elem,name,extra,isBorderBox){var i=extra===(isBorderBox?"border":"content")?4:name==="width"?1:0,val=0;for(;i<4;i+=2){if(extra==="margin"){val+=jQuery.css(elem,extra+cssExpand[i],true)}if(isBorderBox){if(extra==="content"){val-=parseFloat(curCSS(elem,"padding"+cssExpand[i]))||0}if(extra!=="margin"){val-=parseFloat(curCSS(elem,"border"+cssExpand[i]+"Width"))||0}}else{val+=parseFloat(curCSS(elem,"padding"+cssExpand[i]))||0;if(extra!=="padding"){val+=parseFloat(curCSS(elem,"border"+cssExpand[i]+"Width"))||0}}}return val}function getWidthOrHeight(elem,name,extra){var val=name==="width"?elem.offsetWidth:elem.offsetHeight,valueIsBorderBox=true,isBorderBox=jQuery.support.boxSizing&&jQuery.css(elem,"boxSizing")==="border-box";if(val<=0||val==null){val=curCSS(elem,name);if(val<0||val==null){val=elem.style[name]}if(rnumnonpx.test(val)){return val}valueIsBorderBox=isBorderBox&&(jQuery.support.boxSizingReliable||val===elem.style[name]);val=parseFloat(val)||0}return val+augmentWidthOrHeight(elem,name,extra||(isBorderBox?"border":"content"),valueIsBorderBox)+"px"}function css_defaultDisplay(nodeName){if(elemdisplay[nodeName]){return elemdisplay[nodeName]}var elem=jQuery("<"+nodeName+">").appendTo(document.body),display=elem.css("display");elem.remove();if(display==="none"||display===""){iframe=document.body.appendChild(iframe||jQuery.extend(document.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!iframeDoc||!iframe.createElement){iframeDoc=(iframe.contentWindow||iframe.contentDocument).document;iframeDoc.write("");iframeDoc.close()}elem=iframeDoc.body.appendChild(iframeDoc.createElement(nodeName));display=curCSS(elem,"display");document.body.removeChild(iframe)}elemdisplay[nodeName]=display;return display}jQuery.each(["height","width"],function(i,name){jQuery.cssHooks[name]={get:function(elem,computed,extra){if(computed){if(elem.offsetWidth===0&&rdisplayswap.test(curCSS(elem,"display"))){return jQuery.swap(elem,cssShow,function(){return getWidthOrHeight(elem,name,extra)})}else{return getWidthOrHeight(elem,name,extra)}}},set:function(elem,value,extra){return setPositiveNumber(elem,value,extra?augmentWidthOrHeight(elem,name,extra,jQuery.support.boxSizing&&jQuery.css(elem,"boxSizing")==="border-box"):0)}}});if(!jQuery.support.opacity){jQuery.cssHooks.opacity={get:function(elem,computed){return ropacity.test((computed&&elem.currentStyle?elem.currentStyle.filter:elem.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":computed?"1":""},set:function(elem,value){var style=elem.style,currentStyle=elem.currentStyle,opacity=jQuery.isNumeric(value)?"alpha(opacity="+value*100+")":"",filter=currentStyle&¤tStyle.filter||style.filter||"";style.zoom=1;if(value>=1&&jQuery.trim(filter.replace(ralpha,""))===""&&style.removeAttribute){style.removeAttribute("filter");if(currentStyle&&!currentStyle.filter){return}}style.filter=ralpha.test(filter)?filter.replace(ralpha,opacity):filter+" "+opacity}}}jQuery(function(){if(!jQuery.support.reliableMarginRight){jQuery.cssHooks.marginRight={get:function(elem,computed){return jQuery.swap(elem,{display:"inline-block"},function(){if(computed){return curCSS(elem,"marginRight")}})}}}if(!jQuery.support.pixelPosition&&jQuery.fn.position){jQuery.each(["top","left"],function(i,prop){jQuery.cssHooks[prop]={get:function(elem,computed){if(computed){var ret=curCSS(elem,prop);return rnumnonpx.test(ret)?jQuery(elem).position()[prop]+"px":ret}}}})}});if(jQuery.expr&&jQuery.expr.filters){jQuery.expr.filters.hidden=function(elem){return elem.offsetWidth===0&&elem.offsetHeight===0||!jQuery.support.reliableHiddenOffsets&&(elem.style&&elem.style.display||curCSS(elem,"display"))==="none"};jQuery.expr.filters.visible=function(elem){return!jQuery.expr.filters.hidden(elem)}}jQuery.each({margin:"",padding:"",border:"Width"},function(prefix,suffix){jQuery.cssHooks[prefix+suffix]={expand:function(value){var i,parts=typeof value==="string"?value.split(" "):[value],expanded={};for(i=0;i<4;i++){expanded[prefix+cssExpand[i]+suffix]=parts[i]||parts[i-2]||parts[0]}return expanded}};if(!rmargin.test(prefix)){jQuery.cssHooks[prefix+suffix].set=setPositiveNumber}});var r20=/%20/g,rbracket=/\[\]$/,rCRLF=/\r?\n/g,rinput=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,rselectTextarea=/^(?:select|textarea)/i;jQuery.fn.extend({serialize:function(){return jQuery.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?jQuery.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||rselectTextarea.test(this.nodeName)||rinput.test(this.type))}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:jQuery.isArray(val)?jQuery.map(val,function(val,i){return{name:elem.name,value:val.replace(rCRLF,"\r\n")}}):{name:elem.name,value:val.replace(rCRLF,"\r\n")}}).get()}});jQuery.param=function(a,traditional){var prefix,s=[],add=function(key,value){value=jQuery.isFunction(value)?value():value==null?"":value;s[s.length]=encodeURIComponent(key)+"="+encodeURIComponent(value)};if(traditional===undefined){traditional=jQuery.ajaxSettings&&jQuery.ajaxSettings.traditional}if(jQuery.isArray(a)||a.jquery&&!jQuery.isPlainObject(a)){jQuery.each(a,function(){add(this.name,this.value)})}else{for(prefix in a){buildParams(prefix,a[prefix],traditional,add)}}return s.join("&").replace(r20,"+")};function buildParams(prefix,obj,traditional,add){var name;if(jQuery.isArray(obj)){jQuery.each(obj,function(i,v){if(traditional||rbracket.test(prefix)){add(prefix,v)}else{buildParams(prefix+"["+(typeof v==="object"?i:"")+"]",v,traditional,add)}})}else if(!traditional&&jQuery.type(obj)==="object"){for(name in obj){buildParams(prefix+"["+name+"]",obj[name],traditional,add)}}else{add(prefix,obj)}}var ajaxLocParts,ajaxLocation,rhash=/#.*$/,rheaders=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,rlocalProtocol=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,rnoContent=/^(?:GET|HEAD)$/,rprotocol=/^\/\//,rquery=/\?/,rscript=/)<[^<]*)*<\/script>/gi,rts=/([?&])_=[^&]*/,rurl=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,_load=jQuery.fn.load,prefilters={},transports={},allTypes=["*/"]+["*"];try{ajaxLocation=location.href}catch(e){ajaxLocation=document.createElement("a");ajaxLocation.href="";ajaxLocation=ajaxLocation.href}ajaxLocParts=rurl.exec(ajaxLocation.toLowerCase())||[];function addToPrefiltersOrTransports(structure){return function(dataTypeExpression,func){if(typeof dataTypeExpression!=="string"){func=dataTypeExpression;dataTypeExpression="*"}var dataType,list,placeBefore,dataTypes=dataTypeExpression.toLowerCase().split(core_rspace),i=0,length=dataTypes.length;if(jQuery.isFunction(func)){for(;i=0){selector=url.slice(off,url.length);url=url.slice(0,off)}if(jQuery.isFunction(params)){callback=params;params=undefined}else if(params&&typeof params==="object"){type="POST"}jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(jqXHR,status){if(callback){self.each(callback,response||[jqXHR.responseText,status,jqXHR])}}}).done(function(responseText){response=arguments;self.html(selector?jQuery("
").append(responseText.replace(rscript,"")).find(selector):responseText)});return this};jQuery.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(i,o){jQuery.fn[o]=function(f){return this.on(o,f)}});jQuery.each(["get","post"],function(i,method){jQuery[method]=function(url,data,callback,type){if(jQuery.isFunction(data)){type=type||callback;callback=data;data=undefined}return jQuery.ajax({type:method,url:url,data:data,success:callback,dataType:type})}});jQuery.extend({getScript:function(url,callback){return jQuery.get(url,undefined,callback,"script")},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json")},ajaxSetup:function(target,settings){if(settings){ajaxExtend(target,jQuery.ajaxSettings)}else{settings=target;target=jQuery.ajaxSettings}ajaxExtend(target,settings);return target},ajaxSettings:{url:ajaxLocation,isLocal:rlocalProtocol.test(ajaxLocParts[1]),global:true,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:true,async:true,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":allTypes},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":window.String,"text html":true,"text json":jQuery.parseJSON,"text xml":jQuery.parseXML},flatOptions:{context:true,url:true}},ajaxPrefilter:addToPrefiltersOrTransports(prefilters),ajaxTransport:addToPrefiltersOrTransports(transports),ajax:function(url,options){if(typeof url==="object"){options=url;url=undefined}options=options||{};var ifModifiedKey,responseHeadersString,responseHeaders,transport,timeoutTimer,parts,fireGlobals,i,s=jQuery.ajaxSetup({},options),callbackContext=s.context||s,globalEventContext=callbackContext!==s&&(callbackContext.nodeType||callbackContext instanceof jQuery)?jQuery(callbackContext):jQuery.event,deferred=jQuery.Deferred(),completeDeferred=jQuery.Callbacks("once memory"),statusCode=s.statusCode||{},requestHeaders={},requestHeadersNames={},state=0,strAbort="canceled",jqXHR={readyState:0,setRequestHeader:function(name,value){if(!state){var lname=name.toLowerCase();name=requestHeadersNames[lname]=requestHeadersNames[lname]||name;requestHeaders[name]=value}return this},getAllResponseHeaders:function(){return state===2?responseHeadersString:null},getResponseHeader:function(key){var match;if(state===2){if(!responseHeaders){responseHeaders={};while(match=rheaders.exec(responseHeadersString)){responseHeaders[match[1].toLowerCase()]=match[2]}}match=responseHeaders[key.toLowerCase()]}return match===undefined?null:match},overrideMimeType:function(type){if(!state){s.mimeType=type}return this},abort:function(statusText){statusText=statusText||strAbort;if(transport){transport.abort(statusText)}done(0,statusText);return this}};function done(status,nativeStatusText,responses,headers){var isSuccess,success,error,response,modified,statusText=nativeStatusText;if(state===2){return}state=2;if(timeoutTimer){clearTimeout(timeoutTimer)}transport=undefined;responseHeadersString=headers||"";jqXHR.readyState=status>0?4:0;if(responses){response=ajaxHandleResponses(s,jqXHR,responses)}if(status>=200&&status<300||status===304){if(s.ifModified){modified=jqXHR.getResponseHeader("Last-Modified");if(modified){jQuery.lastModified[ifModifiedKey]=modified}modified=jqXHR.getResponseHeader("Etag");if(modified){jQuery.etag[ifModifiedKey]=modified}}if(status===304){statusText="notmodified";isSuccess=true}else{isSuccess=ajaxConvert(s,response);statusText=isSuccess.state;success=isSuccess.data;error=isSuccess.error;isSuccess=!error}}else{error=statusText;if(!statusText||status){statusText="error";if(status<0){status=0}}}jqXHR.status=status;jqXHR.statusText=(nativeStatusText||statusText)+"";if(isSuccess){deferred.resolveWith(callbackContext,[success,statusText,jqXHR])}else{deferred.rejectWith(callbackContext,[jqXHR,statusText,error])}jqXHR.statusCode(statusCode);statusCode=undefined;if(fireGlobals){globalEventContext.trigger("ajax"+(isSuccess?"Success":"Error"),[jqXHR,s,isSuccess?success:error])}completeDeferred.fireWith(callbackContext,[jqXHR,statusText]);if(fireGlobals){globalEventContext.trigger("ajaxComplete",[jqXHR,s]);if(!--jQuery.active){jQuery.event.trigger("ajaxStop")}}}deferred.promise(jqXHR);jqXHR.success=jqXHR.done;jqXHR.error=jqXHR.fail;jqXHR.complete=completeDeferred.add;jqXHR.statusCode=function(map){if(map){var tmp;if(state<2){for(tmp in map){statusCode[tmp]=[statusCode[tmp],map[tmp]]}}else{tmp=map[jqXHR.status];jqXHR.always(tmp)}}return this};s.url=((url||s.url)+"").replace(rhash,"").replace(rprotocol,ajaxLocParts[1]+"//");s.dataTypes=jQuery.trim(s.dataType||"*").toLowerCase().split(core_rspace);if(s.crossDomain==null){parts=rurl.exec(s.url.toLowerCase());s.crossDomain=!!(parts&&(parts[1]!==ajaxLocParts[1]||parts[2]!==ajaxLocParts[2]||(parts[3]||(parts[1]==="http:"?80:443))!=(ajaxLocParts[3]||(ajaxLocParts[1]==="http:"?80:443))))}if(s.data&&s.processData&&typeof s.data!=="string"){s.data=jQuery.param(s.data,s.traditional)}inspectPrefiltersOrTransports(prefilters,s,options,jqXHR);if(state===2){return jqXHR}fireGlobals=s.global;s.type=s.type.toUpperCase();s.hasContent=!rnoContent.test(s.type);if(fireGlobals&&jQuery.active++===0){jQuery.event.trigger("ajaxStart")}if(!s.hasContent){if(s.data){s.url+=(rquery.test(s.url)?"&":"?")+s.data;delete s.data}ifModifiedKey=s.url;if(s.cache===false){var ts=jQuery.now(),ret=s.url.replace(rts,"$1_="+ts);s.url=ret+(ret===s.url?(rquery.test(s.url)?"&":"?")+"_="+ts:"")}}if(s.data&&s.hasContent&&s.contentType!==false||options.contentType){jqXHR.setRequestHeader("Content-Type",s.contentType)}if(s.ifModified){ifModifiedKey=ifModifiedKey||s.url;if(jQuery.lastModified[ifModifiedKey]){jqXHR.setRequestHeader("If-Modified-Since",jQuery.lastModified[ifModifiedKey])}if(jQuery.etag[ifModifiedKey]){jqXHR.setRequestHeader("If-None-Match",jQuery.etag[ifModifiedKey])}}jqXHR.setRequestHeader("Accept",s.dataTypes[0]&&s.accepts[s.dataTypes[0]]?s.accepts[s.dataTypes[0]]+(s.dataTypes[0]!=="*"?", "+allTypes+"; q=0.01":""):s.accepts["*"]);for(i in s.headers){jqXHR.setRequestHeader(i,s.headers[i])}if(s.beforeSend&&(s.beforeSend.call(callbackContext,jqXHR,s)===false||state===2)){return jqXHR.abort()}strAbort="abort";for(i in{success:1,error:1,complete:1}){jqXHR[i](s[i])}transport=inspectPrefiltersOrTransports(transports,s,options,jqXHR);if(!transport){done(-1,"No Transport")}else{jqXHR.readyState=1;if(fireGlobals){globalEventContext.trigger("ajaxSend",[jqXHR,s])}if(s.async&&s.timeout>0){timeoutTimer=setTimeout(function(){jqXHR.abort("timeout")},s.timeout)}try{state=1;transport.send(requestHeaders,done)}catch(e){if(state<2){done(-1,e)}else{throw e}}}return jqXHR},active:0,lastModified:{},etag:{}});function ajaxHandleResponses(s,jqXHR,responses){var ct,type,finalDataType,firstDataType,contents=s.contents,dataTypes=s.dataTypes,responseFields=s.responseFields;for(type in responseFields){if(type in responses){jqXHR[responseFields[type]]=responses[type]}}while(dataTypes[0]==="*"){dataTypes.shift();if(ct===undefined){ct=s.mimeType||jqXHR.getResponseHeader("content-type")}}if(ct){for(type in contents){if(contents[type]&&contents[type].test(ct)){dataTypes.unshift(type);break}}}if(dataTypes[0]in responses){finalDataType=dataTypes[0]}else{for(type in responses){if(!dataTypes[0]||s.converters[type+" "+dataTypes[0]]){finalDataType=type;break}if(!firstDataType){firstDataType=type}}finalDataType=finalDataType||firstDataType}if(finalDataType){if(finalDataType!==dataTypes[0]){dataTypes.unshift(finalDataType)}return responses[finalDataType]}}function ajaxConvert(s,response){var conv,conv2,current,tmp,dataTypes=s.dataTypes.slice(),prev=dataTypes[0],converters={},i=0;if(s.dataFilter){response=s.dataFilter(response,s.dataType)}if(dataTypes[1]){for(conv in s.converters){converters[conv.toLowerCase()]=s.converters[conv]}}for(;current=dataTypes[++i];){if(current!=="*"){if(prev!=="*"&&prev!==current){conv=converters[prev+" "+current]||converters["* "+current];if(!conv){for(conv2 in converters){tmp=conv2.split(" ");if(tmp[1]===current){conv=converters[prev+" "+tmp[0]]||converters["* "+tmp[0]];if(conv){if(conv===true){conv=converters[conv2]}else if(converters[conv2]!==true){current=tmp[0];dataTypes.splice(i--,0,current)}break}}}}if(conv!==true){if(conv&&s["throws"]){response=conv(response)}else{try{response=conv(response)}catch(e){return{state:"parsererror",error:conv?e:"No conversion from "+prev+" to "+current}}}}}prev=current}}return{state:"success",data:response}}var oldCallbacks=[],rquestion=/\?/,rjsonp=/(=)\?(?=&|$)|\?\?/,nonce=jQuery.now();jQuery.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var callback=oldCallbacks.pop()||jQuery.expando+"_"+nonce++;this[callback]=true;return callback}});jQuery.ajaxPrefilter("json jsonp",function(s,originalSettings,jqXHR){var callbackName,overwritten,responseContainer,data=s.data,url=s.url,hasCallback=s.jsonp!==false,replaceInUrl=hasCallback&&rjsonp.test(url),replaceInData=hasCallback&&!replaceInUrl&&typeof data==="string"&&!(s.contentType||"").indexOf("application/x-www-form-urlencoded")&&rjsonp.test(data);if(s.dataTypes[0]==="jsonp"||replaceInUrl||replaceInData){callbackName=s.jsonpCallback=jQuery.isFunction(s.jsonpCallback)?s.jsonpCallback():s.jsonpCallback;overwritten=window[callbackName];if(replaceInUrl){s.url=url.replace(rjsonp,"$1"+callbackName)}else if(replaceInData){s.data=data.replace(rjsonp,"$1"+callbackName)}else if(hasCallback){s.url+=(rquestion.test(url)?"&":"?")+s.jsonp+"="+callbackName}s.converters["script json"]=function(){if(!responseContainer){jQuery.error(callbackName+" was not called")}return responseContainer[0]};s.dataTypes[0]="json";window[callbackName]=function(){responseContainer=arguments};jqXHR.always(function(){window[callbackName]=overwritten;if(s[callbackName]){s.jsonpCallback=originalSettings.jsonpCallback;oldCallbacks.push(callbackName)}if(responseContainer&&jQuery.isFunction(overwritten)){overwritten(responseContainer[0])}responseContainer=overwritten=undefined});return"script"}});jQuery.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(text){jQuery.globalEval(text);return text}}});jQuery.ajaxPrefilter("script",function(s){if(s.cache===undefined){s.cache=false}if(s.crossDomain){s.type="GET";s.global=false}});jQuery.ajaxTransport("script",function(s){if(s.crossDomain){var script,head=document.head||document.getElementsByTagName("head")[0]||document.documentElement;return{send:function(_,callback){script=document.createElement("script");script.async="async";if(s.scriptCharset){script.charset=s.scriptCharset}script.src=s.url;script.onload=script.onreadystatechange=function(_,isAbort){if(isAbort||!script.readyState||/loaded|complete/.test(script.readyState)){script.onload=script.onreadystatechange=null;if(head&&script.parentNode){head.removeChild(script)}script=undefined;if(!isAbort){callback(200,"success")}}};head.insertBefore(script,head.firstChild)},abort:function(){if(script){script.onload(0,1)}}}}});var xhrCallbacks,xhrOnUnloadAbort=window.ActiveXObject?function(){for(var key in xhrCallbacks){xhrCallbacks[key](0,1)}}:false,xhrId=0;function createStandardXHR(){try{return new window.XMLHttpRequest}catch(e){}}function createActiveXHR(){try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(e){}}jQuery.ajaxSettings.xhr=window.ActiveXObject?function(){return!this.isLocal&&createStandardXHR()||createActiveXHR()}:createStandardXHR;(function(xhr){jQuery.extend(jQuery.support,{ajax:!!xhr,cors:!!xhr&&"withCredentials"in xhr})})(jQuery.ajaxSettings.xhr());if(jQuery.support.ajax){jQuery.ajaxTransport(function(s){if(!s.crossDomain||jQuery.support.cors){var callback;return{send:function(headers,complete){var handle,i,xhr=s.xhr();if(s.username){xhr.open(s.type,s.url,s.async,s.username,s.password)}else{xhr.open(s.type,s.url,s.async)}if(s.xhrFields){for(i in s.xhrFields){xhr[i]=s.xhrFields[i]}}if(s.mimeType&&xhr.overrideMimeType){xhr.overrideMimeType(s.mimeType)}if(!s.crossDomain&&!headers["X-Requested-With"]){headers["X-Requested-With"]="XMLHttpRequest"}try{for(i in headers){xhr.setRequestHeader(i,headers[i])}}catch(_){}xhr.send(s.hasContent&&s.data||null);callback=function(_,isAbort){var status,statusText,responseHeaders,responses,xml;try{if(callback&&(isAbort||xhr.readyState===4)){callback=undefined;if(handle){xhr.onreadystatechange=jQuery.noop;if(xhrOnUnloadAbort){delete xhrCallbacks[handle]}}if(isAbort){if(xhr.readyState!==4){xhr.abort()}}else{status=xhr.status;responseHeaders=xhr.getAllResponseHeaders();responses={};xml=xhr.responseXML;if(xml&&xml.documentElement){responses.xml=xml}try{responses.text=xhr.responseText}catch(e){}try{statusText=xhr.statusText}catch(e){statusText=""}if(!status&&s.isLocal&&!s.crossDomain){status=responses.text?200:404}else if(status===1223){status=204}}}}catch(firefoxAccessException){if(!isAbort){complete(-1,firefoxAccessException)}}if(responses){complete(status,statusText,responses,responseHeaders)}};if(!s.async){callback()}else if(xhr.readyState===4){setTimeout(callback,0)}else{handle=++xhrId;if(xhrOnUnloadAbort){if(!xhrCallbacks){xhrCallbacks={};jQuery(window).unload(xhrOnUnloadAbort)}xhrCallbacks[handle]=callback}xhr.onreadystatechange=callback}},abort:function(){if(callback){callback(0,1)}}}}})}var fxNow,timerId,rfxtypes=/^(?:toggle|show|hide)$/,rfxnum=new RegExp("^(?:([-+])=|)("+core_pnum+")([a-z%]*)$","i"),rrun=/queueHooks$/,animationPrefilters=[defaultPrefilter],tweeners={"*":[function(prop,value){var end,unit,tween=this.createTween(prop,value),parts=rfxnum.exec(value),target=tween.cur(),start=+target||0,scale=1,maxIterations=20;if(parts){end=+parts[2];unit=parts[3]||(jQuery.cssNumber[prop]?"":"px");if(unit!=="px"&&start){start=jQuery.css(tween.elem,prop,true)||end||1;do{scale=scale||".5";start=start/scale;jQuery.style(tween.elem,prop,start+unit)}while(scale!==(scale=tween.cur()/target)&&scale!==1&&--maxIterations)}tween.unit=unit;tween.start=start;tween.end=parts[1]?start+(parts[1]+1)*end:end}return tween}]};function createFxNow(){setTimeout(function(){fxNow=undefined},0);return fxNow=jQuery.now()}function createTweens(animation,props){jQuery.each(props,function(prop,value){var collection=(tweeners[prop]||[]).concat(tweeners["*"]),index=0,length=collection.length;for(;index-1,props={},curPosition={},curTop,curLeft;if(calculatePosition){curPosition=curElem.position();curTop=curPosition.top;curLeft=curPosition.left}else{curTop=parseFloat(curCSSTop)||0;curLeft=parseFloat(curCSSLeft)||0}if(jQuery.isFunction(options)){options=options.call(elem,i,curOffset)}if(options.top!=null){props.top=options.top-curOffset.top+curTop}if(options.left!=null){props.left=options.left-curOffset.left+curLeft}if("using"in options){options.using.call(elem,props)}else{curElem.css(props)}}};jQuery.fn.extend({position:function(){if(!this[0]){return}var elem=this[0],offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=rroot.test(offsetParent[0].nodeName)?{top:0,left:0}:offsetParent.offset();offset.top-=parseFloat(jQuery.css(elem,"marginTop"))||0;offset.left-=parseFloat(jQuery.css(elem,"marginLeft"))||0;parentOffset.top+=parseFloat(jQuery.css(offsetParent[0],"borderTopWidth"))||0;parentOffset.left+=parseFloat(jQuery.css(offsetParent[0],"borderLeftWidth"))||0;return{top:offset.top-parentOffset.top,left:offset.left-parentOffset.left}},offsetParent:function(){return this.map(function(){var offsetParent=this.offsetParent||document.body;while(offsetParent&&(!rroot.test(offsetParent.nodeName)&&jQuery.css(offsetParent,"position")==="static")){offsetParent=offsetParent.offsetParent}return offsetParent||document.body})}});jQuery.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(method,prop){var top=/Y/.test(prop);jQuery.fn[method]=function(val){return jQuery.access(this,function(elem,method,val){var win=getWindow(elem);if(val===undefined){return win?prop in win?win[prop]:win.document.documentElement[method]:elem[method]}if(win){win.scrollTo(!top?val:jQuery(win).scrollLeft(),top?val:jQuery(win).scrollTop())}else{elem[method]=val}},method,val,arguments.length,null)}});function getWindow(elem){return jQuery.isWindow(elem)?elem:elem.nodeType===9?elem.defaultView||elem.parentWindow:false}jQuery.each({Height:"height",Width:"width"},function(name,type){jQuery.each({padding:"inner"+name,content:type,"":"outer"+name},function(defaultExtra,funcName){jQuery.fn[funcName]=function(margin,value){var chainable=arguments.length&&(defaultExtra||typeof margin!=="boolean"),extra=defaultExtra||(margin===true||value===true?"margin":"border");return jQuery.access(this,function(elem,type,value){var doc;if(jQuery.isWindow(elem)){return elem.document.documentElement["client"+name]}if(elem.nodeType===9){doc=elem.documentElement;return Math.max(elem.body["scroll"+name],doc["scroll"+name],elem.body["offset"+name],doc["offset"+name],doc["client"+name])}return value===undefined?jQuery.css(elem,type,value,extra):jQuery.style(elem,type,value,extra)},type,chainable?margin:undefined,chainable,null)}})});window.jQuery=window.$=jQuery;if(typeof define==="function"&&define.amd&&define.amd.jQuery){define("jquery",[],function(){return jQuery})}})(window); \ No newline at end of file From d9e558720f3bf2bc88ad35c63538ad9dcc1826c5 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 11:38:54 +0200 Subject: [PATCH 144/204] Fix typos --- utils/check-style/check-typos | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/check-style/check-typos b/utils/check-style/check-typos index b29f97107d5..3819b6785d7 100755 --- a/utils/check-style/check-typos +++ b/utils/check-style/check-typos @@ -5,7 +5,7 @@ ROOT_PATH=$(git rev-parse --show-toplevel) codespell \ - --skip "*generated*,*gperf*,*.bin,*.mrk*,*.idx,checksums.txt,*.dat,*.pyc,*.kate-swp,*obfuscateQueries.cpp,${ROOT_PATH}/utils/check-style/aspell-ignore" \ + --skip "*generated*,*gperf*,*.bin,*.mrk*,*.idx,checksums.txt,*.dat,*.pyc,*.kate-swp,*obfuscateQueries.cpp,d3-*.js,*.min.js,${ROOT_PATH}/utils/check-style/aspell-ignore" \ --ignore-words "${ROOT_PATH}/utils/check-style/codespell-ignore-words.list" \ --exclude-file "${ROOT_PATH}/utils/check-style/codespell-ignore-lines.list" \ --quiet-level 2 \ From c737bc2bcf538636f9af03fe6a81c66cf463f99f Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 12:41:02 +0300 Subject: [PATCH 145/204] Update README.md --- utils/trace-visualizer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/trace-visualizer/README.md b/utils/trace-visualizer/README.md index c38c4b8eeea..63a6a737e3c 100644 --- a/utils/trace-visualizer/README.md +++ b/utils/trace-visualizer/README.md @@ -1,7 +1,7 @@ Trace visualizer is a tool for representation of a tracing data as a Gantt diagram. # Quick start -For now this tool is not integrate into clickhouse and require a lot of manual adjustments. +For now this tool is not integrated into ClickHouse and requires a lot of manual adjustments. ```bash cd utils/trace-visualizer python3 -m http.server From ca649da97f1b9c71e1828d8d9c3329e1da2f255b Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 16 Jun 2022 12:12:01 +0200 Subject: [PATCH 146/204] better comments --- tests/queries/0_stateless/00984_parser_stack_overflow.sh | 2 +- tests/queries/0_stateless/01172_transaction_counters.sql | 2 +- tests/queries/0_stateless/01183_custom_separated_format_http.sh | 2 +- .../0_stateless/01184_long_insert_values_huge_strings.sh | 2 +- tests/queries/0_stateless/01651_lc_insert_tiny_log.sql | 2 +- .../0_stateless/01746_long_zstd_http_compression_json_format.sh | 2 +- tests/queries/0_stateless/01926_order_by_desc_limit.sql | 2 +- tests/queries/1_stateful/00159_parallel_formatting_http.sh | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/queries/0_stateless/00984_parser_stack_overflow.sh b/tests/queries/0_stateless/00984_parser_stack_overflow.sh index 7c4a6336a51..168ef155d9b 100755 --- a/tests/queries/0_stateless/00984_parser_stack_overflow.sh +++ b/tests/queries/0_stateless/00984_parser_stack_overflow.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Tags: no-tsan -# FIXME should work with tsan +# FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan # Such a huge timeout mostly for debug build. CLICKHOUSE_CURL_TIMEOUT=60 diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index 83bad35c40b..8e04b6c89bd 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -1,6 +1,6 @@ -- Tags: no-s3-storage, no-tsan -- FIXME this test fails with S3 due to a bug in DiskCacheWrapper --- FIXME should work with tsan +-- FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan drop table if exists txn_counters; create table txn_counters (n Int64, creation_tid DEFAULT transactionID()) engine=MergeTree order by n; diff --git a/tests/queries/0_stateless/01183_custom_separated_format_http.sh b/tests/queries/0_stateless/01183_custom_separated_format_http.sh index 0aa750a171f..744cf0c08bd 100755 --- a/tests/queries/0_stateless/01183_custom_separated_format_http.sh +++ b/tests/queries/0_stateless/01183_custom_separated_format_http.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Tags: no-tsan -# FIXME should work with tsan +# FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh b/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh index 9c3a2d295f7..f4bad961f21 100755 --- a/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh +++ b/tests/queries/0_stateless/01184_long_insert_values_huge_strings.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Tags: long, no-tsan -# FIXME should work with tsan +# FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql b/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql index 15baa88ec4e..ec2a1850594 100644 --- a/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql +++ b/tests/queries/0_stateless/01651_lc_insert_tiny_log.sql @@ -1,5 +1,5 @@ -- Tags: no-tsan --- FIXME should work with tsan +-- FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan drop table if exists perf_lc_num; diff --git a/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh b/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh index e198adc2dc6..16f5211f012 100755 --- a/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh +++ b/tests/queries/0_stateless/01746_long_zstd_http_compression_json_format.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Tags: long, no-fasttest, no-tsan -# FIXME should work with tsan +# FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01926_order_by_desc_limit.sql b/tests/queries/0_stateless/01926_order_by_desc_limit.sql index 785a2e10ee3..223dbf70fc4 100644 --- a/tests/queries/0_stateless/01926_order_by_desc_limit.sql +++ b/tests/queries/0_stateless/01926_order_by_desc_limit.sql @@ -1,5 +1,5 @@ -- Tags: no-random-settings, no-tsan --- FIXME should work with tsan +-- FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan DROP TABLE IF EXISTS order_by_desc; diff --git a/tests/queries/1_stateful/00159_parallel_formatting_http.sh b/tests/queries/1_stateful/00159_parallel_formatting_http.sh index b7382c5f491..7b949cf23e6 100755 --- a/tests/queries/1_stateful/00159_parallel_formatting_http.sh +++ b/tests/queries/1_stateful/00159_parallel_formatting_http.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Tags: no-tsan -# FIXME should work with tsan +# FIXME It became flaky after upgrading to llvm-14 due to obscure freezes in tsan CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 3e8aa2dc47a24e2cfdc4d116418ec213fa8ff922 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 16 Jun 2022 13:24:21 +0300 Subject: [PATCH 147/204] Update run.sh --- docker/test/stress/run.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index fa9d427aa33..9688ae424c9 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -340,6 +340,9 @@ then -e "UNFINISHED" \ -e "Renaming unexpected part" \ -e "PART_IS_TEMPORARILY_LOCKED" \ + -e "and a merge is impossible: we didn't find smaller parts" \ + -e "found in queue and some source parts for it was lost" \ + -e "is lost forever." \ /var/log/clickhouse-server/clickhouse-server.backward.clean.log | zgrep -Fa "" > /test_output/bc_check_error_messages.txt \ && echo -e 'Backward compatibility check: Error message in clickhouse-server.log (see bc_check_error_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'Backward compatibility check: No Error messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv From a3a7cc7a5dad48765fb46106c38821b7644e4440 Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 16 Jun 2022 10:41:53 +0000 Subject: [PATCH 148/204] Fix logical error in array mapped functions with const nullable column --- src/Functions/array/FunctionArrayMapped.h | 26 +++++++++++++------ ...mapped_array_witn_const_nullable.reference | 18 +++++++++++++ ...02320_mapped_array_witn_const_nullable.sql | 7 +++++ 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.reference create mode 100644 tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql diff --git a/src/Functions/array/FunctionArrayMapped.h b/src/Functions/array/FunctionArrayMapped.h index 0af68910b70..c4ac89df78e 100644 --- a/src/Functions/array/FunctionArrayMapped.h +++ b/src/Functions/array/FunctionArrayMapped.h @@ -342,16 +342,26 @@ public: else if (lambda_result.column->isNullable()) { auto result_column = IColumn::mutate(std::move(lambda_result.column)); - auto * column_nullable = assert_cast(result_column.get()); - auto & null_map = column_nullable->getNullMapData(); - auto nested_column = IColumn::mutate(std::move(column_nullable->getNestedColumnPtr())); - auto & nested_data = assert_cast(nested_column.get())->getData(); - for (size_t i = 0; i != nested_data.size(); ++i) + + if (isColumnConst(*result_column)) { - if (null_map[i]) - nested_data[i] = 0; + UInt8 value = result_column->empty() ? 0 : result_column->getBool(0); + auto result_type = std::make_shared(); + lambda_result.column = result_type->createColumnConst(result_column->size(), value); + } + else + { + auto * column_nullable = assert_cast(result_column.get()); + auto & null_map = column_nullable->getNullMapData(); + auto nested_column = IColumn::mutate(std::move(column_nullable->getNestedColumnPtr())); + auto & nested_data = assert_cast(nested_column.get())->getData(); + for (size_t i = 0; i != nested_data.size(); ++i) + { + if (null_map[i]) + nested_data[i] = 0; + } + lambda_result.column = std::move(nested_column); } - lambda_result.column = std::move(nested_column); } } diff --git a/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.reference b/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.reference new file mode 100644 index 00000000000..dc31f044eef --- /dev/null +++ b/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.reference @@ -0,0 +1,18 @@ +[] +[1] +[1,1] +[] +[0] +[0,1] +[] +[0] +[0,0] +[] +[] +[] +[] +[NULL] +[NULL,NULL] +[] +[] +[] diff --git a/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql b/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql new file mode 100644 index 00000000000..48f34fa8fc0 --- /dev/null +++ b/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql @@ -0,0 +1,7 @@ +select arrayMap(x -> toNullable(1), range(number)) from numbers(3); +select arrayFilter(x -> toNullable(1), range(number)) from numbers(3); +select arrayMap(x -> toNullable(0), range(number)) from numbers(3); +select arrayFilter(x -> toNullable(0), range(number)) from numbers(3); +select arrayMap(x -> NULL::Nullable(UInt8), range(number)) from numbers(3); +select arrayFilter(x -> NULL::Nullable(UInt8), range(number)) from numbers(3); + From 72e02748886d77f9e63188ef89a28421f0b0753c Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 14:21:13 +0200 Subject: [PATCH 149/204] Edits --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 317bb766336..94ea0ce2118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * If you run different ClickHouse versions on a cluster with AArch64 CPU or mix AArch64 and amd64 on a cluster, and use distributed queries with GROUP BY multiple keys of fixed-size type that fit in 256 bits but don't fit in 64 bits, and the size of the result is huge, the data will not be fully aggregated in the result of these queries during upgrade. Workaround: upgrade with downtime instead of a rolling upgrade. #### New Feature +* Add `GROUPING` function. It allows to disambiguate the records in the queries with `ROLLUP`, `CUBE` or `GROUPING SETS`. Closes [#19426](https://github.com/ClickHouse/ClickHouse/issues/19426). [#37163](https://github.com/ClickHouse/ClickHouse/pull/37163) ([Dmitry Novik](https://github.com/novikd)). * A new codec [FPC](https://userweb.cs.txstate.edu/~burtscher/papers/dcc07a.pdf) algorithm for floating point data compression. [#37553](https://github.com/ClickHouse/ClickHouse/pull/37553) ([Mikhail Guzov](https://github.com/koloshmet)). * Add new columnar JSON formats: `JSONColumns`, `JSONCompactColumns`, `JSONColumnsWithMetadata`. Closes [#36338](https://github.com/ClickHouse/ClickHouse/issues/36338) Closes [#34509](https://github.com/ClickHouse/ClickHouse/issues/34509). [#36975](https://github.com/ClickHouse/ClickHouse/pull/36975) ([Kruglov Pavel](https://github.com/Avogar)). * Added open telemetry traces visualizing tool based on d3js. [#37810](https://github.com/ClickHouse/ClickHouse/pull/37810) ([Sergei Trifonov](https://github.com/serxa)). @@ -38,7 +39,6 @@ * Added `SYSTEM UNFREEZE` query that deletes the whole backup regardless if the corresponding table is deleted or not. [#36424](https://github.com/ClickHouse/ClickHouse/pull/36424) ([Vadim Volodin](https://github.com/PolyProgrammist)). #### Experimental Feature -* Add `GROUPING` function. Closes [#19426](https://github.com/ClickHouse/ClickHouse/issues/19426). [#37163](https://github.com/ClickHouse/ClickHouse/pull/37163) ([Dmitry Novik](https://github.com/novikd)). The behavior of this function will be changed in subsequent releases. * Enables `POPULATE` for `WINDOW VIEW`. [#36945](https://github.com/ClickHouse/ClickHouse/pull/36945) ([vxider](https://github.com/Vxider)). * `ALTER TABLE ... MODIFY QUERY` support for `WINDOW VIEW`. [#37188](https://github.com/ClickHouse/ClickHouse/pull/37188) ([vxider](https://github.com/Vxider)). * This PR changes the behavior of the `ENGINE` syntax in `WINDOW VIEW`, to make it like in `MATERIALIZED VIEW`. [#37214](https://github.com/ClickHouse/ClickHouse/pull/37214) ([vxider](https://github.com/Vxider)). From 7ed305f9b17095d07980c2018aa8bc5560eb8b48 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 13:20:03 +0200 Subject: [PATCH 150/204] Improvement of cherry-pick/backport script - cherry_pick.py now can ba launched locally, with dry-run - get rid of fallback import paths - do not create a huge pile of objects for every sneezing - the same for hidden imports in deep local functions - improve logging - fix imports for cherry_pick_utils entities - Significantly reduced requests to GraphQL API --- tests/ci/cherry_pick.py | 86 +++++++++++------ tests/ci/cherry_pick_utils/__init__.py | 1 + tests/ci/cherry_pick_utils/backport.py | 59 ++++++------ tests/ci/cherry_pick_utils/cherrypick.py | 23 ++--- tests/ci/cherry_pick_utils/local.py | 15 +-- tests/ci/cherry_pick_utils/query.py | 116 ++++++++++++----------- 6 files changed, 171 insertions(+), 129 deletions(-) diff --git a/tests/ci/cherry_pick.py b/tests/ci/cherry_pick.py index 4bbd30cd186..d9f94ffa6c7 100644 --- a/tests/ci/cherry_pick.py +++ b/tests/ci/cherry_pick.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import sys +import argparse import logging import os import subprocess @@ -12,37 +12,61 @@ from cherry_pick_utils.backport import Backport from cherry_pick_utils.cherrypick import CherryPick +def parse_args(): + parser = argparse.ArgumentParser("Create cherry-pick and backport PRs") + parser.add_argument("--token", help="github token, if not set, used from smm") + parser.add_argument("--dry-run", action="store_true", help="do not create anything") + return parser.parse_args() + + +def main(): + args = parse_args() + token = args.token or get_parameter_from_ssm("github_robot_token_1") + + bp = Backport( + token, + os.environ.get("REPO_OWNER"), + os.environ.get("REPO_NAME"), + os.environ.get("REPO_TEAM"), + ) + + cherry_pick = CherryPick( + token, + os.environ.get("REPO_OWNER"), + os.environ.get("REPO_NAME"), + os.environ.get("REPO_TEAM"), + 1, + "master", + ) + # Use the same _gh in both objects to have a proper cost + # pylint: disable=protected-access + for key in bp._gh.api_costs: + if key in cherry_pick._gh.api_costs: + bp._gh.api_costs[key] += cherry_pick._gh.api_costs[key] + for key in cherry_pick._gh.api_costs: + if key not in bp._gh.api_costs: + bp._gh.api_costs[key] = cherry_pick._gh.api_costs[key] + cherry_pick._gh = bp._gh + # pylint: enable=protected-access + + def cherrypick_run(pr_data, branch): + cherry_pick.update_pr_branch(pr_data, branch) + return cherry_pick.execute(GITHUB_WORKSPACE, args.dry_run) + + try: + bp.execute(GITHUB_WORKSPACE, "origin", None, cherrypick_run) + except subprocess.CalledProcessError as e: + logging.error(e.output) + + if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - repo_path = GITHUB_WORKSPACE - temp_path = TEMP_PATH - if not os.path.exists(temp_path): - os.makedirs(temp_path) + if not os.path.exists(TEMP_PATH): + os.makedirs(TEMP_PATH) - sys.path.append(os.path.join(repo_path, "utils/github")) - - with SSHKey("ROBOT_CLICKHOUSE_SSH_KEY"): - token = get_parameter_from_ssm("github_robot_token_1") - - bp = Backport( - token, - os.environ.get("REPO_OWNER"), - os.environ.get("REPO_NAME"), - os.environ.get("REPO_TEAM"), - ) - - def cherrypick_run(token, pr, branch): - return CherryPick( - token, - os.environ.get("REPO_OWNER"), - os.environ.get("REPO_NAME"), - os.environ.get("REPO_TEAM"), - pr, - branch, - ).execute(repo_path, False) - - try: - bp.execute(repo_path, "origin", None, cherrypick_run) - except subprocess.CalledProcessError as e: - logging.error(e.output) + if os.getenv("ROBOT_CLICKHOUSE_SSH_KEY", ""): + with SSHKey("ROBOT_CLICKHOUSE_SSH_KEY"): + main() + else: + main() diff --git a/tests/ci/cherry_pick_utils/__init__.py b/tests/ci/cherry_pick_utils/__init__.py index 40a96afc6ff..faa18be5bbf 100644 --- a/tests/ci/cherry_pick_utils/__init__.py +++ b/tests/ci/cherry_pick_utils/__init__.py @@ -1 +1,2 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- diff --git a/tests/ci/cherry_pick_utils/backport.py b/tests/ci/cherry_pick_utils/backport.py index 615c0d19ffa..9a510caf92a 100644 --- a/tests/ci/cherry_pick_utils/backport.py +++ b/tests/ci/cherry_pick_utils/backport.py @@ -1,19 +1,17 @@ # -*- coding: utf-8 -*- -try: - from clickhouse.utils.github.cherrypick import CherryPick - from clickhouse.utils.github.query import Query as RemoteRepo - from clickhouse.utils.github.local import Repository as LocalRepo -except: - from .cherrypick import CherryPick - from .query import Query as RemoteRepo - from .local import Repository as LocalRepo - import argparse import logging +import os import re import sys +sys.path.append(os.path.dirname(__file__)) + +from cherrypick import CherryPick +from query import Query as RemoteRepo +from local import Repository as LocalRepo + class Backport: def __init__(self, token, owner, name, team): @@ -49,14 +47,16 @@ class Backport: logging.info("No release branches found!") return - for branch in branches: - logging.info("Found release branch: %s", branch[0]) + logging.info( + "Found release branches: %s", ", ".join([br[0] for br in branches]) + ) if not until_commit: until_commit = branches[0][1] pull_requests = self.getPullRequests(until_commit) backport_map = {} + pr_map = {pr["number"]: pr for pr in pull_requests} RE_MUST_BACKPORT = re.compile(r"^v(\d+\.\d+)-must-backport$") RE_NO_BACKPORT = re.compile(r"^v(\d+\.\d+)-no-backport$") @@ -68,17 +68,17 @@ class Backport: pr["mergeCommit"]["oid"] ): logging.info( - "PR #{} is already inside {}. Dropping this branch for further PRs".format( - pr["number"], branches[-1][0] - ) + "PR #%s is already inside %s. Dropping this branch for further PRs", + pr["number"], + branches[-1][0], ) branches.pop() - logging.info("Processing PR #{}".format(pr["number"])) + logging.info("Processing PR #%s", pr["number"]) - assert len(branches) + assert len(branches) != 0 - branch_set = set([branch[0] for branch in branches]) + branch_set = {branch[0] for branch in branches} # First pass. Find all must-backports for label in pr["labels"]["nodes"]: @@ -120,16 +120,16 @@ class Backport: ) for pr, branches in list(backport_map.items()): - logging.info("PR #%s needs to be backported to:", pr) + statuses = [] for branch in branches: - logging.info( - "\t%s, and the status is: %s", - branch, - run_cherrypick(self._token, pr, branch), - ) + branch_status = run_cherrypick(pr_map[pr], branch) + statuses.append(f"{branch}, and the status is: {branch_status}") + logging.info( + "PR #%s needs to be backported to:\n\t%s", pr, "\n\t".join(statuses) + ) # print API costs - logging.info("\nGitHub API total costs per query:") + logging.info("\nGitHub API total costs for backporting per query:") for name, value in list(self._gh.api_costs.items()): logging.info("%s : %s", name, value) @@ -178,8 +178,13 @@ if __name__ == "__main__": else: logging.basicConfig(format="%(message)s", stream=sys.stdout, level=logging.INFO) - cherrypick_run = lambda token, pr, branch: CherryPick( - token, "ClickHouse", "ClickHouse", "core", pr, branch - ).execute(args.repo, args.dry_run) + cherry_pick = CherryPick( + args.token, "ClickHouse", "ClickHouse", "core", 1, "master" + ) + + def cherrypick_run(pr_data, branch): + cherry_pick.update_pr_branch(pr_data, branch) + return cherry_pick.execute(args.repo, args.dry_run) + bp = Backport(args.token, "ClickHouse", "ClickHouse", "core") bp.execute(args.repo, args.upstream, args.til, cherrypick_run) diff --git a/tests/ci/cherry_pick_utils/cherrypick.py b/tests/ci/cherry_pick_utils/cherrypick.py index c6469fa62a9..a6e4b9eec5b 100644 --- a/tests/ci/cherry_pick_utils/cherrypick.py +++ b/tests/ci/cherry_pick_utils/cherrypick.py @@ -14,10 +14,6 @@ Second run checks PR from previous run to be merged or at least being mergeable. Third run creates PR from backport branch (with merged previous PR) to release branch. """ -try: - from clickhouse.utils.github.query import Query as RemoteRepo -except: - from .query import Query as RemoteRepo import argparse from enum import Enum @@ -26,6 +22,10 @@ import os import subprocess import sys +sys.path.append(os.path.dirname(__file__)) + +from query import Query as RemoteRepo + class CherryPick: class Status(Enum): @@ -45,20 +45,21 @@ class CherryPick: def __init__(self, token, owner, name, team, pr_number, target_branch): self._gh = RemoteRepo(token, owner=owner, name=name, team=team) self._pr = self._gh.get_pull_request(pr_number) + self.target_branch = target_branch self.ssh_url = self._gh.ssh_url # TODO: check if pull-request is merged. + self.update_pr_branch(self._pr, self.target_branch) + def update_pr_branch(self, pr_data, target_branch): + """The method is here to avoid unnecessary creation of new objects""" + self._pr = pr_data + self.target_branch = target_branch self.merge_commit_oid = self._pr["mergeCommit"]["oid"] - self.target_branch = target_branch - self.backport_branch = "backport/{branch}/{pr}".format( - branch=target_branch, pr=pr_number - ) - self.cherrypick_branch = "cherrypick/{branch}/{oid}".format( - branch=target_branch, oid=self.merge_commit_oid - ) + self.backport_branch = f"backport/{target_branch}/{pr_data['number']}" + self.cherrypick_branch = f"cherrypick/{target_branch}/{self.merge_commit_oid}" def getCherryPickPullRequest(self): return self._gh.find_pull_request( diff --git a/tests/ci/cherry_pick_utils/local.py b/tests/ci/cherry_pick_utils/local.py index 571c9102ba0..71923b63c35 100644 --- a/tests/ci/cherry_pick_utils/local.py +++ b/tests/ci/cherry_pick_utils/local.py @@ -5,10 +5,11 @@ import logging import os import re +import git + class RepositoryBase: def __init__(self, repo_path): - import git self._repo = git.Repo(repo_path, search_parent_directories=(not repo_path)) @@ -23,22 +24,22 @@ class RepositoryBase: self.comparator = functools.cmp_to_key(cmp) - def get_head_commit(self): - return self._repo.commit(self._default) - def iterate(self, begin, end): - rev_range = "{}...{}".format(begin, end) + rev_range = f"{begin}...{end}" for commit in self._repo.iter_commits(rev_range, first_parent=True): yield commit class Repository(RepositoryBase): def __init__(self, repo_path, remote_name, default_branch_name): - super(Repository, self).__init__(repo_path) + super().__init__(repo_path) self._remote = self._repo.remotes[remote_name] self._remote.fetch() self._default = self._remote.refs[default_branch_name] + def get_head_commit(self): + return self._repo.commit(self._default) + def get_release_branches(self): """ Returns sorted list of tuples: @@ -73,7 +74,7 @@ class Repository(RepositoryBase): class BareRepository(RepositoryBase): def __init__(self, repo_path, default_branch_name): - super(BareRepository, self).__init__(repo_path) + super().__init__(repo_path) self._default = self._repo.branches[default_branch_name] def get_release_branches(self): diff --git a/tests/ci/cherry_pick_utils/query.py b/tests/ci/cherry_pick_utils/query.py index 40eb5bf3604..577a7185142 100644 --- a/tests/ci/cherry_pick_utils/query.py +++ b/tests/ci/cherry_pick_utils/query.py @@ -1,7 +1,13 @@ # -*- coding: utf-8 -*- -import requests +import json +import inspect +import logging import time +from urllib3.util.retry import Retry # type: ignore + +import requests # type: ignore +from requests.adapters import HTTPAdapter # type: ignore class Query: @@ -56,6 +62,7 @@ class Query: self._owner = owner self._name = name self._team = team + self._session = None self._max_page_size = max_page_size self._min_page_size = min_page_size @@ -129,7 +136,11 @@ class Query: next='after: "{}"'.format(result["pageInfo"]["endCursor"]), ) - members += dict([(node["login"], node["id"]) for node in result["nodes"]]) + # Update members with new nodes compatible with py3.8-py3.10 + members = { + **members, + **{node["login"]: node["id"] for node in result["nodes"]}, + } return members @@ -415,32 +426,37 @@ class Query: query = _SET_LABEL.format(pr_id=pull_request["id"], label_id=labels[0]["id"]) self._run(query, is_mutation=True) + @property + def session(self): + if self._session is not None: + return self._session + retries = 5 + self._session = requests.Session() + retry = Retry( + total=retries, + read=retries, + connect=retries, + backoff_factor=1, + status_forcelist=(403, 500, 502, 504), + ) + adapter = HTTPAdapter(max_retries=retry) + self._session.mount("http://", adapter) + self._session.mount("https://", adapter) + return self._session + def _run(self, query, is_mutation=False): - from requests.adapters import HTTPAdapter - from urllib3.util.retry import Retry + # Get caller and parameters from the stack to track the progress + frame = inspect.getouterframes(inspect.currentframe(), 2)[1] + caller = frame[3] + f_parameters = inspect.signature(getattr(self, caller)).parameters + parameters = ", ".join(str(frame[0].f_locals[p]) for p in f_parameters) + mutation = "" + if is_mutation: + mutation = ", is mutation" + print(f"---GraphQL request for {caller}({parameters}){mutation}---") # sleep a little, because we querying github too often - print("Request, is mutation", is_mutation) - time.sleep(0.5) - - def requests_retry_session( - retries=5, - backoff_factor=0.5, - status_forcelist=(403, 500, 502, 504), - session=None, - ): - session = session or requests.Session() - retry = Retry( - total=retries, - read=retries, - connect=retries, - backoff_factor=backoff_factor, - status_forcelist=status_forcelist, - ) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) - return session + time.sleep(0.1) headers = {"Authorization": "bearer {}".format(self._token)} if is_mutation: @@ -464,34 +480,28 @@ class Query: query=query ) - while True: - request = requests_retry_session().post( - "https://api.github.com/graphql", json={"query": query}, headers=headers - ) - if request.status_code == 200: - result = request.json() - if "errors" in result: - raise Exception( - "Errors occurred: {}\nOriginal query: {}".format( - result["errors"], query - ) - ) - - if not is_mutation: - import inspect - - caller = inspect.getouterframes(inspect.currentframe(), 2)[1][3] - if caller not in list(self.api_costs.keys()): - self.api_costs[caller] = 0 - self.api_costs[caller] += result["data"]["rateLimit"]["cost"] - - return result["data"] - else: - import json - + response = self.session.post( + "https://api.github.com/graphql", json={"query": query}, headers=headers + ) + if response.status_code == 200: + result = response.json() + if "errors" in result: raise Exception( - "Query failed with code {code}:\n{json}".format( - code=request.status_code, - json=json.dumps(request.json(), indent=4), + "Errors occurred: {}\nOriginal query: {}".format( + result["errors"], query ) ) + + if not is_mutation: + if caller not in list(self.api_costs.keys()): + self.api_costs[caller] = 0 + self.api_costs[caller] += result["data"]["rateLimit"]["cost"] + + return result["data"] + else: + raise Exception( + "Query failed with code {code}:\n{json}".format( + code=response.status_code, + json=json.dumps(response.json(), indent=4), + ) + ) From d7505d4de7283f62c8edb183da53e15d2c7646e6 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 14:33:39 +0200 Subject: [PATCH 151/204] Change alignment, fix some f-strings --- tests/ci/cherry_pick_utils/cherrypick.py | 25 +- tests/ci/cherry_pick_utils/query.py | 362 +++++++++++------------ 2 files changed, 187 insertions(+), 200 deletions(-) diff --git a/tests/ci/cherry_pick_utils/cherrypick.py b/tests/ci/cherry_pick_utils/cherrypick.py index a6e4b9eec5b..92c87800828 100644 --- a/tests/ci/cherry_pick_utils/cherrypick.py +++ b/tests/ci/cherry_pick_utils/cherrypick.py @@ -119,17 +119,16 @@ class CherryPick: ) # Create pull-request like a local cherry-pick + title = self._pr["title"].replace('"', r"\"") pr = self._gh.create_pull_request( source=self.cherrypick_branch, target=self.backport_branch, - title="Cherry pick #{number} to {target}: {title}".format( - number=self._pr["number"], - target=self.target_branch, - title=self._pr["title"].replace('"', '\\"'), - ), - description="Original pull-request #{}\n\n{}".format( - self._pr["number"], DESCRIPTION + title=( + f'Cherry pick #{self._pr["number"]} ' + f"to {self.target_branch}: " + f"{title}" ), + description=f'Original pull-request #{self._pr["number"]}\n\n{DESCRIPTION}', ) # FIXME: use `team` to leave a single eligible assignee. @@ -166,11 +165,8 @@ class CherryPick: "user.name=robot-clickhouse", ] - pr_title = "Backport #{number} to {target}: {title}".format( - number=self._pr["number"], - target=self.target_branch, - title=self._pr["title"].replace('"', '\\"'), - ) + title = (self._pr["title"].replace('"', r"\""),) + pr_title = f"Backport #{self._pr['number']} to {self.target_branch}: {title}" self._run(git_prefix + ["checkout", "-f", self.backport_branch]) self._run(git_prefix + ["pull", "--ff-only", "origin", self.backport_branch]) @@ -204,9 +200,8 @@ class CherryPick: source=self.backport_branch, target=self.target_branch, title=pr_title, - description="Original pull-request #{}\nCherry-pick pull-request #{}\n\n{}".format( - self._pr["number"], cherrypick_pr["number"], DESCRIPTION - ), + description=f"Original pull-request #{self._pr['number']}\n" + f"Cherry-pick pull-request #{cherrypick_pr['number']}\n\n{DESCRIPTION}", ) # FIXME: use `team` to leave a single eligible assignee. diff --git a/tests/ci/cherry_pick_utils/query.py b/tests/ci/cherry_pick_utils/query.py index 577a7185142..ef1ad5ad0fb 100644 --- a/tests/ci/cherry_pick_utils/query.py +++ b/tests/ci/cherry_pick_utils/query.py @@ -16,43 +16,43 @@ class Query: """ _PULL_REQUEST = """ - author {{ - ... on User {{ - id - login - }} - }} - - baseRepository {{ - nameWithOwner - }} - - mergeCommit {{ - oid - parents(first: {min_page_size}) {{ - totalCount - nodes {{ - oid - }} - }} - }} - - mergedBy {{ - ... on User {{ - id - login - }} - }} - - baseRefName - closed - headRefName +author {{ + ... on User {{ id - mergeable - merged - number - title - url + login + }} +}} + +baseRepository {{ + nameWithOwner +}} + +mergeCommit {{ + oid + parents(first: {min_page_size}) {{ + totalCount + nodes {{ + oid + }} + }} +}} + +mergedBy {{ + ... on User {{ + id + login + }} +}} + +baseRefName +closed +headRefName +id +mergeable +merged +number +title +url """ def __init__(self, token, owner, name, team, max_page_size=100, min_page_size=10): @@ -78,13 +78,13 @@ class Query: def get_repository(self): _QUERY = """ - repository(owner: "{owner}" name: "{name}") {{ - defaultBranchRef {{ - name - }} - id - sshUrl - }} +repository(owner: "{owner}" name: "{name}") {{ + defaultBranchRef {{ + name + }} + id + sshUrl +}} """ query = _QUERY.format(owner=self._owner, name=self._name) @@ -98,20 +98,20 @@ class Query: """ _QUERY = """ - organization(login: "{organization}") {{ - team(slug: "{team}") {{ - members(first: {max_page_size} {next}) {{ - pageInfo {{ - hasNextPage - endCursor - }} - nodes {{ - id - login - }} - }} - }} +organization(login: "{organization}") {{ + team(slug: "{team}") {{ + members(first: {max_page_size} {next}) {{ + pageInfo {{ + hasNextPage + endCursor }} + nodes {{ + id + login + }} + }} + }} +}} """ members = {} @@ -133,7 +133,7 @@ class Query: organization=self._owner, team=self._team, max_page_size=self._max_page_size, - next='after: "{}"'.format(result["pageInfo"]["endCursor"]), + next=f'after: "{result["pageInfo"]["endCursor"]}"', ) # Update members with new nodes compatible with py3.8-py3.10 @@ -146,11 +146,11 @@ class Query: def get_pull_request(self, number): _QUERY = """ - repository(owner: "{owner}" name: "{name}") {{ - pullRequest(number: {number}) {{ - {pull_request_data} - }} - }} +repository(owner: "{owner}" name: "{name}") {{ + pullRequest(number: {number}) {{ + {pull_request_data} + }} +}} """ query = _QUERY.format( @@ -164,14 +164,16 @@ class Query: def find_pull_request(self, base, head): _QUERY = """ - repository(owner: "{owner}" name: "{name}") {{ - pullRequests(first: {min_page_size} baseRefName: "{base}" headRefName: "{head}") {{ - nodes {{ - {pull_request_data} - }} - totalCount - }} - }} +repository(owner: "{owner}" name: "{name}") {{ + pullRequests( + first: {min_page_size} baseRefName: "{base}" headRefName: "{head}" + ) {{ + nodes {{ + {pull_request_data} + }} + totalCount + }} +}} """ query = _QUERY.format( @@ -193,13 +195,13 @@ class Query: Get all pull-requests filtered by label name """ _QUERY = """ - repository(owner: "{owner}" name: "{name}") {{ - pullRequests(first: {min_page_size} labels: "{label_name}" states: OPEN) {{ - nodes {{ - {pull_request_data} - }} - }} - }} +repository(owner: "{owner}" name: "{name}") {{ + pullRequests(first: {min_page_size} labels: "{label_name}" states: OPEN) {{ + nodes {{ + {pull_request_data} + }} + }} +}} """ query = _QUERY.format( @@ -217,35 +219,32 @@ class Query: """ _QUERY = """ - repository(owner: "{owner}" name: "{name}") {{ - defaultBranchRef {{ - target {{ - ... on Commit {{ - history(first: {max_page_size} {next}) {{ - pageInfo {{ - hasNextPage - endCursor - }} - nodes {{ - oid - associatedPullRequests(first: {min_page_size}) {{ - totalCount - nodes {{ - ... on PullRequest {{ - {pull_request_data} +repository(owner: "{owner}" name: "{name}") {{ + defaultBranchRef {{ + target {{ + ... on Commit {{ + history(first: {max_page_size} {next}) {{ + pageInfo {{ + hasNextPage + endCursor + }} + nodes {{ + oid + associatedPullRequests(first: {min_page_size}) {{ + totalCount + nodes {{ + ... on PullRequest {{ + {pull_request_data} - labels(first: {min_page_size}) {{ - totalCount - pageInfo {{ - hasNextPage - endCursor - }} - nodes {{ - name - color - }} - }} - }} + labels(first: {min_page_size}) {{ + totalCount + pageInfo {{ + hasNextPage + endCursor + }} + nodes {{ + name + color }} }} }} @@ -254,6 +253,9 @@ class Query: }} }} }} + }} + }} +}} """ pull_requests = [] @@ -278,7 +280,7 @@ class Query: max_page_size=self._max_page_size, min_page_size=self._min_page_size, pull_request_data=self._PULL_REQUEST, - next='after: "{}"'.format(result["pageInfo"]["endCursor"]), + next=f'after: "{result["pageInfo"]["endCursor"]}"', ) for commit in result["nodes"]: @@ -296,7 +298,7 @@ class Query: for pull_request in commit["associatedPullRequests"]["nodes"]: if ( pull_request["baseRepository"]["nameWithOwner"] - == "{}/{}".format(self._owner, self._name) + == f"{self._owner}/{self._name}" and pull_request["baseRefName"] == self.default_branch and pull_request["mergeCommit"]["oid"] == commit["oid"] ): @@ -308,19 +310,19 @@ class Query: self, source, target, title, description="", draft=False, can_modify=True ): _QUERY = """ - createPullRequest(input: {{ - baseRefName: "{target}", - headRefName: "{source}", - repositoryId: "{id}", - title: "{title}", - body: "{body}", - draft: {draft}, - maintainerCanModify: {modify} - }}) {{ - pullRequest {{ - {pull_request_data} - }} - }} +createPullRequest(input: {{ + baseRefName: "{target}", + headRefName: "{source}", + repositoryId: "{id}", + title: "{title}", + body: "{body}", + draft: {draft}, + maintainerCanModify: {modify} +}}) {{ + pullRequest {{ + {pull_request_data} + }} +}} """ query = _QUERY.format( @@ -335,29 +337,29 @@ class Query: ) return self._run(query, is_mutation=True)["createPullRequest"]["pullRequest"] - def merge_pull_request(self, id): + def merge_pull_request(self, pr_id): _QUERY = """ - mergePullRequest(input: {{ - pullRequestId: "{id}" - }}) {{ - pullRequest {{ - {pull_request_data} - }} - }} +mergePullRequest(input: {{ + pullRequestId: "{pr_id}" +}}) {{ + pullRequest {{ + {pull_request_data} + }} +}} """ - query = _QUERY.format(id=id, pull_request_data=self._PULL_REQUEST) + query = _QUERY.format(pr_id=pr_id, pull_request_data=self._PULL_REQUEST) return self._run(query, is_mutation=True)["mergePullRequest"]["pullRequest"] # FIXME: figure out how to add more assignees at once def add_assignee(self, pr, assignee): _QUERY = """ - addAssigneesToAssignable(input: {{ - assignableId: "{id1}", - assigneeIds: "{id2}" - }}) {{ - clientMutationId - }} +addAssigneesToAssignable(input: {{ + assignableId: "{id1}", + assigneeIds: "{id2}" +}}) {{ + clientMutationId +}} """ query = _QUERY.format(id1=pr["id"], id2=assignee["id"]) @@ -373,28 +375,28 @@ class Query: """ _GET_LABEL = """ - repository(owner: "{owner}" name: "{name}") {{ - labels(first: {max_page_size} {next} query: "{label_name}") {{ - pageInfo {{ - hasNextPage - endCursor - }} - nodes {{ - id - name - color - }} - }} - }} +repository(owner: "{owner}" name: "{name}") {{ + labels(first: {max_page_size} {next} query: "{label_name}") {{ + pageInfo {{ + hasNextPage + endCursor + }} + nodes {{ + id + name + color + }} + }} +}} """ _SET_LABEL = """ - addLabelsToLabelable(input: {{ - labelableId: "{pr_id}", - labelIds: "{label_id}" - }}) {{ - clientMutationId - }} +addLabelsToLabelable(input: {{ + labelableId: "{pr_id}", + labelIds: "{label_id}" +}}) {{ + clientMutationId +}} """ labels = [] @@ -415,10 +417,10 @@ class Query: name=self._name, label_name=label_name, max_page_size=self._max_page_size, - next='after: "{}"'.format(result["pageInfo"]["endCursor"]), + next=f'after: "{result["pageInfo"]["endCursor"]}"', ) - labels += [label for label in result["nodes"]] + labels += list(result["nodes"]) if not labels: return @@ -458,27 +460,23 @@ class Query: # sleep a little, because we querying github too often time.sleep(0.1) - headers = {"Authorization": "bearer {}".format(self._token)} + headers = {"Authorization": f"bearer {self._token}"} if is_mutation: - query = """ - mutation {{ - {query} - }} - """.format( - query=query - ) + query = f""" +mutation {{ + {query} +}} + """ else: - query = """ - query {{ - {query} - rateLimit {{ - cost - remaining - }} - }} - """.format( - query=query - ) + query = f""" +query {{ + {query} + rateLimit {{ + cost + remaining + }} +}} + """ response = self.session.post( "https://api.github.com/graphql", json={"query": query}, headers=headers @@ -487,21 +485,15 @@ class Query: result = response.json() if "errors" in result: raise Exception( - "Errors occurred: {}\nOriginal query: {}".format( - result["errors"], query - ) + f"Errors occurred: {result['errors']}\nOriginal query: {query}" ) if not is_mutation: - if caller not in list(self.api_costs.keys()): + if caller not in self.api_costs: self.api_costs[caller] = 0 self.api_costs[caller] += result["data"]["rateLimit"]["cost"] return result["data"] else: - raise Exception( - "Query failed with code {code}:\n{json}".format( - code=response.status_code, - json=json.dumps(response.json(), indent=4), - ) - ) + data = json.dumps(response.json(), indent=4) + raise Exception(f"Query failed with code {response.status_code}:\n{data}") From d14a2d3583e606601cfbaadf3a26bdcfb2bac767 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:47:15 +0200 Subject: [PATCH 152/204] Fix docs about encryption functions --- .../functions/encryption-functions.md | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/en/sql-reference/functions/encryption-functions.md b/docs/en/sql-reference/functions/encryption-functions.md index fb821ca7783..06995cefaee 100644 --- a/docs/en/sql-reference/functions/encryption-functions.md +++ b/docs/en/sql-reference/functions/encryption-functions.md @@ -19,11 +19,10 @@ This function encrypts data using these modes: - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb - aes-128-gcm, aes-192-gcm, aes-256-gcm +- aes-128-ctr, aes-192-ctr, aes-256-ctr **Syntax** @@ -63,9 +62,9 @@ Insert some data (please avoid storing the keys/ivs in the database as this unde Query: ``` sql -INSERT INTO encryption_test VALUES('aes-256-cfb128 no IV', encrypt('aes-256-cfb128', 'Secret', '12345678910121314151617181920212')),\ -('aes-256-cfb128 no IV, different key', encrypt('aes-256-cfb128', 'Secret', 'keykeykeykeykeykeykeykeykeykeyke')),\ -('aes-256-cfb128 with IV', encrypt('aes-256-cfb128', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv')),\ +INSERT INTO encryption_test VALUES('aes-256-ofb no IV', encrypt('aes-256-ofb', 'Secret', '12345678910121314151617181920212')),\ +('aes-256-ofb no IV, different key', encrypt('aes-256-ofb', 'Secret', 'keykeykeykeykeykeykeykeykeykeyke')),\ +('aes-256-ofb with IV', encrypt('aes-256-ofb', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv')),\ ('aes-256-cbc no IV', encrypt('aes-256-cbc', 'Secret', '12345678910121314151617181920212')); ``` @@ -78,12 +77,12 @@ SELECT comment, hex(secret) FROM encryption_test; Result: ``` text -┌─comment─────────────────────────────┬─hex(secret)──────────────────────┐ -│ aes-256-cfb128 no IV │ B4972BDC4459 │ -│ aes-256-cfb128 no IV, different key │ 2FF57C092DC9 │ -│ aes-256-cfb128 with IV │ 5E6CB398F653 │ -│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ -└─────────────────────────────────────┴──────────────────────────────────┘ +┌─comment──────────────────────────┬─hex(secret)──────────────────────┐ +│ aes-256-ofb no IV │ B4972BDC4459 │ +│ aes-256-ofb no IV, different key │ 2FF57C092DC9 │ +│ aes-256-ofb with IV │ 5E6CB398F653 │ +│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ +└──────────────────────────────────┴──────────────────────────────────┘ ``` Example with `-gcm`: @@ -116,9 +115,7 @@ Supported encryption modes: - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb **Syntax** @@ -145,7 +142,7 @@ Given equal input `encrypt` and `aes_encrypt_mysql` produce the same ciphertext: Query: ``` sql -SELECT encrypt('aes-256-cfb128', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') = aes_encrypt_mysql('aes-256-cfb128', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') AS ciphertexts_equal; +SELECT encrypt('aes-256-ofb', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') = aes_encrypt_mysql('aes-256-ofb', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') AS ciphertexts_equal; ``` Result: @@ -161,14 +158,14 @@ But `encrypt` fails when `key` or `iv` is longer than expected: Query: ``` sql -SELECT encrypt('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'); +SELECT encrypt('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'); ``` Result: ``` text -Received exception from server (version 21.1.2): -Code: 36. DB::Exception: Received from localhost:9000. DB::Exception: Invalid key size: 33 expected 32: While processing encrypt('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'). +Received exception from server (version 22.6.1): +Code: 36. DB::Exception: Received from localhost:9000. DB::Exception: Invalid key size: 33 expected 32: While processing encrypt('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'). ``` While `aes_encrypt_mysql` produces MySQL-compatitalbe output: @@ -176,7 +173,7 @@ While `aes_encrypt_mysql` produces MySQL-compatitalbe output: Query: ``` sql -SELECT hex(aes_encrypt_mysql('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123')) AS ciphertext; +SELECT hex(aes_encrypt_mysql('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123')) AS ciphertext; ``` Result: @@ -192,7 +189,7 @@ Notice how supplying even longer `IV` produces the same result Query: ``` sql -SELECT hex(aes_encrypt_mysql('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456')) AS ciphertext +SELECT hex(aes_encrypt_mysql('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456')) AS ciphertext ``` Result: @@ -224,11 +221,10 @@ This function decrypts ciphertext into a plaintext using these modes: - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb - aes-128-gcm, aes-192-gcm, aes-256-gcm +- aes-128-ctr, aes-192-ctr, aes-256-ctr **Syntax** @@ -265,12 +261,12 @@ Result: │ aes-256-gcm │ A8A3CCBC6426CFEEB60E4EAE03D3E94204C1B09E0254 │ │ aes-256-gcm with AAD │ A8A3CCBC6426D9A1017A0A932322F1852260A4AD6837 │ └──────────────────────┴──────────────────────────────────────────────┘ -┌─comment─────────────────────────────┬─hex(secret)──────────────────────┐ -│ aes-256-cfb128 no IV │ B4972BDC4459 │ -│ aes-256-cfb128 no IV, different key │ 2FF57C092DC9 │ -│ aes-256-cfb128 with IV │ 5E6CB398F653 │ -│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ -└─────────────────────────────────────┴──────────────────────────────────┘ +┌─comment──────────────────────────┬─hex(secret)──────────────────────┐ +│ aes-256-ofb no IV │ B4972BDC4459 │ +│ aes-256-ofb no IV, different key │ 2FF57C092DC9 │ +│ aes-256-ofb with IV │ 5E6CB398F653 │ +│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ +└──────────────────────────────────┴──────────────────────────────────┘ ``` Now let's try to decrypt all that data. @@ -284,13 +280,19 @@ SELECT comment, decrypt('aes-256-cfb128', secret, '12345678910121314151617181920 Result: ``` text -┌─comment─────────────────────────────┬─plaintext─┐ -│ aes-256-cfb128 no IV │ Secret │ -│ aes-256-cfb128 no IV, different key │ �4� - � │ -│ aes-256-cfb128 with IV │ ���6�~ │ - │aes-256-cbc no IV │ �2*4�h3c�4w��@ -└─────────────────────────────────────┴───────────┘ +┌─comment──────────────┬─plaintext──┐ +│ aes-256-gcm │ OQ�E + �t�7T�\���\� │ +│ aes-256-gcm with AAD │ OQ�E + �\��si����;�o�� │ +└──────────────────────┴────────────┘ +┌─comment──────────────────────────┬─plaintext─┐ +│ aes-256-ofb no IV │ Secret │ +│ aes-256-ofb no IV, different key │ �4� + � │ +│ aes-256-ofb with IV │ ���6�~ │ + │aes-256-cbc no IV │ �2*4�h3c�4w��@ +└──────────────────────────────────┴───────────┘ ``` Notice how only a portion of the data was properly decrypted, and the rest is gibberish since either `mode`, `key`, or `iv` were different upon encryption. @@ -305,9 +307,7 @@ Supported decryption modes: - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb **Syntax** @@ -347,7 +347,7 @@ mysql> SELECT aes_encrypt('Secret', '123456789101213141516171819202122', 'iviviv Query: ``` sql -SELECT aes_decrypt_mysql('aes-256-cfb128', unhex('24E9E4966469'), '123456789101213141516171819202122', 'iviviviviviviviv123456') AS plaintext +SELECT aes_decrypt_mysql('aes-256-ofb', unhex('24E9E4966469'), '123456789101213141516171819202122', 'iviviviviviviviv123456') AS plaintext ``` Result: From d50c51f26215fbf85f0b7f5e03f574227195d997 Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 16 Jun 2022 11:25:03 +0000 Subject: [PATCH 153/204] Try to fix temporary name clash in partial merge join --- src/Interpreters/MergeJoin.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Interpreters/MergeJoin.cpp b/src/Interpreters/MergeJoin.cpp index d0bf4939c90..3dd6b7de574 100644 --- a/src/Interpreters/MergeJoin.cpp +++ b/src/Interpreters/MergeJoin.cpp @@ -31,9 +31,12 @@ namespace ErrorCodes namespace { -String deriveTempName(const String & name) +String deriveTempName(const String & name, JoinTableSide block_side) { - return "--" + name; + if (block_side == JoinTableSide::Left) + return "--pmj_cond_left_" + name; + else + return "--pmj_cond_right_" + name; } /* @@ -42,7 +45,7 @@ String deriveTempName(const String & name) * 0 converted to NULL and such rows won't be joined, * 1 converted to 0 (any constant non-NULL value to join) */ -ColumnWithTypeAndName condtitionColumnToJoinable(const Block & block, const String & src_column_name) +ColumnWithTypeAndName condtitionColumnToJoinable(const Block & block, const String & src_column_name, JoinTableSide block_side) { size_t res_size = block.rows(); auto data_col = ColumnUInt8::create(res_size, 0); @@ -60,10 +63,10 @@ ColumnWithTypeAndName condtitionColumnToJoinable(const Block & block, const Stri ColumnPtr res_col = ColumnNullable::create(std::move(data_col), std::move(null_map)); DataTypePtr res_col_type = std::make_shared(std::make_shared()); - String res_name = deriveTempName(src_column_name); + String res_name = deriveTempName(src_column_name, block_side); if (block.has(res_name)) - throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Conflicting column name '{}'", res_name); + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Conflicting column name '{}' in block {}", res_name, block.dumpStructure()); return {res_col, res_col_type, res_name}; } @@ -517,8 +520,8 @@ MergeJoin::MergeJoin(std::shared_ptr table_join_, const Block & right { JoinCommon::checkTypesOfMasks({}, "", right_sample_block, mask_column_name_right); - key_names_left.push_back(deriveTempName(mask_column_name_left)); - key_names_right.push_back(deriveTempName(mask_column_name_right)); + key_names_left.push_back(deriveTempName(mask_column_name_left, JoinTableSide::Left)); + key_names_right.push_back(deriveTempName(mask_column_name_right, JoinTableSide::Right)); } key_names_left.insert(key_names_left.end(), onexpr.key_names_left.begin(), onexpr.key_names_left.end()); @@ -735,7 +738,7 @@ void MergeJoin::joinBlock(Block & block, ExtraBlockPtr & not_processed) not_processed = std::make_shared(NotProcessed{{}, 0, 0, 0}); if (needConditionJoinColumn()) - block.erase(deriveTempName(mask_column_name_left)); + block.erase(deriveTempName(mask_column_name_left, JoinTableSide::Left)); JoinCommon::restoreLowCardinalityInplace(block, lowcard_keys); } @@ -1126,9 +1129,9 @@ void MergeJoin::addConditionJoinColumn(Block & block, JoinTableSide block_side) if (needConditionJoinColumn()) { if (block_side == JoinTableSide::Left) - block.insert(condtitionColumnToJoinable(block, mask_column_name_left)); + block.insert(condtitionColumnToJoinable(block, mask_column_name_left, block_side)); else - block.insert(condtitionColumnToJoinable(block, mask_column_name_right)); + block.insert(condtitionColumnToJoinable(block, mask_column_name_right, block_side)); } } From 023e7132fc8a836542b0acb6b17cf23ba2c2f0c9 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:50:35 +0200 Subject: [PATCH 154/204] Fix --- docs/en/sql-reference/functions/encryption-functions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/sql-reference/functions/encryption-functions.md b/docs/en/sql-reference/functions/encryption-functions.md index 06995cefaee..58a1d9d56f8 100644 --- a/docs/en/sql-reference/functions/encryption-functions.md +++ b/docs/en/sql-reference/functions/encryption-functions.md @@ -203,7 +203,7 @@ Result: Which is binary equal to what MySQL produces on same inputs: ``` sql -mysql> SET block_encryption_mode='aes-256-cfb128'; +mysql> SET block_encryption_mode='aes-256-ofb'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT aes_encrypt('Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456') as ciphertext; @@ -332,7 +332,7 @@ aes_decrypt_mysql('mode', 'ciphertext', 'key' [, iv]) Let's decrypt data we've previously encrypted with MySQL: ``` sql -mysql> SET block_encryption_mode='aes-256-cfb128'; +mysql> SET block_encryption_mode='aes-256-ofb'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT aes_encrypt('Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456') as ciphertext; From 489765aa6d97c5eaed56310c1c172f2d46febf17 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:51:49 +0200 Subject: [PATCH 155/204] Fix ru docs --- .../functions/encryption-functions.md | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/ru/sql-reference/functions/encryption-functions.md b/docs/ru/sql-reference/functions/encryption-functions.md index 2eaad0e1930..9a8e09348f9 100644 --- a/docs/ru/sql-reference/functions/encryption-functions.md +++ b/docs/ru/sql-reference/functions/encryption-functions.md @@ -19,11 +19,10 @@ sidebar_label: "Функции для шифрования" - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb - aes-128-gcm, aes-192-gcm, aes-256-gcm +- aes-128-ctr, aes-192-ctr, aes-256-ctr **Синтаксис** @@ -63,9 +62,9 @@ ENGINE = Memory; Запрос: ``` sql -INSERT INTO encryption_test VALUES('aes-256-cfb128 no IV', encrypt('aes-256-cfb128', 'Secret', '12345678910121314151617181920212')),\ -('aes-256-cfb128 no IV, different key', encrypt('aes-256-cfb128', 'Secret', 'keykeykeykeykeykeykeykeykeykeyke')),\ -('aes-256-cfb128 with IV', encrypt('aes-256-cfb128', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv')),\ +INSERT INTO encryption_test VALUES('aes-256-ofb no IV', encrypt('aes-256-ofb', 'Secret', '12345678910121314151617181920212')),\ +('aes-256-ofb no IV, different key', encrypt('aes-256-ofb', 'Secret', 'keykeykeykeykeykeykeykeykeykeyke')),\ +('aes-256-ofb with IV', encrypt('aes-256-ofb', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv')),\ ('aes-256-cbc no IV', encrypt('aes-256-cbc', 'Secret', '12345678910121314151617181920212')); ``` @@ -78,12 +77,12 @@ SELECT comment, hex(secret) FROM encryption_test; Результат: ``` text -┌─comment─────────────────────────────┬─hex(secret)──────────────────────┐ -│ aes-256-cfb128 no IV │ B4972BDC4459 │ -│ aes-256-cfb128 no IV, different key │ 2FF57C092DC9 │ -│ aes-256-cfb128 with IV │ 5E6CB398F653 │ -│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ -└─────────────────────────────────────┴──────────────────────────────────┘ +┌─comment──────────────────────────┬─hex(secret)──────────────────────┐ +│ aes-256-ofb no IV │ B4972BDC4459 │ +│ aes-256-ofb no IV, different key │ 2FF57C092DC9 │ +│ aes-256-ofb with IV │ 5E6CB398F653 │ +│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ +└──────────────────────────────────┴──────────────────────────────────┘ ``` Пример в режиме `-gcm`: @@ -116,9 +115,7 @@ SELECT comment, hex(secret) FROM encryption_test WHERE comment LIKE '%gcm%'; - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb **Синтаксис** @@ -145,7 +142,7 @@ aes_encrypt_mysql('mode', 'plaintext', 'key' [, iv]) Запрос: ``` sql -SELECT encrypt('aes-256-cfb128', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') = aes_encrypt_mysql('aes-256-cfb128', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') AS ciphertexts_equal; +SELECT encrypt('aes-256-ofb', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') = aes_encrypt_mysql('aes-256-ofb', 'Secret', '12345678910121314151617181920212', 'iviviviviviviviv') AS ciphertexts_equal; ``` Результат: @@ -161,14 +158,14 @@ SELECT encrypt('aes-256-cfb128', 'Secret', '12345678910121314151617181920212', ' Запрос: ``` sql -SELECT encrypt('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'); +SELECT encrypt('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'); ``` Результат: ``` text Received exception from server (version 21.1.2): -Code: 36. DB::Exception: Received from localhost:9000. DB::Exception: Invalid key size: 33 expected 32: While processing encrypt('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'). +Code: 36. DB::Exception: Received from localhost:9000. DB::Exception: Invalid key size: 33 expected 32: While processing encrypt('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123'). ``` Однако функция `aes_encrypt_mysql` в аналогичном случае возвращает результат, который может быть обработан MySQL: @@ -176,7 +173,7 @@ Code: 36. DB::Exception: Received from localhost:9000. DB::Exception: Invalid ke Запрос: ``` sql -SELECT hex(aes_encrypt_mysql('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123')) AS ciphertext; +SELECT hex(aes_encrypt_mysql('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123')) AS ciphertext; ``` Результат: @@ -192,7 +189,7 @@ SELECT hex(aes_encrypt_mysql('aes-256-cfb128', 'Secret', '1234567891012131415161 Запрос: ``` sql -SELECT hex(aes_encrypt_mysql('aes-256-cfb128', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456')) AS ciphertext +SELECT hex(aes_encrypt_mysql('aes-256-ofb', 'Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456')) AS ciphertext ``` Результат: @@ -206,7 +203,7 @@ SELECT hex(aes_encrypt_mysql('aes-256-cfb128', 'Secret', '1234567891012131415161 Это совпадает с результатом, возвращаемым MySQL при таких же входящих значениях: ``` sql -mysql> SET block_encryption_mode='aes-256-cfb128'; +mysql> SET block_encryption_mode='aes-256-ofb'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT aes_encrypt('Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456') as ciphertext; @@ -224,11 +221,10 @@ mysql> SELECT aes_encrypt('Secret', '123456789101213141516171819202122', 'iviviv - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb - aes-128-gcm, aes-192-gcm, aes-256-gcm +- aes-128-ctr, aes-192-ctr, aes-256-ctr **Синтаксис** @@ -265,12 +261,12 @@ SELECT comment, hex(secret) FROM encryption_test; │ aes-256-gcm │ A8A3CCBC6426CFEEB60E4EAE03D3E94204C1B09E0254 │ │ aes-256-gcm with AAD │ A8A3CCBC6426D9A1017A0A932322F1852260A4AD6837 │ └──────────────────────┴──────────────────────────────────────────────┘ -┌─comment─────────────────────────────┬─hex(secret)──────────────────────┐ -│ aes-256-cfb128 no IV │ B4972BDC4459 │ -│ aes-256-cfb128 no IV, different key │ 2FF57C092DC9 │ -│ aes-256-cfb128 with IV │ 5E6CB398F653 │ -│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ -└─────────────────────────────────────┴──────────────────────────────────┘ +┌─comment──────────────────────────┬─hex(secret)──────────────────────┐ +│ aes-256-ofb no IV │ B4972BDC4459 │ +│ aes-256-ofb no IV, different key │ 2FF57C092DC9 │ +│ aes-256-ofb with IV │ 5E6CB398F653 │ +│ aes-256-cbc no IV │ 1BC0629A92450D9E73A00E7D02CF4142 │ +└──────────────────────────────────┴──────────────────────────────────┘ ``` Теперь попытаемся расшифровать эти данные: @@ -278,19 +274,25 @@ SELECT comment, hex(secret) FROM encryption_test; Запрос: ``` sql -SELECT comment, decrypt('aes-256-cfb128', secret, '12345678910121314151617181920212') as plaintext FROM encryption_test; +SELECT comment, decrypt('aes-256-ofb', secret, '12345678910121314151617181920212') as plaintext FROM encryption_test; ``` Результат: ``` text -┌─comment─────────────────────────────┬─plaintext─┐ -│ aes-256-cfb128 no IV │ Secret │ -│ aes-256-cfb128 no IV, different key │ �4� - � │ -│ aes-256-cfb128 with IV │ ���6�~ │ - │aes-256-cbc no IV │ �2*4�h3c�4w��@ -└─────────────────────────────────────┴───────────┘ +┌─comment──────────────┬─plaintext──┐ +│ aes-256-gcm │ OQ�E + �t�7T�\���\� │ +│ aes-256-gcm with AAD │ OQ�E + �\��si����;�o�� │ +└──────────────────────┴────────────┘ +┌─comment──────────────────────────┬─plaintext─┐ +│ aes-256-ofb no IV │ Secret │ +│ aes-256-ofb no IV, different key │ �4� + � │ +│ aes-256-ofb with IV │ ���6�~ │ + │aes-256-cbc no IV │ �2*4�h3c�4w��@ +└──────────────────────────────────┴───────────┘ ``` Обратите внимание, что только часть данных была расшифрована верно. Оставшаяся часть расшифрована некорректно, так как при шифровании использовались другие значения `mode`, `key`, или `iv`. @@ -305,9 +307,7 @@ SELECT comment, decrypt('aes-256-cfb128', secret, '12345678910121314151617181920 - aes-128-ecb, aes-192-ecb, aes-256-ecb - aes-128-cbc, aes-192-cbc, aes-256-cbc -- aes-128-cfb1, aes-192-cfb1, aes-256-cfb1 -- aes-128-cfb8, aes-192-cfb8, aes-256-cfb8 -- aes-128-cfb128, aes-192-cfb128, aes-256-cfb128 +- aes-128-cfb128 - aes-128-ofb, aes-192-ofb, aes-256-ofb **Синтаксис** @@ -333,7 +333,7 @@ aes_decrypt_mysql('mode', 'ciphertext', 'key' [, iv]) ``` sql -mysql> SET block_encryption_mode='aes-256-cfb128'; +mysql> SET block_encryption_mode='aes-256-ofb'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT aes_encrypt('Secret', '123456789101213141516171819202122', 'iviviviviviviviv123456') as ciphertext; @@ -348,7 +348,7 @@ mysql> SELECT aes_encrypt('Secret', '123456789101213141516171819202122', 'iviviv Запрос: ``` sql -SELECT aes_decrypt_mysql('aes-256-cfb128', unhex('24E9E4966469'), '123456789101213141516171819202122', 'iviviviviviviviv123456') AS plaintext; +SELECT aes_decrypt_mysql('aes-256-ofb', unhex('24E9E4966469'), '123456789101213141516171819202122', 'iviviviviviviviv123456') AS plaintext; ``` Результат: From 867c3cde59bc5700e6b78511793a60e45de28baa Mon Sep 17 00:00:00 2001 From: Dan Roscigno Date: Thu, 16 Jun 2022 10:01:40 -0400 Subject: [PATCH 156/204] Update metrica.md Fix reference, change `What's Next` to `Next Steps` --- docs/en/getting-started/example-datasets/metrica.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/getting-started/example-datasets/metrica.md b/docs/en/getting-started/example-datasets/metrica.md index a3a14818179..da0286d8c05 100644 --- a/docs/en/getting-started/example-datasets/metrica.md +++ b/docs/en/getting-started/example-datasets/metrica.md @@ -87,7 +87,7 @@ clickhouse-client --query "SELECT COUNT(*) FROM datasets.visits_v1" The hits and visits dataset is used in the ClickHouse test routines, this is one of the queries from the test suite. The rest -of the tests are refernced in the *What's Next* section at the +of the tests are refernced in the *Next Steps* section at the end of this page. ```sql From 5f3de6f48650bbfb5792fee4fdb00a902e24be71 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 16:27:49 +0200 Subject: [PATCH 157/204] Use the best token instead of the constant --- tests/ci/cherry_pick.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ci/cherry_pick.py b/tests/ci/cherry_pick.py index d9f94ffa6c7..745284b2b29 100644 --- a/tests/ci/cherry_pick.py +++ b/tests/ci/cherry_pick.py @@ -6,7 +6,7 @@ import os import subprocess from env_helper import GITHUB_WORKSPACE, TEMP_PATH -from get_robot_token import get_parameter_from_ssm +from get_robot_token import get_best_robot_token from ssh import SSHKey from cherry_pick_utils.backport import Backport from cherry_pick_utils.cherrypick import CherryPick @@ -21,7 +21,7 @@ def parse_args(): def main(): args = parse_args() - token = args.token or get_parameter_from_ssm("github_robot_token_1") + token = args.token or get_best_robot_token() bp = Backport( token, From aff66742e784fb3416fb2ef24b7845ca4292992d Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Thu, 16 Jun 2022 16:29:04 +0200 Subject: [PATCH 158/204] Disable parameters for non direct executable user defined functions --- ...xternalUserDefinedExecutableFunctionsLoader.cpp | 3 +++ tests/config/test_function.xml | 14 -------------- ...table_user_defined_function_parameter.reference | 2 -- ..._executable_user_defined_function_parameter.sql | 6 ------ 4 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 tests/queries/0_stateless/02315_executable_user_defined_function_parameter.reference delete mode 100644 tests/queries/0_stateless/02315_executable_user_defined_function_parameter.sql diff --git a/src/Interpreters/ExternalUserDefinedExecutableFunctionsLoader.cpp b/src/Interpreters/ExternalUserDefinedExecutableFunctionsLoader.cpp index 33829aceb31..8c7220a85da 100644 --- a/src/Interpreters/ExternalUserDefinedExecutableFunctionsLoader.cpp +++ b/src/Interpreters/ExternalUserDefinedExecutableFunctionsLoader.cpp @@ -148,6 +148,9 @@ ExternalLoader::LoadablePtr ExternalUserDefinedExecutableFunctionsLoader::create String command_value = config.getString(key_in_config + ".command"); std::vector parameters = extractParametersFromCommand(command_value); + if (!execute_direct && !parameters.empty()) + throw Exception(ErrorCodes::UNSUPPORTED_METHOD, "Parameters are not supported if executable user defined function is not direct"); + std::vector command_arguments; if (execute_direct) diff --git a/tests/config/test_function.xml b/tests/config/test_function.xml index a50ab69422a..928cbd75c78 100644 --- a/tests/config/test_function.xml +++ b/tests/config/test_function.xml @@ -13,18 +13,4 @@ cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure 'x UInt64, y UInt64' --query "SELECT x + y FROM table" 0 - - executable - test_function_with_parameter - UInt64 - - UInt64 - - - UInt64 - - TabSeparated - cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure 'x UInt64, y UInt64' --query "SELECT x + y + {test_parameter : UInt64} FROM table" - 0 - diff --git a/tests/queries/0_stateless/02315_executable_user_defined_function_parameter.reference b/tests/queries/0_stateless/02315_executable_user_defined_function_parameter.reference deleted file mode 100644 index fd3c81a4d76..00000000000 --- a/tests/queries/0_stateless/02315_executable_user_defined_function_parameter.reference +++ /dev/null @@ -1,2 +0,0 @@ -5 -5 diff --git a/tests/queries/0_stateless/02315_executable_user_defined_function_parameter.sql b/tests/queries/0_stateless/02315_executable_user_defined_function_parameter.sql deleted file mode 100644 index f6e5678e612..00000000000 --- a/tests/queries/0_stateless/02315_executable_user_defined_function_parameter.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT test_function_with_parameter('test')(1, 2); --{serverError 53} -SELECT test_function_with_parameter(2, 2)(1, 2); --{serverError 36} -SELECT test_function_with_parameter(1, 2); --{serverError 36} - -SELECT test_function_with_parameter(2)(1, 2); -SELECT test_function_with_parameter('2')(1, 2); From 2544b0d53eb1f7d1c4acb8a6d37903adf149dccb Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 16:29:48 +0200 Subject: [PATCH 159/204] Add retries with progressive sleep and dynamic page sice decreasing --- tests/ci/cherry_pick_utils/backport.py | 2 +- tests/ci/cherry_pick_utils/query.py | 69 +++++++++++++++++++------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/tests/ci/cherry_pick_utils/backport.py b/tests/ci/cherry_pick_utils/backport.py index 9a510caf92a..1bc910886de 100644 --- a/tests/ci/cherry_pick_utils/backport.py +++ b/tests/ci/cherry_pick_utils/backport.py @@ -16,7 +16,7 @@ from local import Repository as LocalRepo class Backport: def __init__(self, token, owner, name, team): self._gh = RemoteRepo( - token, owner=owner, name=name, team=team, max_page_size=30, min_page_size=7 + token, owner=owner, name=name, team=team, max_page_size=60, min_page_size=7 ) self._token = token self.default_branch_name = self._gh.default_branch diff --git a/tests/ci/cherry_pick_utils/query.py b/tests/ci/cherry_pick_utils/query.py index ef1ad5ad0fb..917f9901287 100644 --- a/tests/ci/cherry_pick_utils/query.py +++ b/tests/ci/cherry_pick_utils/query.py @@ -457,9 +457,6 @@ addLabelsToLabelable(input: {{ mutation = ", is mutation" print(f"---GraphQL request for {caller}({parameters}){mutation}---") - # sleep a little, because we querying github too often - time.sleep(0.1) - headers = {"Authorization": f"bearer {self._token}"} if is_mutation: query = f""" @@ -478,22 +475,58 @@ query {{ }} """ - response = self.session.post( - "https://api.github.com/graphql", json={"query": query}, headers=headers - ) - if response.status_code == 200: - result = response.json() - if "errors" in result: - raise Exception( - f"Errors occurred: {result['errors']}\nOriginal query: {query}" + def request_with_retry(retry=0): + max_retries = 5 + # From time to time we face some concrete errors, when it worth to + # retry instead of failing competely + # We should sleep progressively + progressive_sleep = 5 * sum(i + 1 for i in range(retry)) + if progressive_sleep: + logging.warning( + "Retry GraphQL request %s time, sleep %s seconds", + retry, + progressive_sleep, ) + time.sleep(progressive_sleep) + response = self.session.post( + "https://api.github.com/graphql", json={"query": query}, headers=headers + ) + result = response.json() + if response.status_code == 200: + if "errors" in result: + raise Exception( + f"Errors occurred: {result['errors']}\nOriginal query: {query}" + ) - if not is_mutation: - if caller not in self.api_costs: - self.api_costs[caller] = 0 - self.api_costs[caller] += result["data"]["rateLimit"]["cost"] + if not is_mutation: + if caller not in self.api_costs: + self.api_costs[caller] = 0 + self.api_costs[caller] += result["data"]["rateLimit"]["cost"] - return result["data"] - else: - data = json.dumps(response.json(), indent=4) + return result["data"] + elif ( + response.status_code == 403 + and "secondary rate limit" in result["message"] + ): + if retry <= max_retries: + logging.warning("Secondary rate limit reached") + return request_with_retry(retry + 1) + elif response.status_code == 502 and "errors" in result: + too_many_data = any( + True + for err in result["errors"] + if "message" in err + and "This may be the result of a timeout" in err["message"] + ) + if too_many_data: + logging.warning( + "Too many data is requested, decreasing page size %s by 10%%", + self._max_page_size, + ) + self._max_page_size = int(self._max_page_size * 0.9) + return request_with_retry(retry) + + data = json.dumps(result, indent=4) raise Exception(f"Query failed with code {response.status_code}:\n{data}") + + return request_with_retry() From f8d5e908d79ee44827e860e645eb49967f752ef0 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Thu, 16 Jun 2022 16:39:34 +0200 Subject: [PATCH 160/204] Update SortingTransform.cpp --- src/Processors/Transforms/SortingTransform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processors/Transforms/SortingTransform.cpp b/src/Processors/Transforms/SortingTransform.cpp index 2360b4b6f6a..56805a46cab 100644 --- a/src/Processors/Transforms/SortingTransform.cpp +++ b/src/Processors/Transforms/SortingTransform.cpp @@ -84,7 +84,7 @@ Chunk MergeSorter::mergeImpl(TSortingHeap & queue) { /// The size of output block will not be larger than the `max_merged_block_size`. /// If redundant memory space is reserved, `MemoryTracker` will count more memory usage than actual usage. - size_t size_to_reserve = std::min(chunks[0].getNumRows(), max_merged_block_size); + size_t size_to_reserve = std::min(static_cast(chunks[0].getNumRows()), max_merged_block_size); for (auto & column : merged_columns) column->reserve(size_to_reserve); } From cd1bbc6a1b83c94b0fd4ddd954879b54672ad43a Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Thu, 16 Jun 2022 16:40:02 +0200 Subject: [PATCH 161/204] SortDescription compile fix typo --- src/Core/SortDescription.cpp | 8 ++++---- src/Core/SortDescription.h | 2 +- .../Merges/Algorithms/MergingSortedAlgorithm.cpp | 2 +- src/Processors/Transforms/SortingTransform.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Core/SortDescription.cpp b/src/Core/SortDescription.cpp index e7fa6816807..ff81f69383c 100644 --- a/src/Core/SortDescription.cpp +++ b/src/Core/SortDescription.cpp @@ -82,7 +82,7 @@ static Poco::Logger * getLogger() return &logger; } -void compileSortDescriptionIfNeeded(SortDescription & description, const DataTypes & sort_description_types, bool increase_compile_attemps) +void compileSortDescriptionIfNeeded(SortDescription & description, const DataTypes & sort_description_types, bool increase_compile_attempts) { static std::unordered_map counter; static std::mutex mutex; @@ -109,7 +109,7 @@ void compileSortDescriptionIfNeeded(SortDescription & description, const DataTyp UInt64 & current_counter = counter[sort_description_hash_key]; if (current_counter < description.min_count_to_compile_sort_description) { - current_counter += static_cast(increase_compile_attemps); + current_counter += static_cast(increase_compile_attempts); return; } } @@ -142,11 +142,11 @@ void compileSortDescriptionIfNeeded(SortDescription & description, const DataTyp #else -void compileSortDescriptionIfNeeded(SortDescription & description, const DataTypes & sort_description_types, bool increase_compile_attemps) +void compileSortDescriptionIfNeeded(SortDescription & description, const DataTypes & sort_description_types, bool increase_compile_attempts) { (void)(description); (void)(sort_description_types); - (void)(increase_compile_attemps); + (void)(increase_compile_attempts); } #endif diff --git a/src/Core/SortDescription.h b/src/Core/SortDescription.h index 3d4e3b665ee..730a64b77ad 100644 --- a/src/Core/SortDescription.h +++ b/src/Core/SortDescription.h @@ -107,7 +107,7 @@ public: /** Compile sort description for header_types. * Description is compiled only if compilation attempts to compile identical description is more than min_count_to_compile_sort_description. */ -void compileSortDescriptionIfNeeded(SortDescription & description, const DataTypes & sort_description_types, bool increase_compile_attemps); +void compileSortDescriptionIfNeeded(SortDescription & description, const DataTypes & sort_description_types, bool increase_compile_attempts); /// Outputs user-readable description into `out`. void dumpSortDescription(const SortDescription & description, WriteBuffer & out); diff --git a/src/Processors/Merges/Algorithms/MergingSortedAlgorithm.cpp b/src/Processors/Merges/Algorithms/MergingSortedAlgorithm.cpp index 3b626e379ca..627ac3f873c 100644 --- a/src/Processors/Merges/Algorithms/MergingSortedAlgorithm.cpp +++ b/src/Processors/Merges/Algorithms/MergingSortedAlgorithm.cpp @@ -40,7 +40,7 @@ MergingSortedAlgorithm::MergingSortedAlgorithm( queue_variants = SortQueueVariants(sort_description_types, description); if (queue_variants.variantSupportJITCompilation()) - compileSortDescriptionIfNeeded(description, sort_description_types, true /*increase_compile_attemps*/); + compileSortDescriptionIfNeeded(description, sort_description_types, true /*increase_compile_attempts*/); } void MergingSortedAlgorithm::addInput() diff --git a/src/Processors/Transforms/SortingTransform.cpp b/src/Processors/Transforms/SortingTransform.cpp index 779e4e6047d..97935bffd0c 100644 --- a/src/Processors/Transforms/SortingTransform.cpp +++ b/src/Processors/Transforms/SortingTransform.cpp @@ -175,7 +175,7 @@ SortingTransform::SortingTransform( description.swap(description_without_constants); if (SortQueueVariants(sort_description_types, description).variantSupportJITCompilation()) - compileSortDescriptionIfNeeded(description, sort_description_types, increase_sort_description_compile_attempts /*increase_compile_attemps*/); + compileSortDescriptionIfNeeded(description, sort_description_types, increase_sort_description_compile_attempts /*increase_compile_attempts*/); } SortingTransform::~SortingTransform() = default; From 797482f132325c508caf55f793feedef464a0a3c Mon Sep 17 00:00:00 2001 From: kssenii Date: Thu, 16 Jun 2022 16:40:29 +0200 Subject: [PATCH 162/204] Better --- src/Common/FileCacheSettings.cpp | 2 +- src/Common/FileCacheSettings.h | 2 +- src/Common/FileSegment.h | 2 + src/Common/IFileCache.cpp | 68 +++++++++++++-------- src/Common/IFileCache.h | 11 ++-- src/Common/LRUFileCache.cpp | 8 ++- src/Common/LRUFileCache.h | 6 +- src/IO/WriteBufferFromS3.cpp | 2 +- src/Interpreters/InterpreterSystemQuery.cpp | 4 +- 9 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/Common/FileCacheSettings.cpp b/src/Common/FileCacheSettings.cpp index 96ddfeebf95..88ca6e3ce6b 100644 --- a/src/Common/FileCacheSettings.cpp +++ b/src/Common/FileCacheSettings.cpp @@ -14,7 +14,7 @@ void FileCacheSettings::loadFromConfig(const Poco::Util::AbstractConfiguration & enable_filesystem_query_cache_limit = config.getUInt64(config_prefix + ".enable_filesystem_query_cache_limit", false); enable_cache_hits_threshold = config.getUInt64(config_prefix + ".enable_cache_hits_threshold", REMOTE_FS_OBJECTS_CACHE_ENABLE_HITS_THRESHOLD); do_not_evict_index_and_mark_files = config.getUInt64(config_prefix + ".do_not_evict_index_and_mark_files", true); - allow_remove_persistent_cache_by_default = config.getUInt64(config_prefix + ".allow_remove_persistent_cache_by_default", true); + allow_to_remove_persistent_segments_from_cache_by_default = config.getUInt64(config_prefix + ".allow_to_remove_persistent_segments_from_cache_by_default", true); } } diff --git a/src/Common/FileCacheSettings.h b/src/Common/FileCacheSettings.h index 6e0326fd1af..1d8b613bedd 100644 --- a/src/Common/FileCacheSettings.h +++ b/src/Common/FileCacheSettings.h @@ -19,7 +19,7 @@ struct FileCacheSettings bool enable_filesystem_query_cache_limit = false; bool do_not_evict_index_and_mark_files = true; - bool allow_remove_persistent_cache_by_default = true; + bool allow_to_remove_persistent_segments_from_cache_by_default = true; void loadFromConfig(const Poco::Util::AbstractConfiguration & config, const std::string & config_prefix); }; diff --git a/src/Common/FileSegment.h b/src/Common/FileSegment.h index 2ab77634665..750aa6a1cb2 100644 --- a/src/Common/FileSegment.h +++ b/src/Common/FileSegment.h @@ -246,6 +246,8 @@ private: std::atomic hits_count = 0; /// cache hits. std::atomic ref_count = 0; /// Used for getting snapshot state + /// Currently no-op. (will be added in PR 36171) + /// Defined if a file comply by the eviction policy. bool is_persistent; CurrentMetrics::Increment metric_increment{CurrentMetrics::CacheFileSegments}; }; diff --git a/src/Common/IFileCache.cpp b/src/Common/IFileCache.cpp index ab949d4a536..fb120ae5902 100644 --- a/src/Common/IFileCache.cpp +++ b/src/Common/IFileCache.cpp @@ -56,7 +56,9 @@ String IFileCache::getPathInLocalCache(const Key & key) const static bool isQueryInitialized() { - return CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() && CurrentThread::getQueryId().size != 0; + return CurrentThread::isInitialized() + && CurrentThread::get().getQueryContext() + && CurrentThread::getQueryId().size != 0; } bool IFileCache::isReadOnly() @@ -78,7 +80,7 @@ IFileCache::QueryContextPtr IFileCache::getCurrentQueryContext(std::lock_guard &) +IFileCache::QueryContextPtr IFileCache::getQueryContext(const String & query_id, std::lock_guard & /* cache_lock */) { auto query_iter = query_map.find(query_id); return (query_iter == query_map.end()) ? nullptr : query_iter->second; @@ -90,38 +92,42 @@ void IFileCache::removeQueryContext(const String & query_id) auto query_iter = query_map.find(query_id); if (query_iter == query_map.end()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Attempt to release query context that does not exist"); + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Attempt to release query context that does not exist (query_id: {})", + query_id); + } query_map.erase(query_iter); } -IFileCache::QueryContextPtr IFileCache::getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) +IFileCache::QueryContextPtr IFileCache::getOrSetQueryContext( + const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) { if (query_id.empty()) return nullptr; auto context = getQueryContext(query_id, cache_lock); - if (!context) - { - auto query_iter = query_map.insert({query_id, std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache)}).first; - context = query_iter->second; - } - return context; + if (context) + return context; + + auto query_context = std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache); + auto query_iter = query_map.emplace(query_id, query_context).first; + return query_iter->second; } IFileCache::QueryContextHolder IFileCache::getQueryContextHolder(const String & query_id, const ReadSettings & settings) { std::lock_guard cache_lock(mutex); + if (!enable_filesystem_query_cache_limit || settings.max_query_cache_size == 0) + return {}; + /// if enable_filesystem_query_cache_limit is true, and max_query_cache_size large than zero, /// we create context query for current query. - if (enable_filesystem_query_cache_limit && settings.max_query_cache_size) - { - auto context = getOrSetQueryContext(query_id, settings, cache_lock); - return QueryContextHolder(query_id, this, context); - } - else - return QueryContextHolder(); + auto context = getOrSetQueryContext(query_id, settings, cache_lock); + return QueryContextHolder(query_id, this, context); } void IFileCache::QueryContext::remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) @@ -144,7 +150,12 @@ void IFileCache::QueryContext::remove(const Key & key, size_t offset, size_t siz void IFileCache::QueryContext::reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) { if (cache_size + size > max_cache_size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "reserved cache size exceeds the remaining cache size"); + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Reserved cache size exceeds the remaining cache size (key: {}, offset: {})", + key.toString(), offset); + } if (!skip_download_if_exceeds_query_cache) { @@ -161,16 +172,21 @@ void IFileCache::QueryContext::reserve(const Key & key, size_t offset, size_t si void IFileCache::QueryContext::use(const Key & key, size_t offset, std::lock_guard & cache_lock) { - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - lru_queue.moveToEnd(record->second, cache_lock); - } + if (skip_download_if_exceeds_query_cache) + return; + + auto record = records.find({key, offset}); + if (record != records.end()) + lru_queue.moveToEnd(record->second, cache_lock); } -IFileCache::QueryContextHolder::QueryContextHolder(const String & query_id_, IFileCache * cache_, IFileCache::QueryContextPtr context_) - : query_id(query_id_), cache(cache_), context(context_) +IFileCache::QueryContextHolder::QueryContextHolder( + const String & query_id_, + IFileCache * cache_, + IFileCache::QueryContextPtr context_) + : query_id(query_id_) + , cache(cache_) + , context(context_) { } diff --git a/src/Common/IFileCache.h b/src/Common/IFileCache.h index 6a1806ccbff..c820d18cb95 100644 --- a/src/Common/IFileCache.h +++ b/src/Common/IFileCache.h @@ -128,7 +128,8 @@ protected: virtual void reduceSizeToDownloaded( const Key & key, size_t offset, - std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) = 0; + std::lock_guard & cache_lock, + std::lock_guard & /* segment_lock */) = 0; void assertInitialized() const; @@ -224,7 +225,6 @@ protected: bool enable_filesystem_query_cache_limit; -public: QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); @@ -233,19 +233,20 @@ public: QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); +public: /// Save a query context information, and adopt different cache policies /// for different queries through the context cache layer. struct QueryContextHolder : private boost::noncopyable { - explicit QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); + QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); QueryContextHolder() = default; ~QueryContextHolder(); - String query_id {}; + String query_id; IFileCache * cache = nullptr; - QueryContextPtr context = nullptr; + QueryContextPtr context; }; QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); diff --git a/src/Common/LRUFileCache.cpp b/src/Common/LRUFileCache.cpp index 4c015e49354..0ce76dbdec6 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -27,7 +27,7 @@ LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSetti , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("LRUFileCache")) - , allow_remove_persistent_cache_by_default(cache_settings_.allow_remove_persistent_cache_by_default) + , allow_to_remove_persistent_segments_from_cache_by_default(cache_settings_.allow_to_remove_persistent_segments_from_cache_by_default) { } @@ -111,7 +111,7 @@ FileSegments LRUFileCache::getImpl( files.erase(key); - /// Note: it is guaranteed that there is no concurrency with files delition, + /// Note: it is guaranteed that there is no concurrency with files deletion, /// because cache files are deleted only inside IFileCache and under cache lock. if (fs::exists(key_path)) fs::remove_all(key_path); @@ -746,6 +746,8 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) { /// Try remove all cached files by cache_base_path. /// Only releasable file segments are evicted. + /// `remove_persistent_files` defines whether non-evictable by some criteria files + /// (they do not comply with the cache eviction policy) should also be removed. std::lock_guard cache_lock(mutex); @@ -765,7 +767,7 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) if (file_segment && (!file_segment->isPersistent() || remove_persistent_files - || allow_remove_persistent_cache_by_default)) + || allow_to_remove_persistent_segments_from_cache_by_default)) { std::lock_guard segment_lock(file_segment->mutex); file_segment->detach(cache_lock, segment_lock); diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCache.h index 59de57f3ad3..059fc0c22c9 100644 --- a/src/Common/LRUFileCache.h +++ b/src/Common/LRUFileCache.h @@ -19,6 +19,10 @@ namespace DB { +/** + * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. + * Implements LRU eviction policy. + */ class LRUFileCache final : public IFileCache { public: @@ -79,7 +83,7 @@ private: size_t enable_cache_hits_threshold; Poco::Logger * log; - bool allow_remove_persistent_cache_by_default; + bool allow_to_remove_persistent_segments_from_cache_by_default; FileSegments getImpl( const Key & key, const FileSegment::Range & range, diff --git a/src/IO/WriteBufferFromS3.cpp b/src/IO/WriteBufferFromS3.cpp index a81b81197d3..d287484a524 100644 --- a/src/IO/WriteBufferFromS3.cpp +++ b/src/IO/WriteBufferFromS3.cpp @@ -95,7 +95,7 @@ void WriteBufferFromS3::nextImpl() { auto cache_key = cache->hash(key); - file_segments_holder.emplace(cache->setDownloading(cache_key, current_download_offset, size, false)); + file_segments_holder.emplace(cache->setDownloading(cache_key, current_download_offset, size, /* is_persistent */false)); current_download_offset += size; size_t remaining_size = size; diff --git a/src/Interpreters/InterpreterSystemQuery.cpp b/src/Interpreters/InterpreterSystemQuery.cpp index 1b3616e8a75..94b6eac8db0 100644 --- a/src/Interpreters/InterpreterSystemQuery.cpp +++ b/src/Interpreters/InterpreterSystemQuery.cpp @@ -313,12 +313,12 @@ BlockIO InterpreterSystemQuery::execute() { auto caches = FileCacheFactory::instance().getAll(); for (const auto & [_, cache_data] : caches) - cache_data.cache->removeIfReleasable(false); + cache_data.cache->removeIfReleasable(/* remove_persistent_files */false); } else { auto cache = FileCacheFactory::instance().get(query.filesystem_cache_path); - cache->removeIfReleasable(false); + cache->removeIfReleasable(/* remove_persistent_files */false); } break; } From 06dd85f921ff5cc4e0bd5046e672dbae25910d5c Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 17:15:22 +0200 Subject: [PATCH 163/204] Update version to 22.7.1.1 --- cmake/autogenerated_versions.txt | 10 +++---- .../StorageSystemContributors.generated.cpp | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/cmake/autogenerated_versions.txt b/cmake/autogenerated_versions.txt index 210c927b2fd..e6c60e74c36 100644 --- a/cmake/autogenerated_versions.txt +++ b/cmake/autogenerated_versions.txt @@ -2,11 +2,11 @@ # NOTE: has nothing common with DBMS_TCP_PROTOCOL_VERSION, # only DBMS_TCP_PROTOCOL_VERSION should be incremented on protocol changes. -SET(VERSION_REVISION 54463) +SET(VERSION_REVISION 54464) SET(VERSION_MAJOR 22) -SET(VERSION_MINOR 6) +SET(VERSION_MINOR 7) SET(VERSION_PATCH 1) -SET(VERSION_GITHASH df0cb0620985eb5ec59760cc76f7736e5b6209bb) -SET(VERSION_DESCRIBE v22.6.1.1-testing) -SET(VERSION_STRING 22.6.1.1) +SET(VERSION_GITHASH 7000c4e0033bb9e69050ab8ef73e8e7465f78059) +SET(VERSION_DESCRIBE v22.7.1.1-testing) +SET(VERSION_STRING 22.7.1.1) # end of autochange diff --git a/src/Storages/System/StorageSystemContributors.generated.cpp b/src/Storages/System/StorageSystemContributors.generated.cpp index 42a0f24cc65..5f5a7887e80 100644 --- a/src/Storages/System/StorageSystemContributors.generated.cpp +++ b/src/Storages/System/StorageSystemContributors.generated.cpp @@ -113,6 +113,7 @@ const char * auto_contributors[] { "Andrey Torsunov", "Andrey Urusov", "Andrey Z", + "Andrey Zvonov", "Andrii Buriachevskyi", "Andrii R", "Andy Liang", @@ -168,6 +169,7 @@ const char * auto_contributors[] { "Benjamin Naecker", "Bertrand Junqua", "Bharat Nallan", + "Bharat Nallan Chakravarthy", "Big Elephant", "Bill", "BiteTheDDDDt", @@ -190,6 +192,7 @@ const char * auto_contributors[] { "Caspian", "Chao Ma", "Chao Wang", + "CheSema", "Chen Yufei", "Chienlung Cheung", "Christian", @@ -209,8 +212,10 @@ const char * auto_contributors[] { "DIAOZHAFENG", "Dale McDiarmid", "Dan Roscigno", + "DanRoscigno", "Daniel Bershatsky", "Daniel Dao", + "Daniel Kutenin", "Daniel Qin", "Danila Kutenin", "Dao", @@ -295,6 +300,7 @@ const char * auto_contributors[] { "Frank Chen", "Frank Zhao", "François Violette", + "Fred Wulff", "Fruit of Eden", "Fu Zhe", "Fullstop000", @@ -323,6 +329,7 @@ const char * auto_contributors[] { "Habibullah Oladepo", "HaiBo Li", "Hamoon", + "Han Fei", "Harry-Lee", "HarryLeeIBM", "Hasitha Kanchana", @@ -354,6 +361,7 @@ const char * auto_contributors[] { "Ilya Mazaev", "Ilya Shipitsin", "Ilya Skrypitsa", + "Ilya Strukov", "Ilya Yatsishin", "IlyaTsoi", "ImgBotApp", @@ -371,11 +379,13 @@ const char * auto_contributors[] { "Ivan Remen", "Ivan Starkov", "Ivan Zhukov", + "Jachen Duschletta", "Jack Song", "JackyWoo", "Jacob Hayes", "Jake Liu", "Jakub Kuklis", + "James Maidment", "JaosnHsieh", "Jason", "Jason Keirstead", @@ -425,6 +435,7 @@ const char * auto_contributors[] { "Kozlov Ivan", "Kruglov Pavel", "Kseniia Sumarokova", + "Kuz Le", "Ky Li", "LAL2211", "LB", @@ -449,6 +460,7 @@ const char * auto_contributors[] { "Lv Feng", "Léo Ercolanelli", "M0r64n", + "MEX7", "MagiaGroz", "Maks Skorokhod", "Maksim", @@ -502,6 +514,7 @@ const char * auto_contributors[] { "Michael Kolupaev", "Michael Lex", "Michael Monashev", + "Michael Nutt", "Michael Razuvaev", "Michael Smitasin", "Michail Safronov", @@ -522,6 +535,7 @@ const char * auto_contributors[] { "Mikhail Filimonov", "Mikhail Fursov", "Mikhail Gaidamaka", + "Mikhail Guzov", "Mikhail Korotov", "Mikhail Malafeev", "Mikhail Nacharov", @@ -602,6 +616,7 @@ const char * auto_contributors[] { "Pablo Alegre", "Paramtamtam", "Patrick Zippenfenig", + "Paul Loyd", "Pavel", "Pavel Cheremushkin", "Pavel Kartaviy", @@ -610,6 +625,7 @@ const char * auto_contributors[] { "Pavel Kruglov", "Pavel Litvinenko", "Pavel Medvedev", + "Pavel Novitskiy", "Pavel Patrin", "Pavel Yakunin", "Pavlo Bashynskiy", @@ -620,6 +636,7 @@ const char * auto_contributors[] { "Pervakov Grigorii", "Pervakov Grigory", "Philippe Ombredanne", + "PigInCloud", "Potya", "Pradeep Chhetri", "Prashant Shahi", @@ -671,6 +688,7 @@ const char * auto_contributors[] { "Saulius Valatka", "Sean Haynes", "Sean Lafferty", + "Sema Checherinda", "Serg Kulakov", "Serge Rider", "Sergei Bocharov", @@ -1005,6 +1023,7 @@ const char * auto_contributors[] { "guoleiyi", "guomaolin", "guov100", + "guyco87", "guykohen", "gyuton", "hanqf-git", @@ -1049,6 +1068,7 @@ const char * auto_contributors[] { "jewisliu", "jiahui-97", "jianmei zhang", + "jinjunzh", "jkuklis", "jus1096", "jyz0309", @@ -1059,6 +1079,7 @@ const char * auto_contributors[] { "khamadiev", "kirillikoff", "kmeaw", + "koloshmet", "kolsys", "koshachy", "kreuzerkrieg", @@ -1088,6 +1109,7 @@ const char * auto_contributors[] { "lichengxiang", "linceyou", "lincion", + "lingo-xp", "listar", "litao91", "liu-bov", @@ -1183,6 +1205,7 @@ const char * auto_contributors[] { "qianlixiang", "qianmoQ", "qieqieplus", + "qinghuan wang", "quid", "quoctan132", "r1j1k", @@ -1266,17 +1289,20 @@ const char * auto_contributors[] { "vxider", "vzakaznikov", "wangchao", + "wangdh15", "weeds085490", "wuxiaobai24", "wzl", "xPoSx", "xiedeyantu", "xinhuitian", + "xlwh", "yakkomajuri", "yakov-olkhovskiy", "yandd", "yang", "yangshuai", + "yaqi-zhao", "yeer", "ygrek", "yhgcn", From dbe1eafff1dc89f43c2a9d0e55c3c7ab2de85be9 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 17:18:18 +0200 Subject: [PATCH 164/204] Update docker server version --- docker/server/Dockerfile.ubuntu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/server/Dockerfile.ubuntu b/docker/server/Dockerfile.ubuntu index 9e063a006b6..7c0108f8b2c 100644 --- a/docker/server/Dockerfile.ubuntu +++ b/docker/server/Dockerfile.ubuntu @@ -21,7 +21,7 @@ RUN sed -i "s|http://archive.ubuntu.com|${apt_archive}|g" /etc/apt/sources.list ARG REPO_CHANNEL="stable" ARG REPOSITORY="deb https://packages.clickhouse.com/deb ${REPO_CHANNEL} main" -ARG VERSION=22.5.1.* +ARG VERSION=22.6.1.* ARG PACKAGES="clickhouse-client clickhouse-server clickhouse-common-static" # set non-empty deb_location_url url to create a docker image From 7ac4b9b935a337801951cbe648ca441a6aaf0256 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 17:37:26 +0200 Subject: [PATCH 165/204] New changelog and versions updated --- docs/changelogs/v22.6.1.1985-stable.md | 192 +++++++++++++++++++++++++ utils/list-versions/version_date.tsv | 1 + 2 files changed, 193 insertions(+) create mode 100644 docs/changelogs/v22.6.1.1985-stable.md diff --git a/docs/changelogs/v22.6.1.1985-stable.md b/docs/changelogs/v22.6.1.1985-stable.md new file mode 100644 index 00000000000..583ffddf279 --- /dev/null +++ b/docs/changelogs/v22.6.1.1985-stable.md @@ -0,0 +1,192 @@ +### ClickHouse release v22.6.1.1985-stable FIXME as compared to v22.5.1.2079-stable + +#### Backward Incompatible Change +* Changes how settings using `seconds` as type are parsed to support floating point values (for example: `max_execution_time=0.5`). Infinity or NaN values will throw an exception. [#37187](https://github.com/ClickHouse/ClickHouse/pull/37187) ([Raúl Marín](https://github.com/Algunenano)). +* Changed format of binary serialization of columns of experimental type `Object`. New format is more convenient to implement by third-party clients. [#37482](https://github.com/ClickHouse/ClickHouse/pull/37482) ([Anton Popov](https://github.com/CurtizJ)). +* Turn on setting `output_format_json_named_tuples_as_objects` by default. It allows to serialize named tuples as JSON objects in JSON formats. [#37756](https://github.com/ClickHouse/ClickHouse/pull/37756) ([Anton Popov](https://github.com/CurtizJ)). + +#### New Feature +* Added `SYSTEM UNFREEZE` query that deletes the whole backup regardless if the corresponding table is deleted or not. [#36424](https://github.com/ClickHouse/ClickHouse/pull/36424) ([Vadim Volodin](https://github.com/PolyProgrammist)). +* Adds H3 unidirectional edge functions. [#36843](https://github.com/ClickHouse/ClickHouse/pull/36843) ([Bharat Nallan](https://github.com/bharatnc)). +* Add merge_reason column to system.part_log table. [#36912](https://github.com/ClickHouse/ClickHouse/pull/36912) ([Sema Checherinda](https://github.com/CheSema)). +* This PR enables `POPULATE` for WindowView. [#36945](https://github.com/ClickHouse/ClickHouse/pull/36945) ([vxider](https://github.com/Vxider)). +* Add new columnar JSON formats: JSONColumns, JSONCompactColumns, JSONColumnsWithMetadata. Closes [#36338](https://github.com/ClickHouse/ClickHouse/issues/36338) Closes [#34509](https://github.com/ClickHouse/ClickHouse/issues/34509). [#36975](https://github.com/ClickHouse/ClickHouse/pull/36975) ([Kruglov Pavel](https://github.com/Avogar)). +* Add support for calculating [hashids](https://hashids.org/) from unsigned integers. [#37013](https://github.com/ClickHouse/ClickHouse/pull/37013) ([Michael Nutt](https://github.com/mnutt)). +* Add GROUPING function. Closes [#19426](https://github.com/ClickHouse/ClickHouse/issues/19426). [#37163](https://github.com/ClickHouse/ClickHouse/pull/37163) ([Dmitry Novik](https://github.com/novikd)). +* `ALTER TABLE ... MODIFY QUERY` support for WindowView. [#37188](https://github.com/ClickHouse/ClickHouse/pull/37188) ([vxider](https://github.com/Vxider)). +* This PR changes the behavior of the `ENGINE` syntax in WindowView, to make it like in MaterializedView. [#37214](https://github.com/ClickHouse/ClickHouse/pull/37214) ([vxider](https://github.com/Vxider)). +* SALT is allowed for CREATE USER IDENTIFIED WITH sha256_hash. [#37377](https://github.com/ClickHouse/ClickHouse/pull/37377) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Implemented changing comment to a ReplicatedMergeTree table. [#37416](https://github.com/ClickHouse/ClickHouse/pull/37416) ([Vasily Nemkov](https://github.com/Enmk)). +* Add support for Maps and Records in Avro format. Add new setting `input_format_avro_null_as_default ` that allow to insert null as default in Avro format. Closes [#18925](https://github.com/ClickHouse/ClickHouse/issues/18925) Closes [#37378](https://github.com/ClickHouse/ClickHouse/issues/37378) Closes [#32899](https://github.com/ClickHouse/ClickHouse/issues/32899). [#37525](https://github.com/ClickHouse/ClickHouse/pull/37525) ([Kruglov Pavel](https://github.com/Avogar)). +* Add two new settings `input_format_csv_skip_first_lines/input_format_tsv_skip_first_lines` to allow skipping specified number of lines in the beginning of the file in CSV/TSV formats. [#37537](https://github.com/ClickHouse/ClickHouse/pull/37537) ([Kruglov Pavel](https://github.com/Avogar)). +* showCertificate() function shows current server's SSL certificate. [#37540](https://github.com/ClickHouse/ClickHouse/pull/37540) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Implementation of [FPC](https://userweb.cs.txstate.edu/~burtscher/papers/dcc07a.pdf) algorithm for floating point data compression. [#37553](https://github.com/ClickHouse/ClickHouse/pull/37553) ([Mikhail Guzov](https://github.com/koloshmet)). +* HTTP source for Data Dictionaries in Named Collections is supported. [#37581](https://github.com/ClickHouse/ClickHouse/pull/37581) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* This PR aims to resolve [#22130](https://github.com/ClickHouse/ClickHouse/issues/22130) which allows inserting into `system.zookeeper`. To simplify, this PR is designed as:. [#37596](https://github.com/ClickHouse/ClickHouse/pull/37596) ([Han Fei](https://github.com/hanfei1991)). +* Added a new window function `nonNegativeDerivative(metric_column, timestamp_column[, INTERVAL x SECOND])`. [#37628](https://github.com/ClickHouse/ClickHouse/pull/37628) ([Andrey Zvonov](https://github.com/zvonand)). +* Executable user defined functions support parameters. Example: `SELECT test_function(parameters)(arguments)`. Closes [#37578](https://github.com/ClickHouse/ClickHouse/issues/37578). [#37720](https://github.com/ClickHouse/ClickHouse/pull/37720) ([Maksim Kita](https://github.com/kitaisreal)). +* Added open telemetry traces visualizing tool based on d3js. [#37810](https://github.com/ClickHouse/ClickHouse/pull/37810) ([Sergei Trifonov](https://github.com/serxa)). + +#### Performance Improvement +* Improve performance of insert into MergeTree if there are multiple columns in ORDER BY. [#35762](https://github.com/ClickHouse/ClickHouse/pull/35762) ([Maksim Kita](https://github.com/kitaisreal)). +* Apply read method 'threadpool' for StorageHive. [#36328](https://github.com/ClickHouse/ClickHouse/pull/36328) ([李扬](https://github.com/taiyang-li)). +* Now we split data parts into layers and distribute them among threads instead of whole parts to make the execution of queries with `FINAL` more data-parallel. [#36396](https://github.com/ClickHouse/ClickHouse/pull/36396) ([Nikita Taranov](https://github.com/nickitat)). +* Load marks for only necessary columns when reading wide parts. [#36879](https://github.com/ClickHouse/ClickHouse/pull/36879) ([Anton Kozlov](https://github.com/tonickkozlov)). +* When all the columns to read are partition keys, construct columns by the file's row number without real reading the hive file. [#37103](https://github.com/ClickHouse/ClickHouse/pull/37103) ([lgbo](https://github.com/lgbo-ustc)). +* Fix performance of `dictGetDescendants`, `dictGetChildren` functions, create temporary parent to children hierarchical index per query, not per function call during query. Allow to specify `BIDIRECTIONAL` for `HIERARHICAL` attributes, dictionary will maintain parent to children index in memory, that way functions `dictGetDescendants`, `dictGetChildren` will not create temporary index per query. Closes [#32481](https://github.com/ClickHouse/ClickHouse/issues/32481). [#37148](https://github.com/ClickHouse/ClickHouse/pull/37148) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance and memory usage for select of subset of columns for formats Native, Protobuf, CapnProto, JSONEachRow, TSKV, all formats with suffixes WithNames/WithNamesAndTypes. Previously while selecting only subset of columns from files in these formats all columns were read and stored in memory. Now only required columns are read. This PR enables setting `input_format_skip_unknown_fields` by default, because otherwise in case of select of subset of columns exception will be thrown. [#37192](https://github.com/ClickHouse/ClickHouse/pull/37192) ([Kruglov Pavel](https://github.com/Avogar)). +* Improve sort performance by single column. [#37195](https://github.com/ClickHouse/ClickHouse/pull/37195) ([Maksim Kita](https://github.com/kitaisreal)). +* Support multi disks for caching hive files. [#37279](https://github.com/ClickHouse/ClickHouse/pull/37279) ([lgbo](https://github.com/lgbo-ustc)). +* Improved performance on array norm and distance functions 2x-4x times. [#37394](https://github.com/ClickHouse/ClickHouse/pull/37394) ([Alexander Gololobov](https://github.com/davenger)). +* Improve performance of number comparison functions using dynamic dispatch. [#37399](https://github.com/ClickHouse/ClickHouse/pull/37399) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of ORDER BY with LIMIT. [#37481](https://github.com/ClickHouse/ClickHouse/pull/37481) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of `hasAll` function using dynamic dispatch infrastructure. [#37484](https://github.com/ClickHouse/ClickHouse/pull/37484) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of `greatCircleAngle`, `greatCircleDistance`, `geoDistance` functions. [#37524](https://github.com/ClickHouse/ClickHouse/pull/37524) ([Maksim Kita](https://github.com/kitaisreal)). +* Optimized the internal caching of re2 patterns which occur e.g. in LIKE and MATCH functions. [#37544](https://github.com/ClickHouse/ClickHouse/pull/37544) ([Robert Schulze](https://github.com/rschu1ze)). +* Improve filter bitmask generator function all in one with avx512 instructions. [#37588](https://github.com/ClickHouse/ClickHouse/pull/37588) ([yaqi-zhao](https://github.com/yaqi-zhao)). +* Improved performance of aggregation in case, when sparse columns (can be enabled by experimental setting `ratio_of_defaults_for_sparse_serialization` in `MergeTree` tables) are used as arguments in aggregate functions. [#37617](https://github.com/ClickHouse/ClickHouse/pull/37617) ([Anton Popov](https://github.com/CurtizJ)). +* Optimize function `COALESCE` with two arguments. [#37666](https://github.com/ClickHouse/ClickHouse/pull/37666) ([Anton Popov](https://github.com/CurtizJ)). +* Replace `multiIf` to `if` in case when `multiIf` has only one condition, because function `if` is more performant. [#37695](https://github.com/ClickHouse/ClickHouse/pull/37695) ([Anton Popov](https://github.com/CurtizJ)). +* Aggregates state destruction now may be posted on a thread pool. For queries with LIMIT and big state it provides significant speedup, e.g. `select uniq(number) from numbers_mt(1e7) group by number limit 100` became around 2.5x faster. [#37855](https://github.com/ClickHouse/ClickHouse/pull/37855) ([Nikita Taranov](https://github.com/nickitat)). +* Improve performance of single column sorting using sorting queue specializations. [#37990](https://github.com/ClickHouse/ClickHouse/pull/37990) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix excessive CPU usage in background when there are a lot of tables. [#38028](https://github.com/ClickHouse/ClickHouse/pull/38028) ([Maksim Kita](https://github.com/kitaisreal)). +* Improve performance of `not` function using dynamic dispatch. [#38058](https://github.com/ClickHouse/ClickHouse/pull/38058) ([Maksim Kita](https://github.com/kitaisreal)). +* Added numerous NEON accelerated paths for main libraries. [#38093](https://github.com/ClickHouse/ClickHouse/pull/38093) ([Daniel Kutenin](https://github.com/danlark1)). + +#### Improvement +* Add separate CLUSTER grant (and `access_control_improvements.on_cluster_queries_require_cluster_grant` configuration directive, for backward compatibility, default to `false`). [#35767](https://github.com/ClickHouse/ClickHouse/pull/35767) ([Azat Khuzhin](https://github.com/azat)). +* Add self extracting executable [#34755](https://github.com/ClickHouse/ClickHouse/issues/34755). [#35775](https://github.com/ClickHouse/ClickHouse/pull/35775) ([Filatenkov Artur](https://github.com/FArthur-cmd)). +* Added support for schema inference for `hdfsCluster`. [#35812](https://github.com/ClickHouse/ClickHouse/pull/35812) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)). +* Add feature disks( ls - list files on disk, C - set config file, list-disks - list disks names, disk - set disk for work by name, help - produce help message copy - copy data on disk containing at `from_path` to `to_path` link - Create hardlink on disk from `from_path` to `to_path` list - List files on disk move - Move file or directory on disk from `from_path` to `to_path` read - read File on disk `from_path` to `to_path` or to stdout remove - Remove file or directory on disk with all children. write - Write File on disk`from_path` or stdin to `to_path`). [#36060](https://github.com/ClickHouse/ClickHouse/pull/36060) ([Artyom Yurkov](https://github.com/Varinara)). +* Implement `least_used` load balancing algorithm for disks inside volume (multi disk configuration). [#36686](https://github.com/ClickHouse/ClickHouse/pull/36686) ([Azat Khuzhin](https://github.com/azat)). +* - Modify the HTTP Endpoint to return the full stats under the `X-ClickHouse-Summary` header when `send_progress_in_http_headers=0` (before it would return all zeros). - Modify the HTTP Endpoint to return `X-ClickHouse-Exception-Code` header when progress has been sent before (`send_progress_in_http_headers=1`) - Modify the HTTP Endpoint to return `HTTP_REQUEST_TIMEOUT` (408) instead of `HTTP_INTERNAL_SERVER_ERROR` (500) on `TIMEOUT_EXCEEDED` errors. [#36884](https://github.com/ClickHouse/ClickHouse/pull/36884) ([Raúl Marín](https://github.com/Algunenano)). +* Allow a user to inspect grants from granted roles. [#36941](https://github.com/ClickHouse/ClickHouse/pull/36941) ([nvartolomei](https://github.com/nvartolomei)). +* Do not calculate an integral numerically but use CDF functions instead. This will speed up execution and will increase the precision. This fixes [#36714](https://github.com/ClickHouse/ClickHouse/issues/36714). [#36953](https://github.com/ClickHouse/ClickHouse/pull/36953) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)). +* Add default implementation for Nothing in functions. Now most of the functions will return column with type Nothing in case one of it's arguments is Nothing. It also solves problem with functions like arrayMap/arrayFilter and similar when they have empty array as an argument. Previously queries like `select arrayMap(x -> 2 * x, []);` failed because function inside lambda cannot work with type `Nothing`, now such queries return empty array with type `Array(Nothing)`. Also add support for arrays of nullable types in functions like arrayFilter/arrayFill. Previously, queries like `select arrayFilter(x -> x % 2, [1, NULL])` failed, now they work (if the result of lambda is NULL, then this value won't be included in the result). Closes [#37000](https://github.com/ClickHouse/ClickHouse/issues/37000). [#37048](https://github.com/ClickHouse/ClickHouse/pull/37048) ([Kruglov Pavel](https://github.com/Avogar)). +* Now if a shard has local replica we create a local plan and a plan to read from all remote replicas. They have shared initiator which coordinates reading. [#37204](https://github.com/ClickHouse/ClickHouse/pull/37204) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)). +* In function: CompressedWriteBuffer::nextImpl(), there is an unnecessary write-copy step that would happen frequently during inserting data. Below shows the differentiation with this patch: - Before: 1. Compress "working_buffer" into "compressed_buffer" 2. write-copy into "out" - After: Directly Compress "working_buffer" into "out". [#37242](https://github.com/ClickHouse/ClickHouse/pull/37242) ([jasperzhu](https://github.com/jinjunzh)). +* Support non-constant SQL functions (NOT) (I)LIKE and MATCH. [#37251](https://github.com/ClickHouse/ClickHouse/pull/37251) ([Robert Schulze](https://github.com/rschu1ze)). +* Client will try every IP address returned by DNS resolution until successful connection. [#37273](https://github.com/ClickHouse/ClickHouse/pull/37273) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* - Do no longer abort server startup if configuration option "mark_cache_size" is not explicitly set. [#37326](https://github.com/ClickHouse/ClickHouse/pull/37326) ([Robert Schulze](https://github.com/rschu1ze)). +* Allow to use String type instead of Binary in Arrow/Parquet/ORC formats. This PR introduces 3 new settings for it: `output_format_arrow_string_as_string`, `output_format_parquet_string_as_string`, `output_format_orc_string_as_string`. Default value for all settings is `false`. [#37327](https://github.com/ClickHouse/ClickHouse/pull/37327) ([Kruglov Pavel](https://github.com/Avogar)). +* Apply setting `input_format_max_rows_to_read_for_schema_inference` for all read rows in total from all files in globs. Previously setting `input_format_max_rows_to_read_for_schema_inference` was applied for each file in glob separately and in case of huge number of nulls we could read first `input_format_max_rows_to_read_for_schema_inference` rows from each file and get nothing. Also increase default value for this setting to 25000. [#37332](https://github.com/ClickHouse/ClickHouse/pull/37332) ([Kruglov Pavel](https://github.com/Avogar)). +* allows providing `NULL`/`NOT NULL` right after type in column declaration. [#37337](https://github.com/ClickHouse/ClickHouse/pull/37337) ([Igor Nikonov](https://github.com/devcrafter)). +* optimize file segment PARTIALLY_DOWNLOADED get read buffer. [#37338](https://github.com/ClickHouse/ClickHouse/pull/37338) ([xiedeyantu](https://github.com/xiedeyantu)). +* Allow to prune the list of files via virtual columns such as `_file` and `_path` when reading from S3. This is for [#37174](https://github.com/ClickHouse/ClickHouse/issues/37174) , [#23494](https://github.com/ClickHouse/ClickHouse/issues/23494). [#37356](https://github.com/ClickHouse/ClickHouse/pull/37356) ([Amos Bird](https://github.com/amosbird)). +* Try to improve short circuit functions processing to fix problems with stress tests. [#37384](https://github.com/ClickHouse/ClickHouse/pull/37384) ([Kruglov Pavel](https://github.com/Avogar)). +* Closes [#37395](https://github.com/ClickHouse/ClickHouse/issues/37395). [#37415](https://github.com/ClickHouse/ClickHouse/pull/37415) ([Memo](https://github.com/Joeywzr)). +* Fix extremely rare deadlock during part fetch in zero-copy replication. Fixes [#37423](https://github.com/ClickHouse/ClickHouse/issues/37423). [#37424](https://github.com/ClickHouse/ClickHouse/pull/37424) ([metahys](https://github.com/metahys)). +* Don't allow to create storage with unknown data format. [#37450](https://github.com/ClickHouse/ClickHouse/pull/37450) ([Kruglov Pavel](https://github.com/Avogar)). +* Set `global_memory_usage_overcommit_max_wait_microseconds` default value to 5 seconds. Add info about `OvercommitTracker` to OOM exception message. Add `MemoryOvercommitWaitTimeMicroseconds` profile event. [#37460](https://github.com/ClickHouse/ClickHouse/pull/37460) ([Dmitry Novik](https://github.com/novikd)). +* Play UI: Keep controls in place when the page is scrolled horizontally. This makes edits comfortable even if the table is wide and it was scrolled far to the right. The feature proposed by Maksym Tereshchenko from CaspianDB. [#37470](https://github.com/ClickHouse/ClickHouse/pull/37470) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Now more filters can be pushed down for join. [#37472](https://github.com/ClickHouse/ClickHouse/pull/37472) ([Amos Bird](https://github.com/amosbird)). +* Modify query div in play.html to be extendable beyond 20% height. In case of very long queries it is helpful to extend the textarea element, only today, since the div is fixed height, the extended textarea hides the data div underneath. With this fix, extending the textarea element will push the data div down/up such the extended textarea won't hide it. Also, keeps query box width 100% even when the user adjusting the size of the query textarea. [#37488](https://github.com/ClickHouse/ClickHouse/pull/37488) ([guyco87](https://github.com/guyco87)). +* Modify query div in play.html to be extendable beyond 20% height. In case of very long queries it is helpful to extend the textarea element, only today, since the div is fixed height, the extended textarea hides the data div underneath. With this fix, extending the textarea element will push the data div down/up such the extended textarea won't hide it. Also, keeps query box width 100% even when the user adjusting the size of the query textarea. [#37504](https://github.com/ClickHouse/ClickHouse/pull/37504) ([guyco87](https://github.com/guyco87)). +* Currently clickhouse directly downloads all remote files to the local cache (even if they are only read once), which will frequently cause IO of the local hard disk. In some scenarios, these IOs may not be necessary and may easily cause negative optimization. As shown in the figure below, when we run SSB Q1-Q4, the performance of the cache has caused negative optimization. [#37516](https://github.com/ClickHouse/ClickHouse/pull/37516) ([Han Shukai](https://github.com/KinderRiven)). +* Added `ProfileEvents` for introspection of type of written (inserted or merged) parts (`Inserted{Wide/Compact/InMemory}Parts`, `MergedInto{Wide/Compact/InMemory}Parts`. Added column `part_type` to `system.part_log`. Resolves [#37495](https://github.com/ClickHouse/ClickHouse/issues/37495). [#37536](https://github.com/ClickHouse/ClickHouse/pull/37536) ([Anton Popov](https://github.com/CurtizJ)). +* clickhouse-keeper improvement: move broken logs to a timestamped folder. [#37565](https://github.com/ClickHouse/ClickHouse/pull/37565) ([Antonio Andelic](https://github.com/antonio2368)). +* Do not write expired columns by TTL after subsequent merges (before only first merge/optimize of the part will not write expired by TTL columns, all other will do). [#37570](https://github.com/ClickHouse/ClickHouse/pull/37570) ([Azat Khuzhin](https://github.com/azat)). +* More precise result of the `dumpColumnStructure` miscellaneous function in presence of LowCardinality or Sparse columns. In previous versions, these functions were converting the argument to a full column before returning the result. This is needed to provide an answer in [#6935](https://github.com/ClickHouse/ClickHouse/issues/6935). [#37633](https://github.com/ClickHouse/ClickHouse/pull/37633) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* keeper: store only unique session IDs for watches. [#37641](https://github.com/ClickHouse/ClickHouse/pull/37641) ([Azat Khuzhin](https://github.com/azat)). +* Fix possible "Cannot write to finalized buffer". [#37645](https://github.com/ClickHouse/ClickHouse/pull/37645) ([Azat Khuzhin](https://github.com/azat)). +* Add setting `support_batch_delete` for `DiskS3` to disable multiobject delete calls, which Google Cloud Storage doesn't support. [#37659](https://github.com/ClickHouse/ClickHouse/pull/37659) ([Fred Wulff](https://github.com/frew)). +* Support types with non-standard defaults in ROLLUP, CUBE, GROUPING SETS. Closes [#37360](https://github.com/ClickHouse/ClickHouse/issues/37360). [#37667](https://github.com/ClickHouse/ClickHouse/pull/37667) ([Dmitry Novik](https://github.com/novikd)). +* Add an option to disable connection pooling in ODBC bridge. [#37705](https://github.com/ClickHouse/ClickHouse/pull/37705) ([Anton Kozlov](https://github.com/tonickkozlov)). +* ... LIKE patterns with trailing escape symbol ('\\') are now disallowed (as mandated by the SQL standard). [#37764](https://github.com/ClickHouse/ClickHouse/pull/37764) ([Robert Schulze](https://github.com/rschu1ze)). +* Fix stacktraces collection on ARM. Closes [#37044](https://github.com/ClickHouse/ClickHouse/issues/37044). Closes [#15638](https://github.com/ClickHouse/ClickHouse/issues/15638). [#37797](https://github.com/ClickHouse/ClickHouse/pull/37797) ([Maksim Kita](https://github.com/kitaisreal)). +* Functions `dictGetHierarchy`, `dictIsIn`, `dictGetChildren`, `dictGetDescendants` added support nullable `HIERARCHICAL` attribute in dictionaries. Closes [#35521](https://github.com/ClickHouse/ClickHouse/issues/35521). [#37805](https://github.com/ClickHouse/ClickHouse/pull/37805) ([Maksim Kita](https://github.com/kitaisreal)). +* Expose BoringSSL version related info in the `system.build_options` table. [#37850](https://github.com/ClickHouse/ClickHouse/pull/37850) ([Bharat Nallan](https://github.com/bharatnc)). +* **Description** Limiting the maximum cache usage per query can effectively prevent cache pool contamination. [Related Issues](https://github.com/ClickHouse/ClickHouse/issues/28961). [#37859](https://github.com/ClickHouse/ClickHouse/pull/37859) ([Han Shukai](https://github.com/KinderRiven)). +* Now clickhouse-server removes `delete_tmp` directories on server start. Fixes [#26503](https://github.com/ClickHouse/ClickHouse/issues/26503). [#37906](https://github.com/ClickHouse/ClickHouse/pull/37906) ([alesapin](https://github.com/alesapin)). +* Clean up broken detached parts after timeout. Closes [#25195](https://github.com/ClickHouse/ClickHouse/issues/25195). [#37975](https://github.com/ClickHouse/ClickHouse/pull/37975) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Now in MergeTree table engines family failed-to-move parts will be removed instantly. [#37994](https://github.com/ClickHouse/ClickHouse/pull/37994) ([alesapin](https://github.com/alesapin)). +* Now if setting `always_fetch_merged_part` is enabled for ReplicatedMergeTree merges will try to find parts on other replicas rarely with smaller load for [Zoo]Keeper. [#37995](https://github.com/ClickHouse/ClickHouse/pull/37995) ([alesapin](https://github.com/alesapin)). +* Add implicit grants with grant option too. For example `GRANT CREATE TABLE ON test.* TO A WITH GRANT OPTION` now allows `A` to execute `GRANT CREATE VIEW ON test.* TO B`. [#38017](https://github.com/ClickHouse/ClickHouse/pull/38017) ([Vitaly Baranov](https://github.com/vitlibar)). +* Do not display `-0.0` CPU time in clickhouse-client. It can appear due to rounding errors. This closes [#38003](https://github.com/ClickHouse/ClickHouse/issues/38003). This closes [#38038](https://github.com/ClickHouse/ClickHouse/issues/38038). [#38064](https://github.com/ClickHouse/ClickHouse/pull/38064) ([Alexey Milovidov](https://github.com/alexey-milovidov)). + +#### Build/Testing/Packaging Improvement +* Use clang-14 and LLVM infrastructure version 14 for builds. This closes [#34681](https://github.com/ClickHouse/ClickHouse/issues/34681). [#34754](https://github.com/ClickHouse/ClickHouse/pull/34754) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Allow to drop privileges at startup. This simplifies Docker images. Closes [#36293](https://github.com/ClickHouse/ClickHouse/issues/36293). [#36341](https://github.com/ClickHouse/ClickHouse/pull/36341) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Remove recursive submodules, because we don't need them and they can be confusing. Add style check to prevent recursive submodules. This closes [#32821](https://github.com/ClickHouse/ClickHouse/issues/32821). [#37616](https://github.com/ClickHouse/ClickHouse/pull/37616) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* Add docs spellcheck to CI. [#37790](https://github.com/ClickHouse/ClickHouse/pull/37790) ([Vladimir C](https://github.com/vdimir)). +* Fix overly aggressive stripping which removed the embedded hash required for checking the consistency of the executable. [#37993](https://github.com/ClickHouse/ClickHouse/pull/37993) ([Robert Schulze](https://github.com/rschu1ze)). +* fix MacOS build compressor faild. [#38007](https://github.com/ClickHouse/ClickHouse/pull/38007) ([xiedeyantu](https://github.com/xiedeyantu)). + +#### Bug Fix (user-visible misbehavior in official stable or prestable release) + +* Fix `GROUP BY` `AggregateFunction` (i.e. you `GROUP BY` by the column that has `AggregateFunction` type). [#37093](https://github.com/ClickHouse/ClickHouse/pull/37093) ([Azat Khuzhin](https://github.com/azat)). +* Fix possible heap-use-after-free error when reading system.projection_parts and system.projection_parts_columns . This fixes [#37184](https://github.com/ClickHouse/ClickHouse/issues/37184). [#37185](https://github.com/ClickHouse/ClickHouse/pull/37185) ([Amos Bird](https://github.com/amosbird)). +* Fix `addDependency` in WindowView. This bug can be reproduced like [#37237](https://github.com/ClickHouse/ClickHouse/issues/37237). [#37224](https://github.com/ClickHouse/ClickHouse/pull/37224) ([vxider](https://github.com/Vxider)). +* This PR moving `addDependency` from constructor to `startup()` to avoid adding dependency to a **dropped** table, fix [#37237](https://github.com/ClickHouse/ClickHouse/issues/37237). [#37243](https://github.com/ClickHouse/ClickHouse/pull/37243) ([vxider](https://github.com/Vxider)). +* Fix inserting defaults for missing values in columnar formats. Previously missing columns were filled with defaults for types, not for columns. [#37253](https://github.com/ClickHouse/ClickHouse/pull/37253) ([Kruglov Pavel](https://github.com/Avogar)). +* Fix some cases of insertion nested arrays to columns of type `Object`. [#37305](https://github.com/ClickHouse/ClickHouse/pull/37305) ([Anton Popov](https://github.com/CurtizJ)). +* Fix unexpected errors with a clash of constant strings in aggregate function, prewhere and join. Close [#36891](https://github.com/ClickHouse/ClickHouse/issues/36891). [#37336](https://github.com/ClickHouse/ClickHouse/pull/37336) ([Vladimir C](https://github.com/vdimir)). +* Fix projections with GROUP/ORDER BY in query and optimize_aggregation_in_order (before the result was incorrect since only finish sorting was performed). [#37342](https://github.com/ClickHouse/ClickHouse/pull/37342) ([Azat Khuzhin](https://github.com/azat)). +* Fixed error with symbols in key name in S3. Fixes [#33009](https://github.com/ClickHouse/ClickHouse/issues/33009). [#37344](https://github.com/ClickHouse/ClickHouse/pull/37344) ([Vladimir Chebotarev](https://github.com/excitoon)). +* Throw an exception when GROUPING SETS used with ROLLUP or CUBE. [#37367](https://github.com/ClickHouse/ClickHouse/pull/37367) ([Dmitry Novik](https://github.com/novikd)). +* Fix LOGICAL_ERROR in getMaxSourcePartsSizeForMerge during merges (in case of non standard, greater, values of `background_pool_size`/`background_merges_mutations_concurrency_ratio` has been specified in `config.xml` (new way) not in `users.xml` (deprecated way)). [#37413](https://github.com/ClickHouse/ClickHouse/pull/37413) ([Azat Khuzhin](https://github.com/azat)). +* ``` Stop removing UTF-8 BOM in RowBinary format. [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). ```. [#37428](https://github.com/ClickHouse/ClickHouse/pull/37428) ([Paul Loyd](https://github.com/loyd)). +* clickhouse-keeper bugfix: fix force recovery for single node cluster. [#37440](https://github.com/ClickHouse/ClickHouse/pull/37440) ([Antonio Andelic](https://github.com/antonio2368)). +* Fix logical error in normalizeUTF8 functions. Closes [#37298](https://github.com/ClickHouse/ClickHouse/issues/37298). [#37443](https://github.com/ClickHouse/ClickHouse/pull/37443) ([Maksim Kita](https://github.com/kitaisreal)). +* * Fix cast lowcard of nullable in JoinSwitcher, close [#37385](https://github.com/ClickHouse/ClickHouse/issues/37385). [#37453](https://github.com/ClickHouse/ClickHouse/pull/37453) ([Vladimir C](https://github.com/vdimir)). +* Fix named tuples output in ORC/Arrow/Parquet formats. [#37458](https://github.com/ClickHouse/ClickHouse/pull/37458) ([Kruglov Pavel](https://github.com/Avogar)). +* Fix optimization of monotonous functions in ORDER BY clause in presence of GROUPING SETS. Fixes [#37401](https://github.com/ClickHouse/ClickHouse/issues/37401). [#37493](https://github.com/ClickHouse/ClickHouse/pull/37493) ([Dmitry Novik](https://github.com/novikd)). +* Fix error on joining with dictionary on some conditions. Close [#37386](https://github.com/ClickHouse/ClickHouse/issues/37386). [#37530](https://github.com/ClickHouse/ClickHouse/pull/37530) ([Vladimir C](https://github.com/vdimir)). +* Prohibit `optimize_aggregation_in_order` with `GROUPING SETS` (fixes `LOGICAL_ERROR`). [#37542](https://github.com/ClickHouse/ClickHouse/pull/37542) ([Azat Khuzhin](https://github.com/azat)). +* Fix wrong dump information of ActionsDAG. [#37587](https://github.com/ClickHouse/ClickHouse/pull/37587) ([zhanglistar](https://github.com/zhanglistar)). +* Fix converting types for UNION queries (may produce LOGICAL_ERROR). [#37593](https://github.com/ClickHouse/ClickHouse/pull/37593) ([Azat Khuzhin](https://github.com/azat)). +* Fix `WITH FILL` modifier with negative intervals in `STEP` clause. Fixes [#37514](https://github.com/ClickHouse/ClickHouse/issues/37514). [#37600](https://github.com/ClickHouse/ClickHouse/pull/37600) ([Anton Popov](https://github.com/CurtizJ)). +* Fix illegal joinGet array usage when ` join_use_nulls = 1`. This fixes [#37562](https://github.com/ClickHouse/ClickHouse/issues/37562) . [#37650](https://github.com/ClickHouse/ClickHouse/pull/37650) ([Amos Bird](https://github.com/amosbird)). +* Fix columns number mismatch in cross join, close [#37561](https://github.com/ClickHouse/ClickHouse/issues/37561). [#37653](https://github.com/ClickHouse/ClickHouse/pull/37653) ([Vladimir C](https://github.com/vdimir)). +* Fix segmentation fault in `show create table` from mysql database when it is configured with named collections. Closes [#37683](https://github.com/ClickHouse/ClickHouse/issues/37683). [#37690](https://github.com/ClickHouse/ClickHouse/pull/37690) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Fix RabbitMQ Storage not being able to startup on server restart if storage was create without SETTINGS clause. Closes [#37463](https://github.com/ClickHouse/ClickHouse/issues/37463). [#37691](https://github.com/ClickHouse/ClickHouse/pull/37691) ([Kseniia Sumarokova](https://github.com/kssenii)). +* Fixed DateTime64 fractional seconds behavior prior to Unix epoch. [#37697](https://github.com/ClickHouse/ClickHouse/pull/37697) ([Andrey Zvonov](https://github.com/zvonand)). +* SQL user defined functions disable CREATE/DROP in readonly mode. Closes [#37280](https://github.com/ClickHouse/ClickHouse/issues/37280). [#37699](https://github.com/ClickHouse/ClickHouse/pull/37699) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix formatting of Nullable arguments for executable user defined functions. Closes [#35897](https://github.com/ClickHouse/ClickHouse/issues/35897). [#37711](https://github.com/ClickHouse/ClickHouse/pull/37711) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix optimization enabled by setting `optimize_monotonous_functions_in_order_by` in distributed queries. Fixes [#36037](https://github.com/ClickHouse/ClickHouse/issues/36037). [#37724](https://github.com/ClickHouse/ClickHouse/pull/37724) ([Anton Popov](https://github.com/CurtizJ)). +* Fix `SELECT ... INTERSECT` and `EXCEPT SELECT` statements with constant string types. [#37738](https://github.com/ClickHouse/ClickHouse/pull/37738) ([Antonio Andelic](https://github.com/antonio2368)). +* Fix crash of FunctionHashID, closes [#37735](https://github.com/ClickHouse/ClickHouse/issues/37735). [#37742](https://github.com/ClickHouse/ClickHouse/pull/37742) ([flynn](https://github.com/ucasfl)). +* Fix possible logical error: `Invalid Field get from type UInt64 to type Float64` in `values` table function. Closes [#37602](https://github.com/ClickHouse/ClickHouse/issues/37602). [#37754](https://github.com/ClickHouse/ClickHouse/pull/37754) ([Kruglov Pavel](https://github.com/Avogar)). +* Fix possible segfault in schema inference in case of exception in SchemaReader constructor. Closes [#37680](https://github.com/ClickHouse/ClickHouse/issues/37680). [#37760](https://github.com/ClickHouse/ClickHouse/pull/37760) ([Kruglov Pavel](https://github.com/Avogar)). +* Fix setting cast_ipv4_ipv6_default_on_conversion_error for internal cast function. Closes [#35156](https://github.com/ClickHouse/ClickHouse/issues/35156). [#37761](https://github.com/ClickHouse/ClickHouse/pull/37761) ([Maksim Kita](https://github.com/kitaisreal)). +* Octal literals are not supported. [#37765](https://github.com/ClickHouse/ClickHouse/pull/37765) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* fix toString error on DatatypeDate32. [#37775](https://github.com/ClickHouse/ClickHouse/pull/37775) ([LiuNeng](https://github.com/liuneng1994)). +* The clickhouse-keeper setting `dead_session_check_period_ms` was transformed into microseconds (multiplied by 1000), which lead to dead sessions only being cleaned up after several minutes (instead of 500ms). [#37824](https://github.com/ClickHouse/ClickHouse/pull/37824) ([Michael Lex](https://github.com/mlex)). +* Fix possible "No more packets are available" for distributed queries (in case of `async_socket_for_remote`/`use_hedged_requests` is disabled). [#37826](https://github.com/ClickHouse/ClickHouse/pull/37826) ([Azat Khuzhin](https://github.com/azat)). +* Do not drop the inner target table when executing `ALTER TABLE … MODIFY QUERY` in WindowView. [#37879](https://github.com/ClickHouse/ClickHouse/pull/37879) ([vxider](https://github.com/Vxider)). +* Fix directory ownership of coordination dir in clickhouse-keeper Docker image. Fixes [#37914](https://github.com/ClickHouse/ClickHouse/issues/37914). [#37915](https://github.com/ClickHouse/ClickHouse/pull/37915) ([James Maidment](https://github.com/jamesmaidment)). +* Dictionaries fix custom query with update field and `{condition}`. Closes [#33746](https://github.com/ClickHouse/ClickHouse/issues/33746). [#37947](https://github.com/ClickHouse/ClickHouse/pull/37947) ([Maksim Kita](https://github.com/kitaisreal)). +* Fix possible incorrect result of `SELECT ... WITH FILL` in the case when `ORDER BY` should be applied after `WITH FILL` result (e.g. for outer query). Incorrect result was caused by optimization for `ORDER BY` expressions ([#35623](https://github.com/ClickHouse/ClickHouse/issues/35623)). Closes [#37904](https://github.com/ClickHouse/ClickHouse/issues/37904). [#37959](https://github.com/ClickHouse/ClickHouse/pull/37959) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). +* Add missing default columns when pushing to the target table in WindowView, fix [#37815](https://github.com/ClickHouse/ClickHouse/issues/37815). [#37965](https://github.com/ClickHouse/ClickHouse/pull/37965) ([vxider](https://github.com/Vxider)). +* Fixed a stack overflow issue that would cause compilation to fail. [#37996](https://github.com/ClickHouse/ClickHouse/pull/37996) ([Han Shukai](https://github.com/KinderRiven)). +* when open enable_filesystem_query_cache_limit, throw Reserved cache size exceeds the remaining cache size. [#38004](https://github.com/ClickHouse/ClickHouse/pull/38004) ([xiedeyantu](https://github.com/xiedeyantu)). +* Query, containing ORDER BY ... WITH FILL, can generate extra rows when multiple WITH FILL columns are present. [#38074](https://github.com/ClickHouse/ClickHouse/pull/38074) ([Yakov Olkhovskiy](https://github.com/yakov-olkhovskiy)). + +#### Bug Fix (user-visible misbehaviour in official stable or prestable release) + +* Fix converting types for UNION queries (may produce LOGICAL_ERROR). [#34775](https://github.com/ClickHouse/ClickHouse/pull/34775) ([Azat Khuzhin](https://github.com/azat)). +* TTL merge may not be scheduled again if BackgroundExecutor is busy. --merges_with_ttl_counter is increased in selectPartsToMerge() --merge task will be ignored if BackgroundExecutor is busy --merges_with_ttl_counter will not be decrease. [#36387](https://github.com/ClickHouse/ClickHouse/pull/36387) ([lthaooo](https://github.com/lthaooo)). +* Fix overrided settings value of `normalize_function_names`. [#36937](https://github.com/ClickHouse/ClickHouse/pull/36937) ([李扬](https://github.com/taiyang-li)). +* Fix for exponential time decaying window functions. Now respecting boundaries of the window. [#36944](https://github.com/ClickHouse/ClickHouse/pull/36944) ([Vladimir Chebotarev](https://github.com/excitoon)). +* Fix bug datetime64 parse from string '1969-12-31 23:59:59.123'. Close [#36994](https://github.com/ClickHouse/ClickHouse/issues/36994). [#37039](https://github.com/ClickHouse/ClickHouse/pull/37039) ([李扬](https://github.com/taiyang-li)). + +#### NO CL ENTRY + +* NO CL ENTRY: 'Revert "Fix mutations in tables with columns of type `Object`"'. [#37355](https://github.com/ClickHouse/ClickHouse/pull/37355) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "Remove height restrictions from the query div in play web tool, and m…"'. [#37501](https://github.com/ClickHouse/ClickHouse/pull/37501) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* NO CL ENTRY: 'Revert "Add support for preprocessing ZooKeeper operations in `clickhouse-keeper`"'. [#37534](https://github.com/ClickHouse/ClickHouse/pull/37534) ([Antonio Andelic](https://github.com/antonio2368)). +* NO CL ENTRY: 'Revert "(only with zero-copy replication, non-production experimental feature not recommended to use) fix possible deadlock during fetching part"'. [#37545](https://github.com/ClickHouse/ClickHouse/pull/37545) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "RFC: Fix converting types for UNION queries (may produce LOGICAL_ERROR)"'. [#37582](https://github.com/ClickHouse/ClickHouse/pull/37582) ([Dmitry Novik](https://github.com/novikd)). +* NO CL ENTRY: 'Revert "Revert "(only with zero-copy replication, non-production experimental feature not recommended to use) fix possible deadlock during fetching part""'. [#37598](https://github.com/ClickHouse/ClickHouse/pull/37598) ([alesapin](https://github.com/alesapin)). +* NO CL ENTRY: 'Revert "Implemented changing comment to a ReplicatedMergeTree table"'. [#37627](https://github.com/ClickHouse/ClickHouse/pull/37627) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "Remove resursive submodules"'. [#37774](https://github.com/ClickHouse/ClickHouse/pull/37774) ([Alexey Milovidov](https://github.com/alexey-milovidov)). +* NO CL ENTRY: 'Revert "Fix possible segfault in schema inference"'. [#37785](https://github.com/ClickHouse/ClickHouse/pull/37785) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "Revert "Fix possible segfault in schema inference""'. [#37787](https://github.com/ClickHouse/ClickHouse/pull/37787) ([Kruglov Pavel](https://github.com/Avogar)). +* NO CL ENTRY: 'Add more Rust client libraries to documentation'. [#37880](https://github.com/ClickHouse/ClickHouse/pull/37880) ([Paul Loyd](https://github.com/loyd)). +* NO CL ENTRY: 'Revert "Fix errors of CheckTriviallyCopyableMove type"'. [#37902](https://github.com/ClickHouse/ClickHouse/pull/37902) ([Anton Popov](https://github.com/CurtizJ)). +* NO CL ENTRY: 'Revert "Don't try to kill empty list of containers in `integration/runner`"'. [#38001](https://github.com/ClickHouse/ClickHouse/pull/38001) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "add d3js based trace visualizer as gantt chart"'. [#38043](https://github.com/ClickHouse/ClickHouse/pull/38043) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "Add backoff to merges in replicated queue if `always_fetch_merged_part` is enabled"'. [#38082](https://github.com/ClickHouse/ClickHouse/pull/38082) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "More parallel execution for queries with `FINAL`"'. [#38094](https://github.com/ClickHouse/ClickHouse/pull/38094) ([Alexander Tokmakov](https://github.com/tavplubix)). +* NO CL ENTRY: 'Revert "Revert "add d3js based trace visualizer as gantt chart""'. [#38129](https://github.com/ClickHouse/ClickHouse/pull/38129) ([Alexey Milovidov](https://github.com/alexey-milovidov)). + diff --git a/utils/list-versions/version_date.tsv b/utils/list-versions/version_date.tsv index 1d8bae44904..2047d46251e 100644 --- a/utils/list-versions/version_date.tsv +++ b/utils/list-versions/version_date.tsv @@ -1,3 +1,4 @@ +v22.6.1.1985-stable 2022-06-16 v22.5.1.2079-stable 2022-05-19 v22.4.5.9-stable 2022-05-06 v22.4.4.7-stable 2022-04-29 From e5ceaf3c0f61f59859f8c76c651218bb03e3e999 Mon Sep 17 00:00:00 2001 From: "Mikhail f. Shiryaev" Date: Thu, 16 Jun 2022 17:49:26 +0200 Subject: [PATCH 166/204] Launch cherry-pick/backport job on graviton --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 66dddbee640..da42bbae78a 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -13,7 +13,7 @@ on: # yamllint disable-line rule:truthy jobs: CherryPick: - runs-on: [self-hosted, style-checker] + runs-on: [self-hosted, style-checker-aarch64] steps: - name: Set envs # https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#multiline-strings From 2a9942ee809c1eaafefa5a3fef24d35e45cb560a Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 16 Jun 2022 15:50:03 +0000 Subject: [PATCH 167/204] Add setting multiple_joins_try_to_keep_original_names --- src/Core/Settings.h | 1 + src/Interpreters/InterpreterSelectQuery.cpp | 2 ++ src/Interpreters/JoinToSubqueryTransformVisitor.cpp | 11 +++++++++-- src/Interpreters/JoinToSubqueryTransformVisitor.h | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Core/Settings.h b/src/Core/Settings.h index e3f756c85f5..4115061c3df 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -600,6 +600,7 @@ static constexpr UInt64 operator""_GiB(unsigned long long value) M(TransactionsWaitCSNMode, wait_changes_become_visible_after_commit_mode, TransactionsWaitCSNMode::WAIT_UNKNOWN, "Wait for committed changes to become actually visible in the latest snapshot", 0) \ M(Bool, throw_if_no_data_to_insert, true, "Enables or disables empty INSERTs, enabled by default", 0) \ M(Bool, compatibility_ignore_auto_increment_in_create_table, false, "Ignore AUTO_INCREMENT keyword in column declaration if true, otherwise return error. It simplifies migration from MySQL", 0) \ + M(Bool, multiple_joins_try_to_keep_original_names, false, "Do not add aliases to top level expression list on multiple joins rewrite", 0) \ // End of COMMON_SETTINGS // Please add settings related to formats into the FORMAT_FACTORY_SETTINGS and move obsolete settings to OBSOLETE_SETTINGS. diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 94ac7c26183..ec7c3878b06 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -232,6 +232,8 @@ static void rewriteMultipleJoins(ASTPtr & query, const TablesWithColumns & table CrossToInnerJoinVisitor(cross_to_inner).visit(query); JoinToSubqueryTransformVisitor::Data join_to_subs_data{tables, aliases}; + join_to_subs_data.try_to_keep_original_names = settings.multiple_joins_try_to_keep_original_names; + JoinToSubqueryTransformVisitor(join_to_subs_data).visit(query); } diff --git a/src/Interpreters/JoinToSubqueryTransformVisitor.cpp b/src/Interpreters/JoinToSubqueryTransformVisitor.cpp index a3ffaafa4db..e07430c0feb 100644 --- a/src/Interpreters/JoinToSubqueryTransformVisitor.cpp +++ b/src/Interpreters/JoinToSubqueryTransformVisitor.cpp @@ -361,6 +361,7 @@ struct CheckAliasDependencyVisitorData dependency = &ident; } }; + using CheckAliasDependencyMatcher = OneTypeMatcher; using CheckAliasDependencyVisitor = InDepthNodeVisitor; @@ -500,6 +501,7 @@ void restoreName(ASTIdentifier & ident, const String & original_name, NameSet & { if (!ident.tryGetAlias().empty()) return; + if (original_name.empty()) return; @@ -509,7 +511,9 @@ void restoreName(ASTIdentifier & ident, const String & original_name, NameSet & restored_names.emplace(original_name); } else + { ident.setShortName(original_name); + } } /// Find clashes and normalize names @@ -527,12 +531,12 @@ std::vector normalizeColumnNamesExtractNeeded( { size_t last_table_pos = tables.size() - 1; - NameSet restored_names; std::vector needed_columns; needed_columns.reserve(tables.size()); for (const auto & table : tables) needed_columns.push_back(TableNeededColumns{table.table}); + NameSet restored_names; for (ASTIdentifier * ident : identifiers) { bool got_alias = aliases.contains(ident->name()); @@ -729,7 +733,10 @@ void JoinToSubqueryTransformMatcher::visit(ASTSelectQuery & select, ASTPtr & ast std::unordered_set public_identifiers; for (auto & top_level_child : select.select()->children) if (auto * ident = top_level_child->as()) - public_identifiers.insert(ident); + { + if (!data.try_to_keep_original_names || startsWith(ident->name(), UniqueShortNames::pattern)) + public_identifiers.insert(ident); + } UniqueShortNames unique_names; std::vector needed_columns = diff --git a/src/Interpreters/JoinToSubqueryTransformVisitor.h b/src/Interpreters/JoinToSubqueryTransformVisitor.h index a024a168509..96420512ae6 100644 --- a/src/Interpreters/JoinToSubqueryTransformVisitor.h +++ b/src/Interpreters/JoinToSubqueryTransformVisitor.h @@ -21,6 +21,7 @@ public: const std::vector & tables; const Aliases & aliases; bool done = false; + bool try_to_keep_original_names = false; }; static bool needChildVisit(ASTPtr &, const ASTPtr &); From 15988a220b5463b0f5c485e653bc546a336aa4ce Mon Sep 17 00:00:00 2001 From: vdimir Date: Thu, 16 Jun 2022 15:52:55 +0000 Subject: [PATCH 168/204] Add test multiple_joins_original_names --- ...37_multiple_joins_original_names.reference | 2 ++ .../02337_multiple_joins_original_names.sql | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/queries/0_stateless/02337_multiple_joins_original_names.reference create mode 100644 tests/queries/0_stateless/02337_multiple_joins_original_names.sql diff --git a/tests/queries/0_stateless/02337_multiple_joins_original_names.reference b/tests/queries/0_stateless/02337_multiple_joins_original_names.reference new file mode 100644 index 00000000000..6ed281c757a --- /dev/null +++ b/tests/queries/0_stateless/02337_multiple_joins_original_names.reference @@ -0,0 +1,2 @@ +1 +1 diff --git a/tests/queries/0_stateless/02337_multiple_joins_original_names.sql b/tests/queries/0_stateless/02337_multiple_joins_original_names.sql new file mode 100644 index 00000000000..afafee9f8eb --- /dev/null +++ b/tests/queries/0_stateless/02337_multiple_joins_original_names.sql @@ -0,0 +1,22 @@ +-- https://github.com/ClickHouse/ClickHouse/issues/34697 + +SELECT table1_id FROM ( + SELECT first.table1_id + FROM (SELECT number+1 as table1_id FROM numbers(1)) as first + JOIN (SELECT number+1 as table2_id FROM numbers(1)) as second ON first.table1_id = second.table2_id + JOIN (SELECT number+1 as table3_id FROM numbers(1)) as third ON first.table1_id = third.table3_id +); -- { serverError UNKNOWN_IDENTIFIER } + +SELECT table1_id FROM ( + SELECT first.table1_id + FROM (SELECT number+1 as table1_id FROM numbers(1)) as first + JOIN (SELECT number+1 as table2_id FROM numbers(1)) as second ON first.table1_id = second.table2_id + JOIN (SELECT number+1 as table3_id FROM numbers(1)) as third ON first.table1_id = third.table3_id +) SETTINGS multiple_joins_try_to_keep_original_names = 1; + +SELECT aaa FROM ( + SELECT first.table1_id as aaa + FROM (SELECT number+1 as table1_id FROM numbers(1)) as first + JOIN (SELECT number+1 as table2_id FROM numbers(1)) as second ON first.table1_id = second.table2_id + JOIN (SELECT number+1 as table3_id FROM numbers(1)) as third ON first.table1_id = third.table3_id +) SETTINGS multiple_joins_try_to_keep_original_names = 1; From 6fdcac1c9d109b3f9bddffc9b5e652d5fcd3bde8 Mon Sep 17 00:00:00 2001 From: Yatsishin Ilya <2159081+qoega@users.noreply.github.com> Date: Thu, 16 Jun 2022 19:47:11 +0000 Subject: [PATCH 169/204] Remove processor description from span attributes - it is not working anyway. --- src/Processors/Executors/ExecutionThreadContext.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Processors/Executors/ExecutionThreadContext.cpp b/src/Processors/Executors/ExecutionThreadContext.cpp index d77fe6138cd..5a5c1826c61 100644 --- a/src/Processors/Executors/ExecutionThreadContext.cpp +++ b/src/Processors/Executors/ExecutionThreadContext.cpp @@ -103,7 +103,6 @@ bool ExecutionThreadContext::executeTask() #endif span.addAttribute("thread_number", thread_number); - span.addAttribute("processor.description", node->processor->getDescription()); return node->exception == nullptr; } From 09438ed2dd7a42555052e826b2397dd2c4629f2a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 16 Jun 2022 22:56:34 +0200 Subject: [PATCH 170/204] utils/security-generator/SECURITY.md.sh > SECURITY.md --- SECURITY.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 426559a0439..467bdb51466 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,4 @@ + # Security Policy ## Security Announcements @@ -7,29 +8,30 @@ Security fixes will be announced by posting them in the [security changelog](htt The following versions of ClickHouse server are currently being supported with security updates: -| Version | Supported | -| ------- | ------------------ | -| 1.x | :x: | -| 18.x | :x: | -| 19.x | :x: | -| 20.x | :x: | -| 21.1 | :x: | -| 21.2 | :x: | -| 21.3 | :x: | -| 21.4 | :x: | -| 21.5 | :x: | -| 21.6 | :x: | -| 21.7 | :x: | -| 21.8 | ✅ | -| 21.9 | :x: | -| 21.10 | :x: | -| 21.11 | :x: | -| 21.12 | :x: | -| 22.1 | :x: | -| 22.2 | :x: | -| 22.3 | ✅ | -| 22.4 | ✅ | -| 22.5 | ✅ | +| Version | Supported | +|:-|:-| +| 22.6 | ✔️ | +| 22.5 | ✔️ | +| 22.4 | ✔️ | +| 22.3 | ✔️ | +| 22.2 | ❌ | +| 22.1 | ❌ | +| 21.12 | ❌ | +| 21.11 | ❌ | +| 21.10 | ❌ | +| 21.9 | ❌ | +| 21.8 | ✔️ | +| 21.7 | ❌ | +| 21.6 | ❌ | +| 21.5 | ❌ | +| 21.4 | ❌ | +| 21.3 | ❌ | +| 21.2 | ❌ | +| 21.1 | ❌ | +| 20.* | ❌ | +| 19.* | ❌ | +| 18.* | ❌ | +| 1.* | ❌ | ## Reporting a Vulnerability @@ -57,4 +59,3 @@ As the security issue moves from triage, to identified fix, to release planning A public disclosure date is negotiated by the ClickHouse maintainers and the bug submitter. We prefer to fully disclose the bug as soon as possible once a user mitigation is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to 90 days. For a vulnerability with a straightforward mitigation, we expect report date to disclosure date to be on the order of 7 days. - From 1ae12e13a19e6063ff9b4b980a0c7e9182726e84 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Thu, 16 Jun 2022 18:34:51 -0400 Subject: [PATCH 171/204] add H3 headers for operators --- docs/en/sql-reference/operators/index.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/en/sql-reference/operators/index.md b/docs/en/sql-reference/operators/index.md index 5df018bb920..17b8f014366 100644 --- a/docs/en/sql-reference/operators/index.md +++ b/docs/en/sql-reference/operators/index.md @@ -43,28 +43,38 @@ For tuple subtraction: [tupleMinus](../../sql-reference/functions/tuple-function ## Comparison Operators +### equals function `a = b` – The `equals(a, b)` function. `a == b` – The `equals(a, b)` function. +### notEquals function `a != b` – The `notEquals(a, b)` function. `a <> b` – The `notEquals(a, b)` function. +### lessOrEquals function `a <= b` – The `lessOrEquals(a, b)` function. +### greaterOrEquals function `a >= b` – The `greaterOrEquals(a, b)` function. +### less function `a < b` – The `less(a, b)` function. +### greater function `a > b` – The `greater(a, b)` function. +### like function `a LIKE s` – The `like(a, b)` function. +### notLike function `a NOT LIKE s` – The `notLike(a, b)` function. +### ilike function `a ILIKE s` – The `ilike(a, b)` function. +### BETWEEN function `a BETWEEN b AND c` – The same as `a >= b AND a <= c`. `a NOT BETWEEN b AND c` – The same as `a < b OR a > c`. @@ -73,20 +83,28 @@ For tuple subtraction: [tupleMinus](../../sql-reference/functions/tuple-function See [IN operators](../../sql-reference/operators/in.md) and [EXISTS](../../sql-reference/operators/exists.md) operator. +### in function `a IN ...` – The `in(a, b)` function. +### notIn function `a NOT IN ...` – The `notIn(a, b)` function. +### globalIn function `a GLOBAL IN ...` – The `globalIn(a, b)` function. +### globalNotIn function `a GLOBAL NOT IN ...` – The `globalNotIn(a, b)` function. +### in subquery function `a = ANY (subquery)` – The `in(a, subquery)` function. +### notIn subquery function `a != ANY (subquery)` – The same as `a NOT IN (SELECT singleValueOrNull(*) FROM subquery)`. +### in subquery function `a = ALL (subquery)` – The same as `a IN (SELECT singleValueOrNull(*) FROM subquery)`. +### notIn subquery function `a != ALL (subquery)` – The `notIn(a, subquery)` function. From 00aca924d0586b41b751ed6536c47653494009a8 Mon Sep 17 00:00:00 2001 From: Dmitry Novik Date: Thu, 16 Jun 2022 22:45:28 +0000 Subject: [PATCH 172/204] Update GROUP BY clause docs --- .../statements/select/group-by.md | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/docs/en/sql-reference/statements/select/group-by.md b/docs/en/sql-reference/statements/select/group-by.md index 45230d0b3b1..e02db6d4f6b 100644 --- a/docs/en/sql-reference/statements/select/group-by.md +++ b/docs/en/sql-reference/statements/select/group-by.md @@ -48,9 +48,9 @@ You can see that `GROUP BY` for `y = NULL` summed up `x`, as if `NULL` is this v If you pass several keys to `GROUP BY`, the result will give you all the combinations of the selection, as if `NULL` were a specific value. -## WITH ROLLUP Modifier +## ROLLUP Modifier -`WITH ROLLUP` modifier is used to calculate subtotals for the key expressions, based on their order in the `GROUP BY` list. The subtotals rows are added after the result table. +`ROLLUP` modifier is used to calculate subtotals for the key expressions, based on their order in the `GROUP BY` list. The subtotals rows are added after the result table. The subtotals are calculated in the reverse order: at first subtotals are calculated for the last key expression in the list, then for the previous one, and so on up to the first key expression. @@ -78,7 +78,7 @@ Consider the table t: Query: ```sql -SELECT year, month, day, count(*) FROM t GROUP BY year, month, day WITH ROLLUP; +SELECT year, month, day, count(*) FROM t GROUP BY ROLLUP(year, month, day); ``` As `GROUP BY` section has three key expressions, the result contains four tables with subtotals "rolled up" from right to left: @@ -109,10 +109,14 @@ As `GROUP BY` section has three key expressions, the result contains four tables │ 0 │ 0 │ 0 │ 6 │ └──────┴───────┴─────┴─────────┘ ``` +The same query also can be written using `WITH` keyword. +```sql +SELECT year, month, day, count(*) FROM t GROUP BY year, month, day WITH ROLLUP; +``` -## WITH CUBE Modifier +## CUBE Modifier -`WITH CUBE` modifier is used to calculate subtotals for every combination of the key expressions in the `GROUP BY` list. The subtotals rows are added after the result table. +`CUBE` modifier is used to calculate subtotals for every combination of the key expressions in the `GROUP BY` list. The subtotals rows are added after the result table. In the subtotals rows the values of all "grouped" key expressions are set to `0` or empty line. @@ -138,7 +142,7 @@ Consider the table t: Query: ```sql -SELECT year, month, day, count(*) FROM t GROUP BY year, month, day WITH CUBE; +SELECT year, month, day, count(*) FROM t GROUP BY CUBE(year, month, day); ``` As `GROUP BY` section has three key expressions, the result contains eight tables with subtotals for all key expression combinations: @@ -196,6 +200,10 @@ Columns, excluded from `GROUP BY`, are filled with zeros. │ 0 │ 0 │ 0 │ 6 │ └──────┴───────┴─────┴─────────┘ ``` +The same query also can be written using `WITH` keyword. +```sql +SELECT year, month, day, count(*) FROM t GROUP BY year, month, day WITH CUBE; +``` ## WITH TOTALS Modifier @@ -260,6 +268,39 @@ GROUP BY domain For every different key value encountered, `GROUP BY` calculates a set of aggregate function values. +## GROUPING SETS modifier + +This is the most general modifier. +This modifier allows to manually specify several aggregation key sets (grouping sets). +Aggregation is performed separately for each grouping set, after that all results are combined. +If a column is not presented in a grouping set, it's filled with a default value. + +In other words, modifiers described above can be represented via `GROUPING SETS`. +Despite the fact that queries with `ROLLUP`, `CUBE` and `GROUPING SETS` modifiers are syntactically equal, they may have different performance. +When `GROUPING SETS` try to execute everything in parallel, `ROLLUP` and `CUBE` are executing the final merging of the aggregates in a single thread. + +In the situation when source columns contain default values, it might be hard to distinguish if a row is a part of the aggregation which uses those columns as keys or not. +To solve this problem `GROUPING` function must be used. + +**Example** + +The following two queries are equivalent. + +```sql +-- Query 1 +SELECT year, month, day, count(*) FROM t GROUP BY year, month, day WITH ROLLUP; + +-- Query 2 +SELECT year, month, day, count(*) FROM t GROUP BY +GROUPING SETS +( + (year, month, day), + (year, month), + (year), + () +); +``` + ## Implementation Details Aggregation is one of the most important features of a column-oriented DBMS, and thus it’s implementation is one of the most heavily optimized parts of ClickHouse. By default, aggregation is done in memory using a hash-table. It has 40+ specializations that are chosen automatically depending on “grouping key” data types. From 1d9bcb5993c6b03e4498b5ef7fee9d711cc3f44d Mon Sep 17 00:00:00 2001 From: Sergei Trifonov Date: Fri, 17 Jun 2022 09:30:08 +0200 Subject: [PATCH 173/204] improve trace-visualizer UX --- utils/trace-visualizer/README.md | 14 ++----- utils/trace-visualizer/index.html | 57 +++++++++++++++++++++++---- utils/trace-visualizer/js/d3-gantt.js | 3 ++ 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/utils/trace-visualizer/README.md b/utils/trace-visualizer/README.md index 63a6a737e3c..daef02d411e 100644 --- a/utils/trace-visualizer/README.md +++ b/utils/trace-visualizer/README.md @@ -1,17 +1,12 @@ Trace visualizer is a tool for representation of a tracing data as a Gantt diagram. # Quick start -For now this tool is not integrated into ClickHouse and requires a lot of manual adjustments. -```bash -cd utils/trace-visualizer -python3 -m http.server -``` -Open [localhost](http://localhost:8000). It will show an example of data. To show your tracing data you have to put it in JSON format near `index.html` and change call to `fetchData()` function at the bottom of `index.html`. (Do not forget to disable browser caching while changing it). +For now this tool is not integrated into ClickHouse and requires manual actions. Open `trace-visualizer/index.html` in your browser. It will show an example of data. To visualize your data click `Load` button and select your trace data JSON file. # Visualizing query trace First of all [opentelemetry_span_log](https://clickhouse.com/docs/en/operations/opentelemetry/) system table must be enabled to save query traces. Then run a query you want to trace with a setting: ```sql -set opentelemetry_start_trace_probability=1; +SET opentelemetry_start_trace_probability=1; SELECT 1; ``` @@ -22,10 +17,9 @@ SELECT DISTINCT trace_id FROM system.opentelemetry_span_log ORDER BY query_start To obtain JSON data suitable for visualizing run: ```sql -SELECT tuple (parent_span_id, attribute['clickhouse.thread_id'] || attribute['thread_number'] as thread_id)::Tuple(parent_span_id UInt64, thread_id String) as group, operation_name, start_time_us, finish_time_us, sipHash64(operation_name) as color, attribute -from system.opentelemetry_span_log +SELECT tuple (leftPad(attribute['clickhouse.thread_id'] || attribute['thread_number'], 10, '0') as thread_id, parent_span_id)::Tuple(thread_id String, parent_span_id UInt64) as group, operation_name, start_time_us, finish_time_us, sipHash64(operation_name) as color, attribute +FROM system.opentelemetry_span_log WHERE trace_id = 'your-trace-id' -ORDER BY group ASC FORMAT JSON SETTINGS output_format_json_named_tuples_as_objects = 1; ``` diff --git a/utils/trace-visualizer/index.html b/utils/trace-visualizer/index.html index ea02b3141ad..ac39df1f98f 100644 --- a/utils/trace-visualizer/index.html +++ b/utils/trace-visualizer/index.html @@ -14,7 +14,30 @@ +
+
+ +
+
+ @@ -27,13 +50,16 @@ { t1: 500, t2: 800, band: "band2", color: "#f8f", text: "test\nif\nnew\nline\nworks\nhere?" } ]; - var chart = d3.gantt() - .height(window.innerHeight - $("#placeholder")[0].getBoundingClientRect().y - window.scrollY) - .selector("#placeholder"); - + let chart_height = window.innerHeight - $("#placeholder")[0].getBoundingClientRect().y - 80; var data = null; + var chart = null; + + function renderChart(parsed) { + data = parsed; + chart = d3.gantt().height(chart_height).selector("#placeholder"); + chart(data); + } - // Error message popup $("
").css({ position: "absolute", display: "none", @@ -46,8 +72,7 @@ function fetchData(dataurl, parser = x => x) { function onDataReceived(json, textStatus, xhr) { $("#errmsg").hide(); - data = parser(json); - chart(data); + renderChart(parser(json)); } function onDataError(xhr, error) { @@ -70,6 +95,22 @@ } } + $("#btnDoLoad").click(function(){ + let element = document.getElementById('loadFiles'); + let files = element.files; + if (files.length <= 0) { + return false; + } + let fr = new FileReader(); + fr.onload = function(e) { + $("#errmsg").hide(); + renderChart(parseClickHouseTrace(JSON.parse(e.target.result))); + } + fr.readAsText(files.item(0)); + element.value = ''; + $('#loadModal').modal('hide'); + }); + function parseClickHouseTrace(json) { let min_time_us = Number.MAX_VALUE; for (let i = 0; i < json.data.length; i++) { @@ -114,5 +155,5 @@ } fetchData(); // do not fetch, just draw example_json w/o parsing - //fetchData("your-traces.json" , parseClickHouseTrace); + //fetchData("your-traces.json", parseClickHouseTrace); diff --git a/utils/trace-visualizer/js/d3-gantt.js b/utils/trace-visualizer/js/d3-gantt.js index 21a9dab6133..8712e5b640c 100644 --- a/utils/trace-visualizer/js/d3-gantt.js +++ b/utils/trace-visualizer/js/d3-gantt.js @@ -8,6 +8,9 @@ initAxis(); + // clear previous chart (if any) + d3.select(selector).selectAll("svg").remove(); + // create svg element svg = d3.select(selector) .append("svg") From 26b122d9a94050b2f4fc49d1d135312f1da9905a Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Fri, 17 Jun 2022 09:49:08 +0200 Subject: [PATCH 174/204] Bump minimum / maximum LLVM to 12 / 14 - the CI builds were recently upgraded to Clang 14 - this commit bumps versions of other LLVM tools needed for the build - this is important for people who have multiple LLVM versions installed via their package manager --- cmake/tools.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/tools.cmake b/cmake/tools.cmake index 0560bd46fed..181f5b8ac51 100644 --- a/cmake/tools.cmake +++ b/cmake/tools.cmake @@ -112,7 +112,7 @@ endif() # Archiver if (COMPILER_GCC) - find_program (LLVM_AR_PATH NAMES "llvm-ar" "llvm-ar-13" "llvm-ar-12" "llvm-ar-11") + find_program (LLVM_AR_PATH NAMES "llvm-ar" "llvm-ar-14" "llvm-ar-13" "llvm-ar-12") else () find_program (LLVM_AR_PATH NAMES "llvm-ar-${COMPILER_VERSION_MAJOR}" "llvm-ar") endif () @@ -126,7 +126,7 @@ message(STATUS "Using archiver: ${CMAKE_AR}") # Ranlib if (COMPILER_GCC) - find_program (LLVM_RANLIB_PATH NAMES "llvm-ranlib" "llvm-ranlib-13" "llvm-ranlib-12" "llvm-ranlib-11") + find_program (LLVM_RANLIB_PATH NAMES "llvm-ranlib" "llvm-ranlib-14" "llvm-ranlib-13" "llvm-ranlib-12") else () find_program (LLVM_RANLIB_PATH NAMES "llvm-ranlib-${COMPILER_VERSION_MAJOR}" "llvm-ranlib") endif () @@ -140,7 +140,7 @@ message(STATUS "Using ranlib: ${CMAKE_RANLIB}") # Install Name Tool if (COMPILER_GCC) - find_program (LLVM_INSTALL_NAME_TOOL_PATH NAMES "llvm-install-name-tool" "llvm-install-name-tool-13" "llvm-install-name-tool-12" "llvm-install-name-tool-11") + find_program (LLVM_INSTALL_NAME_TOOL_PATH NAMES "llvm-install-name-tool" "llvm-install-name-tool-14" "llvm-install-name-tool-13" "llvm-install-name-tool-12") else () find_program (LLVM_INSTALL_NAME_TOOL_PATH NAMES "llvm-install-name-tool-${COMPILER_VERSION_MAJOR}" "llvm-install-name-tool") endif () @@ -154,7 +154,7 @@ message(STATUS "Using install-name-tool: ${CMAKE_INSTALL_NAME_TOOL}") # Objcopy if (COMPILER_GCC) - find_program (OBJCOPY_PATH NAMES "llvm-objcopy" "llvm-objcopy-13" "llvm-objcopy-12" "llvm-objcopy-11" "objcopy") + find_program (OBJCOPY_PATH NAMES "llvm-objcopy" "llvm-objcopy-14" "llvm-objcopy-13" "llvm-objcopy-12" "objcopy") else () find_program (OBJCOPY_PATH NAMES "llvm-objcopy-${COMPILER_VERSION_MAJOR}" "llvm-objcopy" "objcopy") endif () @@ -168,7 +168,7 @@ endif () # Strip if (COMPILER_GCC) - find_program (STRIP_PATH NAMES "llvm-strip" "llvm-strip-13" "llvm-strip-12" "llvm-strip-11" "strip") + find_program (STRIP_PATH NAMES "llvm-strip" "llvm-strip-14" "llvm-strip-13" "llvm-strip-12" "strip") else () find_program (STRIP_PATH NAMES "llvm-strip-${COMPILER_VERSION_MAJOR}" "llvm-strip" "strip") endif () From 299f102ccf92e1ea001ad9483171f75f33cb75bc Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 17 Jun 2022 12:36:05 +0300 Subject: [PATCH 175/204] Update 02316_cast_to_ip_address_default_column.sql --- .../0_stateless/02316_cast_to_ip_address_default_column.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02316_cast_to_ip_address_default_column.sql b/tests/queries/0_stateless/02316_cast_to_ip_address_default_column.sql index e8071877379..200cec8fed9 100644 --- a/tests/queries/0_stateless/02316_cast_to_ip_address_default_column.sql +++ b/tests/queries/0_stateless/02316_cast_to_ip_address_default_column.sql @@ -1,3 +1,5 @@ +-- Tags: no-backward-compatibility-check + SET cast_ipv4_ipv6_default_on_conversion_error = 1; DROP TABLE IF EXISTS ipv4_test; From 26b3dd2d9e2de87ea721338b9511c5f786e4be6a Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Fri, 17 Jun 2022 13:41:58 +0200 Subject: [PATCH 176/204] Fix description --- docs/en/sql-reference/functions/string-search-functions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/sql-reference/functions/string-search-functions.md b/docs/en/sql-reference/functions/string-search-functions.md index 8d35204d783..0ecf980c163 100644 --- a/docs/en/sql-reference/functions/string-search-functions.md +++ b/docs/en/sql-reference/functions/string-search-functions.md @@ -354,7 +354,7 @@ Checks whether the string matches the regular expression `pattern` in `re2` synt Returns 0 if it does not match, or 1 if it matches. -Matching is based on UTF-8, e.g. `.` matches the two-codepoint symbol `¥`. The regular expression must not contain null bytes. +Matching is based on UTF-8, e.g. `.` matches the Unicode code point `¥` which is stored in UTF-8 using two bytes. The regular expression must not contain null bytes. For patterns to search for substrings in a string, it is better to use LIKE or ‘position’, since they work much faster. @@ -499,7 +499,7 @@ The regular expression can contain the metasymbols `%` and `_`. Use the backslash (`\`) for escaping metasymbols. See the note on escaping in the description of the ‘match’ function. -Matching is based on UTF-8, e.g. `_` matches the two-codepoint symbol `¥`. +Matching is based on UTF-8, e.g. `_` matches the Unicode code point `¥` which is stored in UTF-8 using two bytes. For regular expressions like `%needle%`, the code is more optimal and works as fast as the `position` function. For other regular expressions, the code is the same as for the ‘match’ function. From 14ecf4c4976a32db7c286116d73b47d55d7522e2 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 17 Jun 2022 08:40:14 -0400 Subject: [PATCH 177/204] s/Clickhouse/ClickHouse/ --- docs/changelogs/v20.5.1.3833-prestable.md | 2 +- docs/changelogs/v21.11.1.8636-prestable.md | 2 +- docs/changelogs/v21.12.1.9017-prestable.md | 2 +- docs/changelogs/v21.12.3.32-stable.md | 2 +- docs/changelogs/v21.2.2.8-stable.md | 2 +- docs/changelogs/v21.5.1.6601-prestable.md | 2 +- docs/changelogs/v22.1.1.2542-prestable.md | 4 ++-- docs/changelogs/v22.3.1.1262-prestable.md | 2 +- docs/en/development/contrib.md | 2 +- docs/en/operations/clickhouse-keeper.md | 4 ++-- docs/en/operations/system-tables/replication_queue.md | 2 +- docs/en/operations/tips.md | 2 +- docs/ru/faq/index.md | 2 +- docs/ru/sql-reference/data-types/lowcardinality.md | 2 +- docs/ru/sql-reference/functions/encryption-functions.md | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/changelogs/v20.5.1.3833-prestable.md b/docs/changelogs/v20.5.1.3833-prestable.md index 824fb051914..11a67ca8430 100644 --- a/docs/changelogs/v20.5.1.3833-prestable.md +++ b/docs/changelogs/v20.5.1.3833-prestable.md @@ -14,7 +14,7 @@ * Selects with final are executed in parallel. Added setting `max_final_threads` to limit the number of threads used. [#10463](https://github.com/ClickHouse/ClickHouse/pull/10463) ([Nikolai Kochetov](https://github.com/KochetovNicolai)). * Function that extracts from haystack all matching non-overlapping groups with regular expressions, and put those into `Array(Array(String))` column. [#10534](https://github.com/ClickHouse/ClickHouse/pull/10534) ([Vasily Nemkov](https://github.com/Enmk)). * Added ability to delete a subset of expired rows, which satisfies the condition in WHERE clause. Added ability to replace expired rows with aggregates of them specified in GROUP BY clause. [#10537](https://github.com/ClickHouse/ClickHouse/pull/10537) ([expl0si0nn](https://github.com/expl0si0nn)). -* (Only Linux) Clickhouse server now tries to fallback to ProcfsMetricsProvider when clickhouse binary is not attributed with CAP_NET_ADMIN capability to collect per-query system metrics (for CPU and I/O). [#10544](https://github.com/ClickHouse/ClickHouse/pull/10544) ([Alexander Kazakov](https://github.com/Akazz)). +* (Only Linux) ClickHouse server now tries to fallback to ProcfsMetricsProvider when clickhouse binary is not attributed with CAP_NET_ADMIN capability to collect per-query system metrics (for CPU and I/O). [#10544](https://github.com/ClickHouse/ClickHouse/pull/10544) ([Alexander Kazakov](https://github.com/Akazz)). * - Add Arrow IPC File format (Input and Output) - Fix incorrect work of resetParser() for Parquet Input Format - Add zero-copy optimization for ORC for RandomAccessFiles - Add missing halffloat type for input parquet and ORC formats ... [#10580](https://github.com/ClickHouse/ClickHouse/pull/10580) ([Zhanna](https://github.com/FawnD2)). * Allowed to profile memory with finer granularity steps than 4 MiB. Added sampling memory profiler to capture random allocations/deallocations. [#10598](https://github.com/ClickHouse/ClickHouse/pull/10598) ([Alexey Milovidov](https://github.com/alexey-milovidov)). * Add new input format `JSONAsString` that accepts a sequence of JSON objects separated by newlines, spaces and/or commas. [#10607](https://github.com/ClickHouse/ClickHouse/pull/10607) ([Kruglov Pavel](https://github.com/Avogar)). diff --git a/docs/changelogs/v21.11.1.8636-prestable.md b/docs/changelogs/v21.11.1.8636-prestable.md index ade8084055c..2aab0293223 100644 --- a/docs/changelogs/v21.11.1.8636-prestable.md +++ b/docs/changelogs/v21.11.1.8636-prestable.md @@ -13,7 +13,7 @@ * Users now can set comments to database in `CREATE DATABASE` statement ... [#29429](https://github.com/ClickHouse/ClickHouse/pull/29429) ([Vasily Nemkov](https://github.com/Enmk)). * New function` mapContainsKeyLike` to get the map that key matches a simple regular expression. [#29471](https://github.com/ClickHouse/ClickHouse/pull/29471) ([凌涛](https://github.com/lingtaolf)). * Huawei OBS Storage support. Closes [#24294](https://github.com/ClickHouse/ClickHouse/issues/24294). [#29511](https://github.com/ClickHouse/ClickHouse/pull/29511) ([kevin wan](https://github.com/MaxWk)). -* Clickhouse HTTP Server can enable HSTS by set `hsts_max_age` in config.xml with a positive number. [#29516](https://github.com/ClickHouse/ClickHouse/pull/29516) ([凌涛](https://github.com/lingtaolf)). +* ClickHouse HTTP Server can enable HSTS by set `hsts_max_age` in config.xml with a positive number. [#29516](https://github.com/ClickHouse/ClickHouse/pull/29516) ([凌涛](https://github.com/lingtaolf)). * - Added MD4 and SHA384 functions. [#29602](https://github.com/ClickHouse/ClickHouse/pull/29602) ([Nikita Tikhomirov](https://github.com/NSTikhomirov)). * Support EXISTS(subquery). Closes [#6852](https://github.com/ClickHouse/ClickHouse/issues/6852). [#29731](https://github.com/ClickHouse/ClickHouse/pull/29731) ([Kseniia Sumarokova](https://github.com/kssenii)). * Added function `ngram`. Closes [#29699](https://github.com/ClickHouse/ClickHouse/issues/29699). [#29738](https://github.com/ClickHouse/ClickHouse/pull/29738) ([Maksim Kita](https://github.com/kitaisreal)). diff --git a/docs/changelogs/v21.12.1.9017-prestable.md b/docs/changelogs/v21.12.1.9017-prestable.md index bcf5424fc63..f5e036c9c52 100644 --- a/docs/changelogs/v21.12.1.9017-prestable.md +++ b/docs/changelogs/v21.12.1.9017-prestable.md @@ -54,7 +54,7 @@ * Add settings `merge_tree_min_rows_for_concurrent_read_for_remote_filesystem` and `merge_tree_min_bytes_for_concurrent_read_for_remote_filesystem`. [#30970](https://github.com/ClickHouse/ClickHouse/pull/30970) ([Kseniia Sumarokova](https://github.com/kssenii)). * Do not allow to drop a table or dictionary if some tables or dictionaries depend on it. [#30977](https://github.com/ClickHouse/ClickHouse/pull/30977) ([Alexander Tokmakov](https://github.com/tavplubix)). * Only grab AlterLock when we do alter command. Let's see if the assumption is correct. [#31010](https://github.com/ClickHouse/ClickHouse/pull/31010) ([Amos Bird](https://github.com/amosbird)). -* The local session inside a Clickhouse dictionary source won't send its events to the session log anymore. This fixes a possible deadlock (tsan alert) on shutdown. Also this PR fixes flaky `test_dictionaries_dependency_xml/`. [#31013](https://github.com/ClickHouse/ClickHouse/pull/31013) ([Vitaly Baranov](https://github.com/vitlibar)). +* The local session inside a ClickHouse dictionary source won't send its events to the session log anymore. This fixes a possible deadlock (tsan alert) on shutdown. Also this PR fixes flaky `test_dictionaries_dependency_xml/`. [#31013](https://github.com/ClickHouse/ClickHouse/pull/31013) ([Vitaly Baranov](https://github.com/vitlibar)). * Cancel vertical merges when partition is dropped. This is a follow-up of https://github.com/ClickHouse/ClickHouse/pull/25684 and https://github.com/ClickHouse/ClickHouse/pull/30996. [#31057](https://github.com/ClickHouse/ClickHouse/pull/31057) ([Amos Bird](https://github.com/amosbird)). * Support `IF EXISTS` modifier for `RENAME DATABASE`/`TABLE`/`DICTIONARY` query, If this directive is used, one will not get an error if the DATABASE/TABLE/DICTIONARY to be renamed doesn't exist. [#31081](https://github.com/ClickHouse/ClickHouse/pull/31081) ([victorgao](https://github.com/kafka1991)). * Function name normalization for ALTER queries. This helps avoid metadata mismatch between creating table with indices/projections and adding indices/projections via alter commands. This is a follow-up PR of https://github.com/ClickHouse/ClickHouse/pull/20174. Mark as improvements as there are no bug reports and the senario is somehow rare. [#31095](https://github.com/ClickHouse/ClickHouse/pull/31095) ([Amos Bird](https://github.com/amosbird)). diff --git a/docs/changelogs/v21.12.3.32-stable.md b/docs/changelogs/v21.12.3.32-stable.md index 3c08aae4cba..b650f62dd34 100644 --- a/docs/changelogs/v21.12.3.32-stable.md +++ b/docs/changelogs/v21.12.3.32-stable.md @@ -1,7 +1,7 @@ ### ClickHouse release v21.12.3.32-stable FIXME as compared to v21.12.2.17-stable #### Bug Fix -* Backported in [#33018](https://github.com/ClickHouse/ClickHouse/issues/33018): - Clickhouse Keeper handler should remove operation when response sent. [#32988](https://github.com/ClickHouse/ClickHouse/pull/32988) ([JackyWoo](https://github.com/JackyWoo)). +* Backported in [#33018](https://github.com/ClickHouse/ClickHouse/issues/33018): - ClickHouse Keeper handler should remove operation when response sent. [#32988](https://github.com/ClickHouse/ClickHouse/pull/32988) ([JackyWoo](https://github.com/JackyWoo)). #### Bug Fix (user-visible misbehaviour in official stable or prestable release) diff --git a/docs/changelogs/v21.2.2.8-stable.md b/docs/changelogs/v21.2.2.8-stable.md index 368243120f1..73baea91547 100644 --- a/docs/changelogs/v21.2.2.8-stable.md +++ b/docs/changelogs/v21.2.2.8-stable.md @@ -68,7 +68,7 @@ * Add separate pool for message brokers (RabbitMQ and Kafka). [#19722](https://github.com/ClickHouse/ClickHouse/pull/19722) ([Azat Khuzhin](https://github.com/azat)). * In distributed queries if the setting `async_socket_for_remote` is enabled, it was possible to get stack overflow at least in debug build configuration if very deeply nested data type is used in table (e.g. `Array(Array(Array(...more...)))`). This fixes [#19108](https://github.com/ClickHouse/ClickHouse/issues/19108). This change introduces minor backward incompatibility: excessive parenthesis in type definitions no longer supported, example: `Array((UInt8))`. [#19736](https://github.com/ClickHouse/ClickHouse/pull/19736) ([Alexey Milovidov](https://github.com/alexey-milovidov)). * Table function `S3` will use global region if the region can't be determined exactly. This closes [#10998](https://github.com/ClickHouse/ClickHouse/issues/10998). [#19750](https://github.com/ClickHouse/ClickHouse/pull/19750) ([Vladimir Chebotarev](https://github.com/excitoon)). -* Clickhouse client query param CTE added test. [#19762](https://github.com/ClickHouse/ClickHouse/pull/19762) ([Maksim Kita](https://github.com/kitaisreal)). +* ClickHouse client query param CTE added test. [#19762](https://github.com/ClickHouse/ClickHouse/pull/19762) ([Maksim Kita](https://github.com/kitaisreal)). * Correctly output infinite arguments for `formatReadableTimeDelta` function. In previous versions, there was implicit conversion to implementation specific integer value. [#19791](https://github.com/ClickHouse/ClickHouse/pull/19791) ([Alexey Milovidov](https://github.com/alexey-milovidov)). * `S3` table function now supports `auto` compression mode (autodetect). This closes [#18754](https://github.com/ClickHouse/ClickHouse/issues/18754). [#19793](https://github.com/ClickHouse/ClickHouse/pull/19793) ([Vladimir Chebotarev](https://github.com/excitoon)). * Set charset to utf8mb4 when interacting with remote MySQL servers. Fixes [#19795](https://github.com/ClickHouse/ClickHouse/issues/19795). [#19800](https://github.com/ClickHouse/ClickHouse/pull/19800) ([Alexey Milovidov](https://github.com/alexey-milovidov)). diff --git a/docs/changelogs/v21.5.1.6601-prestable.md b/docs/changelogs/v21.5.1.6601-prestable.md index 2d09ce6c20b..d64936fefce 100644 --- a/docs/changelogs/v21.5.1.6601-prestable.md +++ b/docs/changelogs/v21.5.1.6601-prestable.md @@ -34,7 +34,7 @@ * Allow to use CTE in VIEW definition. This closes [#22491](https://github.com/ClickHouse/ClickHouse/issues/22491). [#22657](https://github.com/ClickHouse/ClickHouse/pull/22657) ([Amos Bird](https://github.com/amosbird)). * Add metric to track how much time is spend during waiting for Buffer layer lock. [#22725](https://github.com/ClickHouse/ClickHouse/pull/22725) ([Azat Khuzhin](https://github.com/azat)). * Allow RBAC row policy via postgresql protocol. Closes [#22658](https://github.com/ClickHouse/ClickHouse/issues/22658). PostgreSQL protocol is enabled in configuration by default. [#22755](https://github.com/ClickHouse/ClickHouse/pull/22755) ([Kseniia Sumarokova](https://github.com/kssenii)). -* MaterializeMySQL (experimental feature). Make Clickhouse to be able to replicate MySQL databases containing views without failing. This is accomplished by ignoring the views. ... [#22760](https://github.com/ClickHouse/ClickHouse/pull/22760) ([Christian Frøystad](https://github.com/cfroystad)). +* MaterializeMySQL (experimental feature). Make ClickHouse to be able to replicate MySQL databases containing views without failing. This is accomplished by ignoring the views. ... [#22760](https://github.com/ClickHouse/ClickHouse/pull/22760) ([Christian Frøystad](https://github.com/cfroystad)). * `dateDiff` now works with `DateTime64` arguments (even for values outside of `DateTime` range) ... [#22931](https://github.com/ClickHouse/ClickHouse/pull/22931) ([Vasily Nemkov](https://github.com/Enmk)). * Set `background_fetches_pool_size` to 8 that is better for production usage with frequent small insertions or slow ZooKeeper cluster. [#22945](https://github.com/ClickHouse/ClickHouse/pull/22945) ([Alexey Milovidov](https://github.com/alexey-milovidov)). * Fix inactive_parts_to_throw_insert=0 with inactive_parts_to_delay_insert>0. [#22947](https://github.com/ClickHouse/ClickHouse/pull/22947) ([Azat Khuzhin](https://github.com/azat)). diff --git a/docs/changelogs/v22.1.1.2542-prestable.md b/docs/changelogs/v22.1.1.2542-prestable.md index e1e30e28ec6..c9071422949 100644 --- a/docs/changelogs/v22.1.1.2542-prestable.md +++ b/docs/changelogs/v22.1.1.2542-prestable.md @@ -84,7 +84,7 @@ #### Bug Fix * Quota limit was not reached, but the limit was exceeded. This PR fixes [#31174](https://github.com/ClickHouse/ClickHouse/issues/31174). [#31656](https://github.com/ClickHouse/ClickHouse/pull/31656) ([sunny](https://github.com/sunny19930321)). -* - Clickhouse Keeper handler should remove operation when response sent. [#32988](https://github.com/ClickHouse/ClickHouse/pull/32988) ([JackyWoo](https://github.com/JackyWoo)). +* - ClickHouse Keeper handler should remove operation when response sent. [#32988](https://github.com/ClickHouse/ClickHouse/pull/32988) ([JackyWoo](https://github.com/JackyWoo)). * Fix null pointer dereference in low cardinality data when deserializing LowCardinality data in the Native format. [#33021](https://github.com/ClickHouse/ClickHouse/pull/33021) ([Harry Lee](https://github.com/HarryLeeIBM)). * Specifically crafted input data for `Native` format may lead to reading uninitialized memory or crash. This is relevant if `clickhouse-server` is open for write access to adversary. [#33050](https://github.com/ClickHouse/ClickHouse/pull/33050) ([Heena Bansal](https://github.com/HeenaBansal2009)). @@ -196,7 +196,7 @@ * NO CL ENTRY: 'Update CHANGELOG.md'. [#32472](https://github.com/ClickHouse/ClickHouse/pull/32472) ([Rich Raposa](https://github.com/rfraposa)). * NO CL ENTRY: 'Revert "Split long tests into multiple checks"'. [#32514](https://github.com/ClickHouse/ClickHouse/pull/32514) ([alesapin](https://github.com/alesapin)). * NO CL ENTRY: 'Revert "Revert "Split long tests into multiple checks""'. [#32515](https://github.com/ClickHouse/ClickHouse/pull/32515) ([alesapin](https://github.com/alesapin)). -* NO CL ENTRY: 'blog post how to enable predictive capabilities in Clickhouse'. [#32768](https://github.com/ClickHouse/ClickHouse/pull/32768) ([Tom Risse](https://github.com/flickerbox-tom)). +* NO CL ENTRY: 'blog post how to enable predictive capabilities in ClickHouse'. [#32768](https://github.com/ClickHouse/ClickHouse/pull/32768) ([Tom Risse](https://github.com/flickerbox-tom)). * NO CL ENTRY: 'Revert "Fix build issue related to azure blob storage"'. [#32845](https://github.com/ClickHouse/ClickHouse/pull/32845) ([alesapin](https://github.com/alesapin)). * NO CL ENTRY: 'Revert "Dictionaries added Date32 type support"'. [#33053](https://github.com/ClickHouse/ClickHouse/pull/33053) ([Alexander Tokmakov](https://github.com/tavplubix)). * NO CL ENTRY: 'Updated Lawrence Berkeley National Lab stats'. [#33066](https://github.com/ClickHouse/ClickHouse/pull/33066) ([Michael Smitasin](https://github.com/michaelsmitasin)). diff --git a/docs/changelogs/v22.3.1.1262-prestable.md b/docs/changelogs/v22.3.1.1262-prestable.md index f47afd67021..03e81bd1808 100644 --- a/docs/changelogs/v22.3.1.1262-prestable.md +++ b/docs/changelogs/v22.3.1.1262-prestable.md @@ -65,7 +65,7 @@ * Add setting to lower column case when reading parquet/ORC file. [#35145](https://github.com/ClickHouse/ClickHouse/pull/35145) ([shuchaome](https://github.com/shuchaome)). * Do not retry non-rertiable errors. Closes [#35161](https://github.com/ClickHouse/ClickHouse/issues/35161). [#35172](https://github.com/ClickHouse/ClickHouse/pull/35172) ([Kseniia Sumarokova](https://github.com/kssenii)). * Added disk_name to system.part_log. [#35178](https://github.com/ClickHouse/ClickHouse/pull/35178) ([Artyom Yurkov](https://github.com/Varinara)). -* Currently,Clickhouse validates hosts defined under for URL and Remote Table functions. This PR extends the RemoteHostFilter to Mysql and PostgreSQL table functions. [#35191](https://github.com/ClickHouse/ClickHouse/pull/35191) ([Heena Bansal](https://github.com/HeenaBansal2009)). +* Currently,ClickHouse validates hosts defined under for URL and Remote Table functions. This PR extends the RemoteHostFilter to Mysql and PostgreSQL table functions. [#35191](https://github.com/ClickHouse/ClickHouse/pull/35191) ([Heena Bansal](https://github.com/HeenaBansal2009)). * Sometimes it is not enough for us to distinguish queries hierachy only by is_initial_query in system.query_log and system.processes. So distributed_depth is needed. [#35207](https://github.com/ClickHouse/ClickHouse/pull/35207) ([李扬](https://github.com/taiyang-li)). * Support test mode for clickhouse-local. [#35264](https://github.com/ClickHouse/ClickHouse/pull/35264) ([Kseniia Sumarokova](https://github.com/kssenii)). * Return const for function getMacro if not in distributed query. Close [#34727](https://github.com/ClickHouse/ClickHouse/issues/34727). [#35289](https://github.com/ClickHouse/ClickHouse/pull/35289) ([李扬](https://github.com/taiyang-li)). diff --git a/docs/en/development/contrib.md b/docs/en/development/contrib.md index 7713c397e46..3936b613bcb 100644 --- a/docs/en/development/contrib.md +++ b/docs/en/development/contrib.md @@ -97,7 +97,7 @@ SELECT library_name, license_type, license_path FROM system.licenses ORDER BY li ## Adding new third-party libraries and maintaining patches in third-party libraries {#adding-third-party-libraries} 1. Each third-party library must reside in a dedicated directory under the `contrib/` directory of the ClickHouse repository. Avoid dumps/copies of external code, instead use Git submodule feature to pull third-party code from an external upstream repository. -2. Submodules are listed in `.gitmodule`. If the external library can be used as-is, you may reference the upstream repository directly. Otherwise, i.e. the external library requires patching/customization, create a fork of the official repository in the [Clickhouse organization in GitHub](https://github.com/ClickHouse). +2. Submodules are listed in `.gitmodule`. If the external library can be used as-is, you may reference the upstream repository directly. Otherwise, i.e. the external library requires patching/customization, create a fork of the official repository in the [ClickHouse organization in GitHub](https://github.com/ClickHouse). 3. In the latter case, create a branch with `clickhouse/` prefix from the branch you want to integrate, e.g. `clickhouse/master` (for `master`) or `clickhouse/release/vX.Y.Z` (for a `release/vX.Y.Z` tag). The purpose of this branch is to isolate customization of the library from upstream work. For example, pulls from the upstream repository into the fork will leave all `clickhouse/` branches unaffected. Submodules in `contrib/` must only track `clickhouse/` branches of forked third-party repositories. 4. To patch a fork of a third-party library, create a dedicated branch with `clickhouse/` prefix in the fork, e.g. `clickhouse/fix-some-desaster`. Finally, merge the patch branch into the custom tracking branch (e.g. `clickhouse/master` or `clickhouse/release/vX.Y.Z`) using a PR. 5. Always create patches of third-party libraries with the official repository in mind. Once a PR of a patch branch to the `clickhouse/` branch in the fork repository is done and the submodule version in ClickHouse official repository is bumped, consider opening another PR from the patch branch to the upstream library repository. This ensures, that 1) the contribution has more than a single use case and importance, 2) others will also benefit from it, 3) the change will not remain a maintenance burden solely on ClickHouse developers. diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 5baeac80748..0950568cc82 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -325,14 +325,14 @@ clickhouse-keeper-converter --zookeeper-logs-dir /var/lib/zookeeper/version-2 -- ## Recovering after losing quorum -Because Clickhouse Keeper uses Raft it can tolerate certain amount of node crashes depending on the cluster size. \ +Because ClickHouse Keeper uses Raft it can tolerate certain amount of node crashes depending on the cluster size. \ E.g. for a 3-node cluster, it will continue working correctly if only 1 node crashes. Cluster configuration can be dynamically configured but there are some limitations. Reconfiguration relies on Raft also so to add/remove a node from the cluster you need to have a quorum. If you lose too many nodes in your cluster at the same time without any chance of starting them again, Raft will stop working and not allow you to reconfigure your cluster using the conventional way. -Nevertheless, Clickhouse Keeper has a recovery mode which allows you to forcefully reconfigure your cluster with only 1 node. +Nevertheless, ClickHouse Keeper has a recovery mode which allows you to forcefully reconfigure your cluster with only 1 node. This should be done only as your last resort if you cannot start your nodes again, or start a new instance on the same endpoint. Important things to note before continuing: diff --git a/docs/en/operations/system-tables/replication_queue.md b/docs/en/operations/system-tables/replication_queue.md index ba2eac1a854..cb22345c3a2 100644 --- a/docs/en/operations/system-tables/replication_queue.md +++ b/docs/en/operations/system-tables/replication_queue.md @@ -1,6 +1,6 @@ # replication_queue -Contains information about tasks from replication queues stored in Clickhouse Keeper, or ZooKeeper, for tables in the `ReplicatedMergeTree` family. +Contains information about tasks from replication queues stored in ClickHouse Keeper, or ZooKeeper, for tables in the `ReplicatedMergeTree` family. Columns: diff --git a/docs/en/operations/tips.md b/docs/en/operations/tips.md index 836b61d4954..f364bc85088 100644 --- a/docs/en/operations/tips.md +++ b/docs/en/operations/tips.md @@ -274,6 +274,6 @@ end script ## Antivirus software {#antivirus-software} -If you use antivirus software configure it to skip folders with Clickhouse datafiles (`/var/lib/clickhouse`) otherwise performance may be reduced and you may experience unexpected errors during data ingestion and background merges. +If you use antivirus software configure it to skip folders with ClickHouse datafiles (`/var/lib/clickhouse`) otherwise performance may be reduced and you may experience unexpected errors during data ingestion and background merges. [Original article](https://clickhouse.com/docs/en/operations/tips/) diff --git a/docs/ru/faq/index.md b/docs/ru/faq/index.md index d362035284d..1d1dc7df819 100644 --- a/docs/ru/faq/index.md +++ b/docs/ru/faq/index.md @@ -39,6 +39,6 @@ Question candidates: - How to kill a process (query) in ClickHouse? - How to implement pivot (like in pandas)? - How to remove the default ClickHouse user through users.d? -- Importing MySQL dump to Clickhouse +- Importing MySQL dump to ClickHouse - Window function workarounds (row\_number, lag/lead, running diff/sum/average) ##} diff --git a/docs/ru/sql-reference/data-types/lowcardinality.md b/docs/ru/sql-reference/data-types/lowcardinality.md index 14a9e923ac8..2b9abd0ab2d 100644 --- a/docs/ru/sql-reference/data-types/lowcardinality.md +++ b/docs/ru/sql-reference/data-types/lowcardinality.md @@ -55,5 +55,5 @@ ORDER BY id ## Смотрите также -- [Reducing Clickhouse Storage Cost with the Low Cardinality Type – Lessons from an Instana Engineer](https://www.instana.com/blog/reducing-clickhouse-storage-cost-with-the-low-cardinality-type-lessons-from-an-instana-engineer/). +- [Reducing ClickHouse Storage Cost with the Low Cardinality Type – Lessons from an Instana Engineer](https://www.instana.com/blog/reducing-clickhouse-storage-cost-with-the-low-cardinality-type-lessons-from-an-instana-engineer/). - [String Optimization (video presentation in Russian)](https://youtu.be/rqf-ILRgBdY?list=PL0Z2YDlm0b3iwXCpEFiOOYmwXzVmjJfEt). [Slides in English](https://github.com/ClickHouse/clickhouse-presentations/raw/master/meetup19/string_optimization.pdf). diff --git a/docs/ru/sql-reference/functions/encryption-functions.md b/docs/ru/sql-reference/functions/encryption-functions.md index 2eaad0e1930..2760b04f700 100644 --- a/docs/ru/sql-reference/functions/encryption-functions.md +++ b/docs/ru/sql-reference/functions/encryption-functions.md @@ -11,7 +11,7 @@ sidebar_label: "Функции для шифрования" Длина инициализирующего вектора всегда 16 байт (лишние байты игнорируются). -Обратите внимание, что до версии Clickhouse 21.1 эти функции работали медленно. +Обратите внимание, что до версии ClickHouse 21.1 эти функции работали медленно. ## encrypt {#encrypt} From ba0761fb6a9334ce18e20b972757765a79baab47 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Fri, 17 Jun 2022 15:26:59 +0200 Subject: [PATCH 178/204] More details on matching with Unicode --- .../functions/string-functions.md | 8 +++--- .../functions/string-search-functions.md | 26 ++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/en/sql-reference/functions/string-functions.md b/docs/en/sql-reference/functions/string-functions.md index 0c1c738f663..0ab105c79d6 100644 --- a/docs/en/sql-reference/functions/string-functions.md +++ b/docs/en/sql-reference/functions/string-functions.md @@ -273,16 +273,16 @@ Converts ASCII Latin symbols in a string to uppercase. ## lowerUTF8 {#lowerutf8} Converts a string to lowercase, assuming the string contains a set of bytes that make up a UTF-8 encoded text. -It does not detect the language. So for Turkish the result might not be exactly correct. +It does not detect the language. E.g. for Turkish the result might not be exactly correct (i/İ vs. i/I). If the length of the UTF-8 byte sequence is different for upper and lower case of a code point, the result may be incorrect for this code point. -If the string contains a set of bytes that is not UTF-8, then the behavior is undefined. +If the string contains a sequence of bytes that are not valid UTF-8, then the behavior is undefined. ## upperUTF8 {#upperutf8} Converts a string to uppercase, assuming the string contains a set of bytes that make up a UTF-8 encoded text. -It does not detect the language. So for Turkish the result might not be exactly correct. +It does not detect the language. E.g. for Turkish the result might not be exactly correct (i/İ vs. i/I). If the length of the UTF-8 byte sequence is different for upper and lower case of a code point, the result may be incorrect for this code point. -If the string contains a set of bytes that is not UTF-8, then the behavior is undefined. +If the string contains a sequence of bytes that are not valid UTF-8, then the behavior is undefined. ## isValidUTF8 {#isvalidutf8} diff --git a/docs/en/sql-reference/functions/string-search-functions.md b/docs/en/sql-reference/functions/string-search-functions.md index 0ecf980c163..fd9022b6549 100644 --- a/docs/en/sql-reference/functions/string-search-functions.md +++ b/docs/en/sql-reference/functions/string-search-functions.md @@ -7,7 +7,7 @@ sidebar_label: For Searching in Strings The search is case-sensitive by default in all these functions. There are separate variants for case insensitive search. -:::note +:::note Functions for [replacing](../../sql-reference/functions/string-replace-functions.md) and [other manipulations with strings](../../sql-reference/functions/string-functions.md) are described separately. ::: @@ -31,7 +31,7 @@ position(needle IN haystack) Alias: `locate(haystack, needle[, start_pos])`. -:::note +:::note Syntax of `position(needle IN haystack)` provides SQL-compatibility, the function works the same way as to `position(haystack, needle)`. ::: @@ -344,7 +344,7 @@ Returns 1, if at least one string needlei matches the string `haystac For a case-insensitive search or/and in UTF-8 format use functions `multiSearchAnyCaseInsensitive, multiSearchAnyUTF8, multiSearchAnyCaseInsensitiveUTF8`. -:::note +:::note In all `multiSearch*` functions the number of needles should be less than 28 because of implementation specification. ::: @@ -354,7 +354,9 @@ Checks whether the string matches the regular expression `pattern` in `re2` synt Returns 0 if it does not match, or 1 if it matches. -Matching is based on UTF-8, e.g. `.` matches the Unicode code point `¥` which is stored in UTF-8 using two bytes. The regular expression must not contain null bytes. +Matching is based on UTF-8, e.g. `.` matches the Unicode code point `¥` which is represented in UTF-8 using two bytes. The regular expression must not contain null bytes. +If the haystack or pattern contain a sequence of bytes that are not valid UTF-8, then the behavior is undefined. +No automatic Unicode normalization is performed, if you need it you can use the [normalizeUTF8*()](https://clickhouse.com/docs/en/sql-reference/functions/string-functions/) functions for that. For patterns to search for substrings in a string, it is better to use LIKE or ‘position’, since they work much faster. @@ -362,7 +364,7 @@ For patterns to search for substrings in a string, it is better to use LIKE or The same as `match`, but returns 0 if none of the regular expressions are matched and 1 if any of the patterns matches. It uses [hyperscan](https://github.com/intel/hyperscan) library. For patterns to search substrings in a string, it is better to use `multiSearchAny` since it works much faster. -:::note +:::note The length of any of the `haystack` string must be less than 232 bytes otherwise the exception is thrown. This restriction takes place because of hyperscan API. ::: @@ -386,11 +388,11 @@ The same as `multiFuzzyMatchAny`, but returns any index that matches the haystac The same as `multiFuzzyMatchAny`, but returns the array of all indices in any order that match the haystack within a constant edit distance. -:::note +:::note `multiFuzzyMatch*` functions do not support UTF-8 regular expressions, and such expressions are treated as bytes because of hyperscan restriction. ::: -:::note +:::note To turn off all functions that use hyperscan, use setting `SET allow_hyperscan = 0;`. ::: @@ -406,7 +408,7 @@ Extracts all the fragments of a string using a regular expression. If ‘haystac Matches all groups of the `haystack` string using the `pattern` regular expression. Returns an array of arrays, where the first array includes all fragments matching the first group, the second array - matching the second group, etc. -:::note +:::note `extractAllGroupsHorizontal` function is slower than [extractAllGroupsVertical](#extractallgroups-vertical). ::: @@ -499,7 +501,9 @@ The regular expression can contain the metasymbols `%` and `_`. Use the backslash (`\`) for escaping metasymbols. See the note on escaping in the description of the ‘match’ function. -Matching is based on UTF-8, e.g. `_` matches the Unicode code point `¥` which is stored in UTF-8 using two bytes. +Matching is based on UTF-8, e.g. `_` matches the Unicode code point `¥` which is represented in UTF-8 using two bytes. +If the haystack or pattern contain a sequence of bytes that are not valid UTF-8, then the behavior is undefined. +No automatic Unicode normalization is performed, if you need it you can use the [normalizeUTF8*()](https://clickhouse.com/docs/en/sql-reference/functions/string-functions/) functions for that. For regular expressions like `%needle%`, the code is more optimal and works as fast as the `position` function. For other regular expressions, the code is the same as for the ‘match’ function. @@ -512,6 +516,8 @@ The same thing as ‘like’, but negative. Case insensitive variant of [like](https://clickhouse.com/docs/en/sql-reference/functions/string-search-functions/#function-like) function. You can use `ILIKE` operator instead of the `ilike` function. +The function ignores the language, e.g. for Turkish (i/İ), the result might be incorrect. + **Syntax** ``` sql @@ -580,7 +586,7 @@ Same as `ngramDistance` but calculates the non-symmetric difference between `nee For case-insensitive search or/and in UTF-8 format use functions `ngramSearchCaseInsensitive, ngramSearchUTF8, ngramSearchCaseInsensitiveUTF8`. -:::note +:::note For UTF-8 case we use 3-gram distance. All these are not perfectly fair n-gram distances. We use 2-byte hashes to hash n-grams and then calculate the (non-)symmetric difference between these hash tables – collisions may occur. With UTF-8 case-insensitive format we do not use fair `tolower` function – we zero the 5-th bit (starting from zero) of each codepoint byte and first bit of zeroth byte if bytes more than one – this works for Latin and mostly for all Cyrillic letters. ::: From 828946ccf5daa02324a9367af3c51a585fda95ef Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 17 Jun 2022 16:44:52 +0300 Subject: [PATCH 179/204] Update 02320_mapped_array_witn_const_nullable.sql --- .../0_stateless/02320_mapped_array_witn_const_nullable.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql b/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql index 48f34fa8fc0..08651590c76 100644 --- a/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql +++ b/tests/queries/0_stateless/02320_mapped_array_witn_const_nullable.sql @@ -1,3 +1,5 @@ +-- Tags: no-backward-compatibility-check + select arrayMap(x -> toNullable(1), range(number)) from numbers(3); select arrayFilter(x -> toNullable(1), range(number)) from numbers(3); select arrayMap(x -> toNullable(0), range(number)) from numbers(3); From 965b706350c6006cdc4251f1ac5a5e99f3893315 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Fri, 17 Jun 2022 17:43:42 +0200 Subject: [PATCH 180/204] SQLUserDefinedFunctionsLoader allow symlinks in user_defined directory --- .../UserDefinedSQLObjectsLoader.cpp | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Interpreters/UserDefinedSQLObjectsLoader.cpp b/src/Interpreters/UserDefinedSQLObjectsLoader.cpp index c38df5b855f..75b91f3a817 100644 --- a/src/Interpreters/UserDefinedSQLObjectsLoader.cpp +++ b/src/Interpreters/UserDefinedSQLObjectsLoader.cpp @@ -86,28 +86,33 @@ void UserDefinedSQLObjectsLoader::loadObjects(ContextPtr context) if (unlikely(!enable_persistence)) return; - LOG_DEBUG(log, "loading user defined objects"); + LOG_DEBUG(log, "Loading user defined objects"); String dir_path = context->getUserDefinedPath(); Poco::DirectoryIterator dir_end; for (Poco::DirectoryIterator it(dir_path); it != dir_end; ++it) { - if (it->isLink()) + if (it->isDirectory()) continue; - const auto & file_name = it.name(); + const std::string & file_name = it.name(); /// For '.svn', '.gitignore' directory and similar. if (file_name.at(0) == '.') continue; - if (!it->isDirectory() && endsWith(file_name, ".sql")) - { - std::string_view object_name = file_name; - object_name.remove_suffix(strlen(".sql")); - object_name.remove_prefix(strlen("function_")); - loadUserDefinedObject(context, UserDefinedSQLObjectType::Function, object_name, dir_path + it.name()); - } + if (!startsWith(file_name, "function_") || !endsWith(file_name, ".sql")) + continue; + + std::string_view object_name = file_name; + + object_name.remove_prefix(strlen("function_")); + object_name.remove_suffix(strlen(".sql")); + + if (object_name.empty()) + continue; + + loadUserDefinedObject(context, UserDefinedSQLObjectType::Function, object_name, dir_path + it.name()); } } From 6b748f2193dd5546aef2f4557af3460a65adee29 Mon Sep 17 00:00:00 2001 From: Maksim Kita Date: Fri, 17 Jun 2022 16:44:09 +0200 Subject: [PATCH 181/204] AArch64 enable trace collection --- programs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index b013ba9ee05..476725c5627 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -1515,7 +1515,7 @@ int Server::main(const std::vector & /*args*/) /// Init trace collector only after trace_log system table was created /// Disable it if we collect test coverage information, because it will work extremely slow. -#if USE_UNWIND && !WITH_COVERAGE && defined(__x86_64__) +#if USE_UNWIND && !WITH_COVERAGE /// Profilers cannot work reliably with any other libunwind or without PHDR cache. if (hasPHDRCache()) { From 85dd3afa2b4eb89a730ea2a518985816072f73d9 Mon Sep 17 00:00:00 2001 From: Sergei Trifonov Date: Fri, 17 Jun 2022 18:02:03 +0200 Subject: [PATCH 182/204] more improvements --- utils/trace-visualizer/css/d3-gantt.css | 7 ++- utils/trace-visualizer/index.html | 59 ++++++++++++++++++------- utils/trace-visualizer/js/d3-gantt.js | 53 +++++++++++++--------- 3 files changed, 79 insertions(+), 40 deletions(-) diff --git a/utils/trace-visualizer/css/d3-gantt.css b/utils/trace-visualizer/css/d3-gantt.css index 31da093dd2d..3e3150a02f0 100644 --- a/utils/trace-visualizer/css/d3-gantt.css +++ b/utils/trace-visualizer/css/d3-gantt.css @@ -8,7 +8,6 @@ } rect.zoom-panel { - /*cursor: ew-resize;*/ fill: none; pointer-events: all; } @@ -20,18 +19,18 @@ rect.zoom-panel { } .axis.y { - font-size: 16px; + font-size: 9px; cursor: ns-resize; } .axis.x { - font-size: 16px; + font-size: 9px; } #ruler { text-anchor: middle; alignment-baseline: before-edge; - font-size: 16px; + font-size: 9px; font-family: sans-serif; pointer-events: none; } diff --git a/utils/trace-visualizer/index.html b/utils/trace-visualizer/index.html index ac39df1f98f..cec88e587d3 100644 --- a/utils/trace-visualizer/index.html +++ b/utils/trace-visualizer/index.html @@ -14,12 +14,22 @@ -
-
- + +
+ +
+