From af1cdb4aa2e74e995cb155d6fda4f9069321f387 Mon Sep 17 00:00:00 2001 From: maks-buren630501 Date: Tue, 30 Aug 2022 15:13:22 +0300 Subject: [PATCH 001/445] Fix bug with materialized_view and postgresql replication --- src/Processors/Transforms/buildPushingToViewsChain.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Processors/Transforms/buildPushingToViewsChain.cpp b/src/Processors/Transforms/buildPushingToViewsChain.cpp index d71d6901cee..8001bd7607a 100644 --- a/src/Processors/Transforms/buildPushingToViewsChain.cpp +++ b/src/Processors/Transforms/buildPushingToViewsChain.cpp @@ -340,7 +340,8 @@ Chain buildPushingToViewsChain( chains.emplace_back(std::move(out)); /// Add the view to the query access info so it can appear in system.query_log - if (!no_destination) + /// hasQueryContext - for materialized tables with background replication process query context is not added + if (!no_destination && context->hasQueryContext()) { context->getQueryContext()->addQueryAccessInfo( backQuoteIfNeed(database_table.getDatabaseName()), views_data->views.back().runtime_stats->target_name, {}, "", database_table.getFullTableName()); @@ -698,7 +699,6 @@ IProcessor::Status FinalizingViewsTransform::prepare() output.finish(); return Status::Finished; } - return Status::NeedData; } From 64df6ca5209949fe80ca798eb82dc386d5e74652 Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 3 Jan 2023 19:22:31 +0000 Subject: [PATCH 002/445] Split stress test and backward compatibility check --- docker/test/{stress/stress => ci} | 0 docker/test/stress/Dockerfile | 1 - docker/test/stress/run.sh | 251 +--------- docker/test/upgrade/Dockerfile | 31 ++ docker/test/upgrade/run.sh | 462 ++++++++++++++++++ tests/ci/stress.py | 305 ++++++++++++ tests/ci/stress_check.py | 8 +- tests/ci/upgrade_check.py | 4 + tests/clickhouse-test | 45 +- .../0_stateless/00061_merge_tree_alter.sql | 2 +- ..._replace_partition_from_table_zookeeper.sh | 2 +- ...ost_part_and_alive_part_zookeeper_long.sql | 2 +- .../0_stateless/00942_dataparts_500.sh | 2 +- ...tem_parts_race_condition_drop_zookeeper.sh | 2 +- ...9_parallel_alter_detach_table_zookeeper.sh | 2 +- .../01111_create_drop_replicated_db_stress.sh | 2 +- .../0_stateless/01191_rename_dictionary.sql | 2 +- ...18_long_unsuccessful_mutation_zookeeper.sh | 2 +- .../01378_alter_rename_with_ttl_zookeeper.sql | 2 +- .../0_stateless/01391_join_on_dict_crash.sql | 2 +- .../01555_system_distribution_queue_mask.sql | 2 +- .../01576_alter_low_cardinality_and_select.sh | 2 +- ...ch_patition_with_macro_in_zk_path_long.sql | 2 +- .../0_stateless/01780_column_sparse_alter.sql | 2 +- .../02022_storage_filelog_one_file.sh | 2 +- .../02025_storage_filelog_virtual_col.sh | 2 +- .../0_stateless/02067_lost_part_s3.sql | 2 +- ...2_create_table_without_columns_metadata.sh | 2 +- .../0_stateless/02242_delete_user_race.sh | 2 +- .../0_stateless/02243_drop_user_grant_race.sh | 2 +- tests/queries/0_stateless/02293_hashid.sql | 2 +- .../02302_join_auto_lc_nullable_bug.sql | 2 +- .../02306_window_move_row_number_fix.sql | 2 +- .../02313_cross_join_dup_col_names.sql | 2 +- .../02315_pmj_union_ubsan_35857.sql | 2 +- ...2316_cast_to_ip_address_default_column.sql | 3 - .../02316_const_string_intersact.sql | 2 +- ...02320_mapped_array_witn_const_nullable.sql | 2 +- .../02332_dist_insert_send_logs_level.sh | 2 +- ...45_partial_sort_transform_optimization.sql | 2 +- tests/queries/0_stateless/02354_annoy.sh | 2 +- .../0_stateless/02363_mapupdate_improve.sql | 2 +- .../02366_direct_dictionary_dict_has.sql | 2 +- .../0_stateless/02366_with_fill_date.sql | 2 +- .../02381_compress_marks_and_primary_key.sql | 2 +- ...397_system_parts_race_condition_drop_rm.sh | 2 +- .../02429_low_cardinality_trash.sh | 2 +- .../02450_kill_distributed_query_deadlock.sh | 2 +- 48 files changed, 873 insertions(+), 313 deletions(-) rename docker/test/{stress/stress => ci} (100%) create mode 100644 docker/test/upgrade/Dockerfile create mode 100644 docker/test/upgrade/run.sh create mode 100755 tests/ci/stress.py create mode 100644 tests/ci/upgrade_check.py diff --git a/docker/test/stress/stress b/docker/test/ci similarity index 100% rename from docker/test/stress/stress rename to docker/test/ci diff --git a/docker/test/stress/Dockerfile b/docker/test/stress/Dockerfile index 393508fd551..2778b63774d 100644 --- a/docker/test/stress/Dockerfile +++ b/docker/test/stress/Dockerfile @@ -24,7 +24,6 @@ RUN apt-get update -y \ llvm-9 \ brotli -COPY ./stress /stress COPY run.sh / ENV DATASETS="hits visits" diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 41245013a4a..c99de2b93e5 100644 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -28,6 +28,7 @@ function configure() /usr/share/clickhouse-test/config/install.sh # we mount tests folder from repo to /usr/share + ln -s /usr/share/clickhouse-test/ci/stress.py /usr/bin/stress ln -s /usr/share/clickhouse-test/clickhouse-test /usr/bin/clickhouse-test ln -s /usr/share/clickhouse-test/ci/download_release_packages.py /usr/bin/download_release_packages ln -s /usr/share/clickhouse-test/ci/get_previous_release_tag.py /usr/bin/get_previous_release_tag @@ -278,7 +279,7 @@ sudo chgrp clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_defau start -./stress --hung-check --drop-databases --output-folder test_output --skip-func-tests "$SKIP_TESTS_OPTION" --global-time-limit 1200 \ +stress --hung-check --drop-databases --output-folder test_output --skip-func-tests "$SKIP_TESTS_OPTION" --global-time-limit 1200 \ && echo -e 'Test script exit code\tOK' >> /test_output/test_results.tsv \ || echo -e 'Test script failed\tFAIL' >> /test_output/test_results.tsv @@ -292,14 +293,19 @@ unset "${!THREAD_@}" start clickhouse-client --query "SELECT 'Server successfully started', 'OK'" >> /test_output/test_results.tsv \ - || (echo -e 'Server failed to start (see application_errors.txt and clickhouse-server.clean.log)\tFAIL' >> /test_output/test_results.tsv \ + || (echo -e 'Server failed to start (see application_errors.txt and clickhouse-server.final.log)\tFAIL' >> /test_output/test_results.tsv \ && grep -a ".*Application" /var/log/clickhouse-server/clickhouse-server.log > /test_output/application_errors.txt) +# Remove file application_errors.txt if it's empty +[ -s /test_output/application_errors.txt ] || rm /test_output/application_errors.txt + stop [ -f /var/log/clickhouse-server/clickhouse-server.log ] || echo -e "Server log does not exist\tFAIL" [ -f /var/log/clickhouse-server/stderr.log ] || echo -e "Stderr log does not exist\tFAIL" +mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.final.log + # Grep logs for sanitizer asserts, crashes and other critical errors # Sanitizer asserts @@ -312,12 +318,12 @@ rm -f /test_output/tmp # OOM zgrep -Fa " Application: Child process was terminated by signal 9" /var/log/clickhouse-server/clickhouse-server*.log > /dev/null \ - && echo -e 'OOM killer (or signal 9) in clickhouse-server.log\tFAIL' >> /test_output/test_results.tsv \ - || echo -e 'No OOM messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv + && echo -e 'OOM killer (or signal 9) in server logs\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No OOM messages in server logs\tOK' >> /test_output/test_results.tsv # Logical errors zgrep -Fa "Code: 49, e.displayText() = DB::Exception:" /var/log/clickhouse-server/clickhouse-server*.log > /test_output/logical_errors.txt \ - && echo -e 'Logical error thrown (see clickhouse-server.log or logical_errors.txt)\tFAIL' >> /test_output/test_results.tsv \ + && echo -e 'Logical error thrown (see server logs or logical_errors.txt)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'No logical errors\tOK' >> /test_output/test_results.tsv # Remove file logical_errors.txt if it's empty @@ -325,7 +331,7 @@ zgrep -Fa "Code: 49, e.displayText() = DB::Exception:" /var/log/clickhouse-serve # No such key errors zgrep -Ea "Code: 499.*The specified key does not exist" /var/log/clickhouse-server/clickhouse-server*.log > /test_output/no_such_key_errors.txt \ - && echo -e 'S3_ERROR No such key thrown (see clickhouse-server.log or no_such_key_errors.txt)\tFAIL' >> /test_output/test_results.tsv \ + && echo -e 'S3_ERROR No such key thrown (see server logs or no_such_key_errors.txt)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'No lost s3 keys\tOK' >> /test_output/test_results.tsv # Remove file no_such_key_errors.txt if it's empty @@ -333,13 +339,13 @@ zgrep -Ea "Code: 499.*The specified key does not exist" /var/log/clickhouse-serv # Crash zgrep -Fa "########################################" /var/log/clickhouse-server/clickhouse-server*.log > /dev/null \ - && echo -e 'Killed by signal (in clickhouse-server.log)\tFAIL' >> /test_output/test_results.tsv \ + && echo -e 'Killed by signal (in server logs)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'Not crashed\tOK' >> /test_output/test_results.tsv # It also checks for crash without stacktrace (printed by watchdog) zgrep -Fa " " /var/log/clickhouse-server/clickhouse-server*.log > /test_output/fatal_messages.txt \ - && echo -e 'Fatal message in clickhouse-server.log (see fatal_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ - || echo -e 'No fatal messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv + && echo -e 'Fatal message in server logs (see fatal_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No fatal messages in server logs\tOK' >> /test_output/test_results.tsv # Remove file fatal_messages.txt if it's empty [ -s /test_output/fatal_messages.txt ] || rm /test_output/fatal_messages.txt @@ -350,229 +356,12 @@ zgrep -Fa "########################################" /test_output/* > /dev/null zgrep -Fa " received signal " /test_output/gdb.log > /dev/null \ && echo -e 'Found signal in gdb.log\tFAIL' >> /test_output/test_results.tsv -if [ "$DISABLE_BC_CHECK" -ne "1" ]; then - echo -e "Backward compatibility check\n" +for table in query_log trace_log +do + clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | pigz > /test_output/$table.tsv.gz ||: +done - echo "Get previous release tag" - previous_release_tag=$(clickhouse-client --version | grep -o "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | get_previous_release_tag) - echo $previous_release_tag - - echo "Clone previous release repository" - git clone https://github.com/ClickHouse/ClickHouse.git --no-tags --progress --branch=$previous_release_tag --no-recurse-submodules --depth=1 previous_release_repository - - echo "Download clickhouse-server from the previous release" - mkdir previous_release_package_folder - - echo $previous_release_tag | download_release_packages && echo -e 'Download script exit code\tOK' >> /test_output/test_results.tsv \ - || echo -e 'Download script failed\tFAIL' >> /test_output/test_results.tsv - - mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.clean.log - for table in query_log trace_log - do - clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | pigz > /test_output/$table.tsv.gz ||: - done - - tar -chf /test_output/coordination.tar /var/lib/clickhouse/coordination ||: - - # Check if we cloned previous release repository successfully - if ! [ "$(ls -A previous_release_repository/tests/queries)" ] - then - echo -e "Backward compatibility check: Failed to clone previous release tests\tFAIL" >> /test_output/test_results.tsv - elif ! [ "$(ls -A previous_release_package_folder/clickhouse-common-static_*.deb && ls -A previous_release_package_folder/clickhouse-server_*.deb)" ] - then - echo -e "Backward compatibility check: Failed to download previous release packages\tFAIL" >> /test_output/test_results.tsv - else - echo -e "Successfully cloned previous release tests\tOK" >> /test_output/test_results.tsv - echo -e "Successfully downloaded previous release packages\tOK" >> /test_output/test_results.tsv - - # Uninstall current packages - dpkg --remove clickhouse-client - dpkg --remove clickhouse-server - dpkg --remove clickhouse-common-static-dbg - dpkg --remove clickhouse-common-static - - rm -rf /var/lib/clickhouse/* - - # Make BC check more funny by forcing Ordinary engine for system database - mkdir /var/lib/clickhouse/metadata - echo "ATTACH DATABASE system ENGINE=Ordinary" > /var/lib/clickhouse/metadata/system.sql - - # Install previous release packages - install_packages previous_release_package_folder - - # Start server from previous release - # Previous version may not be ready for fault injections - export ZOOKEEPER_FAULT_INJECTION=0 - configure - - # Avoid "Setting s3_check_objects_after_upload is neither a builtin setting..." - rm -f /etc/clickhouse-server/users.d/enable_blobs_check.xml ||: - rm -f /etc/clickhouse-server/users.d/marks.xml ||: - - # Remove s3 related configs to avoid "there is no disk type `cache`" - rm -f /etc/clickhouse-server/config.d/storage_conf.xml ||: - rm -f /etc/clickhouse-server/config.d/azure_storage_conf.xml ||: - - # Turn on after 22.12 - rm -f /etc/clickhouse-server/config.d/compressed_marks_and_index.xml ||: - # it uses recently introduced settings which previous versions may not have - rm -f /etc/clickhouse-server/users.d/insert_keeper_retries.xml ||: - - start - - clickhouse-client --query="SELECT 'Server version: ', version()" - - # Install new package before running stress test because we should use new - # clickhouse-client and new clickhouse-test. - # - # But we should leave old binary in /usr/bin/ and debug symbols in - # /usr/lib/debug/usr/bin (if any) for gdb and internal DWARF parser, so it - # will print sane stacktraces and also to avoid possible crashes. - # - # FIXME: those files can be extracted directly from debian package, but - # actually better solution will be to use different PATH instead of playing - # games with files from packages. - mv /usr/bin/clickhouse previous_release_package_folder/ - mv /usr/lib/debug/usr/bin/clickhouse.debug previous_release_package_folder/ - install_packages package_folder - mv /usr/bin/clickhouse package_folder/ - mv /usr/lib/debug/usr/bin/clickhouse.debug package_folder/ - mv previous_release_package_folder/clickhouse /usr/bin/ - mv previous_release_package_folder/clickhouse.debug /usr/lib/debug/usr/bin/clickhouse.debug - - mkdir tmp_stress_output - - ./stress --test-cmd="/usr/bin/clickhouse-test --queries=\"previous_release_repository/tests/queries\"" --backward-compatibility-check --output-folder tmp_stress_output --global-time-limit=1200 \ - && echo -e 'Backward compatibility check: Test script exit code\tOK' >> /test_output/test_results.tsv \ - || echo -e 'Backward compatibility check: Test script failed\tFAIL' >> /test_output/test_results.tsv - rm -rf tmp_stress_output - - # We experienced deadlocks in this command in very rare cases. Let's debug it: - timeout 10m clickhouse-client --query="SELECT 'Tables count:', count() FROM system.tables" || - ( - echo "thread apply all backtrace (on select tables count)" >> /test_output/gdb.log - timeout 30m gdb -batch -ex 'thread apply all backtrace' -p "$(cat /var/run/clickhouse-server/clickhouse-server.pid)" | ts '%Y-%m-%d %H:%M:%S' >> /test_output/gdb.log - clickhouse stop --force - ) - - stop 1 - mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.backward.stress.log - - # Start new server - mv package_folder/clickhouse /usr/bin/ - mv package_folder/clickhouse.debug /usr/lib/debug/usr/bin/clickhouse.debug - # Disable fault injections on start (we don't test them here, and it can lead to tons of requests in case of huge number of tables). - export ZOOKEEPER_FAULT_INJECTION=0 - configure - start 500 - clickhouse-client --query "SELECT 'Backward compatibility check: Server successfully started', 'OK'" >> /test_output/test_results.tsv \ - || (echo -e 'Backward compatibility check: Server failed to start\tFAIL' >> /test_output/test_results.tsv \ - && grep -a ".*Application" /var/log/clickhouse-server/clickhouse-server.log >> /test_output/bc_check_application_errors.txt) - - clickhouse-client --query="SELECT 'Server version: ', version()" - - # Let the server run for a while before checking log. - sleep 60 - - stop - mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.backward.dirty.log - - # Error messages (we should ignore some errors) - # FIXME https://github.com/ClickHouse/ClickHouse/issues/38643 ("Unknown index: idx.") - # FIXME https://github.com/ClickHouse/ClickHouse/issues/39174 ("Cannot parse string 'Hello' as UInt64") - # FIXME Not sure if it's expected, but some tests from BC check may not be finished yet when we restarting server. - # Let's just ignore all errors from queries ("} TCPHandler: Code:", "} executeQuery: Code:") - # FIXME https://github.com/ClickHouse/ClickHouse/issues/39197 ("Missing columns: 'v3' while processing query: 'v3, k, v1, v2, p'") - # NOTE Incompatibility was introduced in https://github.com/ClickHouse/ClickHouse/pull/39263, it's expected - # ("This engine is deprecated and is not supported in transactions", "[Queue = DB::MergeMutateRuntimeQueue]: Code: 235. DB::Exception: Part") - # FIXME https://github.com/ClickHouse/ClickHouse/issues/39174 - bad mutation does not indicate backward incompatibility - echo "Check for Error messages in server log:" - zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" \ - -e "Code: 236. DB::Exception: Cancelled mutating parts" \ - -e "REPLICA_IS_ALREADY_ACTIVE" \ - -e "REPLICA_ALREADY_EXISTS" \ - -e "ALL_REPLICAS_LOST" \ - -e "DDLWorker: Cannot parse DDL task query" \ - -e "RaftInstance: failed to accept a rpc connection due to error 125" \ - -e "UNKNOWN_DATABASE" \ - -e "NETWORK_ERROR" \ - -e "UNKNOWN_TABLE" \ - -e "ZooKeeperClient" \ - -e "KEEPER_EXCEPTION" \ - -e "DirectoryMonitor" \ - -e "TABLE_IS_READ_ONLY" \ - -e "Code: 1000, e.code() = 111, Connection refused" \ - -e "UNFINISHED" \ - -e "NETLINK_ERROR" \ - -e "Renaming unexpected part" \ - -e "PART_IS_TEMPORARILY_LOCKED" \ - -e "and a merge is impossible: we didn't find" \ - -e "found in queue and some source parts for it was lost" \ - -e "is lost forever." \ - -e "Unknown index: idx." \ - -e "Cannot parse string 'Hello' as UInt64" \ - -e "} TCPHandler: Code:" \ - -e "} executeQuery: Code:" \ - -e "Missing columns: 'v3' while processing query: 'v3, k, v1, v2, p'" \ - -e "This engine is deprecated and is not supported in transactions" \ - -e "[Queue = DB::MergeMutateRuntimeQueue]: Code: 235. DB::Exception: Part" \ - -e "The set of parts restored in place of" \ - -e "(ReplicatedMergeTreeAttachThread): Initialization failed. Error" \ - -e "Code: 269. DB::Exception: Destination table is myself" \ - -e "Coordination::Exception: Connection loss" \ - -e "MutateFromLogEntryTask" \ - -e "No connection to ZooKeeper, cannot get shared table ID" \ - -e "Session expired" \ - /var/log/clickhouse-server/clickhouse-server.backward.dirty.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 - - # Remove file bc_check_error_messages.txt if it's empty - [ -s /test_output/bc_check_error_messages.txt ] || rm /test_output/bc_check_error_messages.txt - - # Sanitizer asserts - zgrep -Fa "==================" /var/log/clickhouse-server/stderr.log >> /test_output/tmp - zgrep -Fa "WARNING" /var/log/clickhouse-server/stderr.log >> /test_output/tmp - zgrep -Fav -e "ASan doesn't fully support makecontext/swapcontext functions" -e "DB::Exception" /test_output/tmp > /dev/null \ - && echo -e 'Backward compatibility check: Sanitizer assert (in stderr.log)\tFAIL' >> /test_output/test_results.tsv \ - || echo -e 'Backward compatibility check: No sanitizer asserts\tOK' >> /test_output/test_results.tsv - rm -f /test_output/tmp - - # OOM - zgrep -Fa " Application: Child process was terminated by signal 9" /var/log/clickhouse-server/clickhouse-server.backward.*.log > /dev/null \ - && echo -e 'Backward compatibility check: OOM killer (or signal 9) in clickhouse-server.log\tFAIL' >> /test_output/test_results.tsv \ - || echo -e 'Backward compatibility check: No OOM messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv - - # Logical errors - echo "Check for Logical errors in server log:" - zgrep -Fa -A20 "Code: 49, e.displayText() = DB::Exception:" /var/log/clickhouse-server/clickhouse-server.backward.*.log > /test_output/bc_check_logical_errors.txt \ - && echo -e 'Backward compatibility check: Logical error thrown (see clickhouse-server.log or bc_check_logical_errors.txt)\tFAIL' >> /test_output/test_results.tsv \ - || echo -e 'Backward compatibility check: No logical errors\tOK' >> /test_output/test_results.tsv - - # Remove file bc_check_logical_errors.txt if it's empty - [ -s /test_output/bc_check_logical_errors.txt ] || rm /test_output/bc_check_logical_errors.txt - - # Crash - zgrep -Fa "########################################" /var/log/clickhouse-server/clickhouse-server.backward.*.log > /dev/null \ - && echo -e 'Backward compatibility check: Killed by signal (in clickhouse-server.log)\tFAIL' >> /test_output/test_results.tsv \ - || echo -e 'Backward compatibility check: Not crashed\tOK' >> /test_output/test_results.tsv - - # It also checks for crash without stacktrace (printed by watchdog) - echo "Check for Fatal message in server log:" - zgrep -Fa " " /var/log/clickhouse-server/clickhouse-server.backward.*.log > /test_output/bc_check_fatal_messages.txt \ - && echo -e 'Backward compatibility check: Fatal message in clickhouse-server.log (see bc_check_fatal_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ - || echo -e 'Backward compatibility check: No fatal messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv - - # Remove file bc_check_fatal_messages.txt if it's empty - [ -s /test_output/bc_check_fatal_messages.txt ] || rm /test_output/bc_check_fatal_messages.txt - - tar -chf /test_output/coordination.backward.tar /var/lib/clickhouse/coordination ||: - for table in query_log trace_log - do - clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | pigz > /test_output/$table.backward.tsv.gz ||: - done - fi -fi +tar -chf /test_output/coordination.tar /var/lib/clickhouse/coordination ||: dmesg -T > /test_output/dmesg.log diff --git a/docker/test/upgrade/Dockerfile b/docker/test/upgrade/Dockerfile new file mode 100644 index 00000000000..c98220b3403 --- /dev/null +++ b/docker/test/upgrade/Dockerfile @@ -0,0 +1,31 @@ +# rebuild in #33610 +# docker build -t clickhouse/upgrade-check . +ARG FROM_TAG=latest +FROM clickhouse/stateful-test:$FROM_TAG + +RUN apt-get update -y \ + && env DEBIAN_FRONTEND=noninteractive \ + apt-get install --yes --no-install-recommends \ + bash \ + tzdata \ + fakeroot \ + debhelper \ + parallel \ + expect \ + python3 \ + python3-lxml \ + python3-termcolor \ + python3-requests \ + curl \ + sudo \ + openssl \ + netcat-openbsd \ + telnet \ + llvm-9 \ + brotli + +COPY run.sh / + +ENV EXPORT_S3_STORAGE_POLICIES=1 + +CMD ["/bin/bash", "/run.sh"] diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh new file mode 100644 index 00000000000..1a107b6df2a --- /dev/null +++ b/docker/test/upgrade/run.sh @@ -0,0 +1,462 @@ +#!/bin/bash +# shellcheck disable=SC2094 +# shellcheck disable=SC2086 +# shellcheck disable=SC2024 + +# This script is similar to script for common stress test + +# Avoid overlaps with previous runs +dmesg --clear + +set -x + +# core.COMM.PID-TID +sysctl kernel.core_pattern='core.%e.%p-%P' + + +function install_packages() +{ + dpkg -i $1/clickhouse-common-static_*.deb + dpkg -i $1/clickhouse-common-static-dbg_*.deb + dpkg -i $1/clickhouse-server_*.deb + dpkg -i $1/clickhouse-client_*.deb +} + +function configure() +{ + # install test configs + export USE_DATABASE_ORDINARY=1 + export EXPORT_S3_STORAGE_POLICIES=1 + /usr/share/clickhouse-test/config/install.sh + + # we mount tests folder from repo to /usr/share + ln -s /usr/share/clickhouse-test/clickhouse-test /usr/bin/clickhouse-test + ln -s /usr/share/clickhouse-test/ci/download_release_packages.py /usr/bin/download_release_packages + ln -s /usr/share/clickhouse-test/ci/get_previous_release_tag.py /usr/bin/get_previous_release_tag + + # avoid too slow startup + sudo cat /etc/clickhouse-server/config.d/keeper_port.xml | sed "s|100000|10000|" > /etc/clickhouse-server/config.d/keeper_port.xml.tmp + sudo mv /etc/clickhouse-server/config.d/keeper_port.xml.tmp /etc/clickhouse-server/config.d/keeper_port.xml + sudo chown clickhouse /etc/clickhouse-server/config.d/keeper_port.xml + sudo chgrp clickhouse /etc/clickhouse-server/config.d/keeper_port.xml + + # for clickhouse-server (via service) + echo "ASAN_OPTIONS='malloc_context_size=10 verbosity=1 allocator_release_to_os_interval_ms=10000'" >> /etc/environment + # for clickhouse-client + export ASAN_OPTIONS='malloc_context_size=10 allocator_release_to_os_interval_ms=10000' + + # since we run clickhouse from root + sudo chown root: /var/lib/clickhouse + + # Set more frequent update period of asynchronous metrics to more frequently update information about real memory usage (less chance of OOM). + echo "1" \ + > /etc/clickhouse-server/config.d/asynchronous_metrics_update_period_s.xml + + local total_mem + total_mem=$(awk '/MemTotal/ { print $(NF-1) }' /proc/meminfo) # KiB + total_mem=$(( total_mem*1024 )) # bytes + # Set maximum memory usage as half of total memory (less chance of OOM). + # + # But not via max_server_memory_usage but via max_memory_usage_for_user, + # so that we can override this setting and execute service queries, like: + # - hung check + # - show/drop database + # - ... + # + # So max_memory_usage_for_user will be a soft limit, and + # max_server_memory_usage will be hard limit, and queries that should be + # executed regardless memory limits will use max_memory_usage_for_user=0, + # instead of relying on max_untracked_memory + local max_server_mem + max_server_mem=$((total_mem*75/100)) # 75% + echo "Setting max_server_memory_usage=$max_server_mem" + cat > /etc/clickhouse-server/config.d/max_server_memory_usage.xml < + ${max_server_mem} + +EOL + local max_users_mem + max_users_mem=$((total_mem*50/100)) # 50% + echo "Setting max_memory_usage_for_user=$max_users_mem" + cat > /etc/clickhouse-server/users.d/max_memory_usage_for_user.xml < + + + ${max_users_mem} + + + +EOL + + cat > /etc/clickhouse-server/config.d/core.xml < + + + 107374182400 + + + $PWD + +EOL + + # Analyzer is not yet ready for testing + cat > /etc/clickhouse-server/users.d/no_analyzer.xml < + + + + + + + + + + +EOL + +} + +function stop() +{ + local pid + # Preserve the pid, since the server can hung after the PID will be deleted. + pid="$(cat /var/run/clickhouse-server/clickhouse-server.pid)" + + clickhouse stop $max_tries --do-not-kill && return + + if [ -n "$1" ] + then + # temporarily disable it in BC check + clickhouse stop --force + return + fi + + # We failed to stop the server with SIGTERM. Maybe it hang, let's collect stacktraces. + kill -TERM "$(pidof gdb)" ||: + sleep 5 + echo "thread apply all backtrace (on stop)" >> /test_output/gdb.log + timeout 30m gdb -batch -ex 'thread apply all backtrace' -p "$pid" | ts '%Y-%m-%d %H:%M:%S' >> /test_output/gdb.log + clickhouse stop --force +} + +function start() +{ + counter=0 + until clickhouse-client --query "SELECT 1" + do + if [ "$counter" -gt ${1:-120} ] + then + echo "Cannot start clickhouse-server" + echo -e "Cannot start clickhouse-server\tFAIL" >> /test_output/test_results.tsv + cat /var/log/clickhouse-server/stdout.log + tail -n1000 /var/log/clickhouse-server/stderr.log + tail -n100000 /var/log/clickhouse-server/clickhouse-server.log | grep -F -v -e ' RaftInstance:' -e ' RaftInstance' | tail -n1000 + break + fi + # use root to match with current uid + clickhouse start --user root >/var/log/clickhouse-server/stdout.log 2>>/var/log/clickhouse-server/stderr.log + sleep 0.5 + counter=$((counter + 1)) + done + + # Set follow-fork-mode to parent, because we attach to clickhouse-server, not to watchdog + # and clickhouse-server can do fork-exec, for example, to run some bridge. + # Do not set nostop noprint for all signals, because some it may cause gdb to hang, + # explicitly ignore non-fatal signals that are used by server. + # Number of SIGRTMIN can be determined only in runtime. + RTMIN=$(kill -l SIGRTMIN) + echo " +set follow-fork-mode parent +handle SIGHUP nostop noprint pass +handle SIGINT nostop noprint pass +handle SIGQUIT nostop noprint pass +handle SIGPIPE nostop noprint pass +handle SIGTERM nostop noprint pass +handle SIGUSR1 nostop noprint pass +handle SIGUSR2 nostop noprint pass +handle SIG$RTMIN nostop noprint pass +info signals +continue +backtrace full +thread apply all backtrace full +info registers +disassemble /s +up +disassemble /s +up +disassemble /s +p \"done\" +detach +quit +" > script.gdb + + # FIXME Hung check may work incorrectly because of attached gdb + # 1. False positives are possible + # 2. We cannot attach another gdb to get stacktraces if some queries hung + gdb -batch -command script.gdb -p "$(cat /var/run/clickhouse-server/clickhouse-server.pid)" | ts '%Y-%m-%d %H:%M:%S' >> /test_output/gdb.log & + sleep 5 + # gdb will send SIGSTOP, spend some time loading debug info and then send SIGCONT, wait for it (up to send_timeout, 300s) + time clickhouse-client --query "SELECT 'Connected to clickhouse-server after attaching gdb'" ||: +} + +# Thread Fuzzer allows to check more permutations of possible thread scheduling +# and find more potential issues. +# Temporarily disable ThreadFuzzer with tsan because of https://github.com/google/sanitizers/issues/1540 +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_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 +fi + +azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --debug /azurite_log & +./setup_minio.sh stateless # to have a proper environment + +# But we still need default disk because some tables loaded only into it +sudo cat /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml | sed "s|
s3
|
s3
default|" > /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml.tmp +mv /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml.tmp /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml +sudo chown clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml +sudo chgrp clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml + +echo "Get previous release tag" +previous_release_tag=$(dpkg --info package_folder/clickhouse-client*.deb | grep "Version: " | awk '{print $2}' | get_previous_release_tag) +echo $previous_release_tag + +echo "Clone previous release repository" +git clone https://github.com/ClickHouse/ClickHouse.git --no-tags --progress --branch=$previous_release_tag --no-recurse-submodules --depth=1 previous_release_repository + +echo "Download clickhouse-server from the previous release" +mkdir previous_release_package_folder + +echo $previous_release_tag | download_release_packages && echo -e 'Download script exit code\tOK' >> /test_output/test_results.tsv \ + || echo -e 'Download script failed\tFAIL' >> /test_output/test_results.tsv + +# Check if we cloned previous release repository successfully +if ! [ "$(ls -A previous_release_repository/tests/queries)" ] +then + echo -e "Failed to clone previous release tests\tFAIL" >> /test_output/test_results.tsv +elif ! [ "$(ls -A previous_release_package_folder/clickhouse-common-static_*.deb && ls -A previous_release_package_folder/clickhouse-server_*.deb)" ] +then + echo -e "Failed to download previous release packages\tFAIL" >> /test_output/test_results.tsv +else + echo -e "Successfully cloned previous release tests\tOK" >> /test_output/test_results.tsv + echo -e "Successfully downloaded previous release packages\tOK" >> /test_output/test_results.tsv + + # Make upgrade check more funny by forcing Ordinary engine for system database + mkdir /var/lib/clickhouse/metadata + echo "ATTACH DATABASE system ENGINE=Ordinary" > /var/lib/clickhouse/metadata/system.sql + + # Install previous release packages + install_packages previous_release_package_folder + + # Start server from previous release + # Previous version may not be ready for fault injections + export ZOOKEEPER_FAULT_INJECTION=0 + configure + + # Avoid "Setting s3_check_objects_after_upload is neither a builtin setting..." + rm -f /etc/clickhouse-server/users.d/enable_blobs_check.xml ||: + rm -f /etc/clickhouse-server/users.d/marks.xml ||: + + # Remove s3 related configs to avoid "there is no disk type `cache`" + rm -f /etc/clickhouse-server/config.d/storage_conf.xml ||: + rm -f /etc/clickhouse-server/config.d/azure_storage_conf.xml ||: + + # Turn on after 22.12 + rm -f /etc/clickhouse-server/config.d/compressed_marks_and_index.xml ||: + # it uses recently introduced settings which previous versions may not have + rm -f /etc/clickhouse-server/users.d/insert_keeper_retries.xml ||: + + start + + clickhouse-client --query="SELECT 'Server version: ', version()" + + # Install new package before running stress test because we should use new + # clickhouse-client and new clickhouse-test. + # + # But we should leave old binary in /usr/bin/ and debug symbols in + # /usr/lib/debug/usr/bin (if any) for gdb and internal DWARF parser, so it + # will print sane stacktraces and also to avoid possible crashes. + # + # FIXME: those files can be extracted directly from debian package, but + # actually better solution will be to use different PATH instead of playing + # games with files from packages. + mv /usr/bin/clickhouse previous_release_package_folder/ + mv /usr/lib/debug/usr/bin/clickhouse.debug previous_release_package_folder/ + install_packages package_folder + mv /usr/bin/clickhouse package_folder/ + mv /usr/lib/debug/usr/bin/clickhouse.debug package_folder/ + mv previous_release_package_folder/clickhouse /usr/bin/ + mv previous_release_package_folder/clickhouse.debug /usr/lib/debug/usr/bin/clickhouse.debug + + mkdir tmp_stress_output + + stress --test-cmd="/usr/bin/clickhouse-test --queries=\"previous_release_repository/tests/queries\"" --upgrade-check --output-folder tmp_stress_output --global-time-limit=1200 \ + && echo -e 'Test script exit code\tOK' >> /test_output/test_results.tsv \ + || echo -e 'Test script failed\tFAIL' >> /test_output/test_results.tsv + rm -rf tmp_stress_output + + # We experienced deadlocks in this command in very rare cases. Let's debug it: + timeout 10m clickhouse-client --query="SELECT 'Tables count:', count() FROM system.tables" || + ( + echo "thread apply all backtrace (on select tables count)" >> /test_output/gdb.log + timeout 30m gdb -batch -ex 'thread apply all backtrace' -p "$(cat /var/run/clickhouse-server/clickhouse-server.pid)" | ts '%Y-%m-%d %H:%M:%S' >> /test_output/gdb.log + clickhouse stop --force + ) + + stop 1 + mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.stress.log + + # Start new server + mv package_folder/clickhouse /usr/bin/ + mv package_folder/clickhouse.debug /usr/lib/debug/usr/bin/clickhouse.debug + # Disable fault injections on start (we don't test them here, and it can lead to tons of requests in case of huge number of tables). + export ZOOKEEPER_FAULT_INJECTION=0 + configure + start 500 + clickhouse-client --query "SELECT 'Server successfully started', 'OK'" >> /test_output/test_results.tsv \ + || (echo -e 'Server failed to start\tFAIL' >> /test_output/test_results.tsv \ + && grep -a ".*Application" /var/log/clickhouse-server/clickhouse-server.log >> /test_output/application_errors.txt) + + # Remove file application_errors.txt if it's empty + [ -s /test_output/application_errors.txt ] || rm /test_output/application_errors.txt + + clickhouse-client --query="SELECT 'Server version: ', version()" + + # Let the server run for a while before checking log. + sleep 60 + + stop + mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.upgrade.log + + # Error messages (we should ignore some errors) + # FIXME https://github.com/ClickHouse/ClickHouse/issues/38643 ("Unknown index: idx.") + # FIXME https://github.com/ClickHouse/ClickHouse/issues/39174 ("Cannot parse string 'Hello' as UInt64") + # FIXME Not sure if it's expected, but some tests from stress test may not be finished yet when we restarting server. + # Let's just ignore all errors from queries ("} TCPHandler: Code:", "} executeQuery: Code:") + # FIXME https://github.com/ClickHouse/ClickHouse/issues/39197 ("Missing columns: 'v3' while processing query: 'v3, k, v1, v2, p'") + # NOTE Incompatibility was introduced in https://github.com/ClickHouse/ClickHouse/pull/39263, it's expected + # ("This engine is deprecated and is not supported in transactions", "[Queue = DB::MergeMutateRuntimeQueue]: Code: 235. DB::Exception: Part") + # FIXME https://github.com/ClickHouse/ClickHouse/issues/39174 - bad mutation does not indicate backward incompatibility + echo "Check for Error messages in server log:" + zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" \ + -e "Code: 236. DB::Exception: Cancelled mutating parts" \ + -e "REPLICA_IS_ALREADY_ACTIVE" \ + -e "REPLICA_ALREADY_EXISTS" \ + -e "ALL_REPLICAS_LOST" \ + -e "DDLWorker: Cannot parse DDL task query" \ + -e "RaftInstance: failed to accept a rpc connection due to error 125" \ + -e "UNKNOWN_DATABASE" \ + -e "NETWORK_ERROR" \ + -e "UNKNOWN_TABLE" \ + -e "ZooKeeperClient" \ + -e "KEEPER_EXCEPTION" \ + -e "DirectoryMonitor" \ + -e "TABLE_IS_READ_ONLY" \ + -e "Code: 1000, e.code() = 111, Connection refused" \ + -e "UNFINISHED" \ + -e "NETLINK_ERROR" \ + -e "Renaming unexpected part" \ + -e "PART_IS_TEMPORARILY_LOCKED" \ + -e "and a merge is impossible: we didn't find" \ + -e "found in queue and some source parts for it was lost" \ + -e "is lost forever." \ + -e "Unknown index: idx." \ + -e "Cannot parse string 'Hello' as UInt64" \ + -e "} TCPHandler: Code:" \ + -e "} executeQuery: Code:" \ + -e "Missing columns: 'v3' while processing query: 'v3, k, v1, v2, p'" \ + -e "This engine is deprecated and is not supported in transactions" \ + -e "[Queue = DB::MergeMutateRuntimeQueue]: Code: 235. DB::Exception: Part" \ + -e "The set of parts restored in place of" \ + -e "(ReplicatedMergeTreeAttachThread): Initialization failed. Error" \ + -e "Code: 269. DB::Exception: Destination table is myself" \ + -e "Coordination::Exception: Connection loss" \ + -e "MutateFromLogEntryTask" \ + -e "No connection to ZooKeeper, cannot get shared table ID" \ + -e "Session expired" \ + /var/log/clickhouse-server/clickhouse-server.upgrade.log | zgrep -Fa "" > /test_output/upgrade_error_messages.txt \ + && echo -e 'Error message in logs after server upgrade (see upgrade_error_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No Error messages after server upgrade\tOK' >> /test_output/test_results.tsv + + # Remove file bc_check_error_messages.txt if it's empty + [ -s /test_output/upgrade_error_messages.txt ] || rm /test_output/upgrade_error_messages.txt + + # Sanitizer asserts + zgrep -Fa "==================" /var/log/clickhouse-server/stderr.log >> /test_output/tmp + zgrep -Fa "WARNING" /var/log/clickhouse-server/stderr.log >> /test_output/tmp + zgrep -Fav -e "ASan doesn't fully support makecontext/swapcontext functions" -e "DB::Exception" /test_output/tmp > /dev/null \ + && echo -e 'Sanitizer assert (in stderr.log)\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No sanitizer asserts\tOK' >> /test_output/test_results.tsv + rm -f /test_output/tmp + + # OOM + zgrep -Fa " Application: Child process was terminated by signal 9" /var/log/clickhouse-server/clickhouse-server.*.log > /dev/null \ + && echo -e 'OOM killer (or signal 9) in clickhouse-server.log\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No OOM messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv + + # Logical errors + echo "Check for Logical errors in server log:" + zgrep -Fa -A20 "Code: 49, e.displayText() = DB::Exception:" /var/log/clickhouse-server/clickhouse-server.*.log > /test_output/logical_errors.txt \ + && echo -e 'Logical error thrown (see server logs or logical_errors.txt)\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No logical errors\tOK' >> /test_output/test_results.tsv + + # Remove file logical_errors.txt if it's empty + [ -s /test_output/logical_errors.txt ] || rm /test_output/logical_errors.txt + + # Crash + zgrep -Fa "########################################" /var/log/clickhouse-server/clickhouse-server.*.log > /dev/null \ + && echo -e 'Killed by signal (in server logs)\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'Not crashed\tOK' >> /test_output/test_results.tsv + + # It also checks for crash without stacktrace (printed by watchdog) + echo "Check for Fatal message in server log:" + zgrep -Fa " " /var/log/clickhouse-server/clickhouse-server.*.log > /test_output/fatal_messages.txt \ + && echo -e 'Fatal message in server logs (see bc_check_fatal_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No fatal messages in server logs\tOK' >> /test_output/test_results.tsv + + # Remove file fatal_messages.txt if it's empty + [ -s /test_output/fatal_messages.txt ] || rm /test_output/fatal_messages.txt + + tar -chf /test_output/coordination.tar /var/lib/clickhouse/coordination ||: + for table in query_log trace_log + do + clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | pigz > /test_output/$table.backward.tsv.gz ||: + done +fi + +dmesg -T > /test_output/dmesg.log + +# OOM in dmesg -- those are real +grep -q -F -e 'Out of memory: Killed process' -e 'oom_reaper: reaped process' -e 'oom-kill:constraint=CONSTRAINT_NONE' /test_output/dmesg.log \ + && echo -e 'OOM in dmesg\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'No OOM in dmesg\tOK' >> /test_output/test_results.tsv + +mv /var/log/clickhouse-server/stderr.log /test_output/ + +# Write check result into check_status.tsv +clickhouse-local --structure "test String, res String" -q "SELECT 'failure', test FROM table WHERE res != 'OK' order by (lower(test) like '%hung%'), rowNumberInAllBlocks() LIMIT 1" < /test_output/test_results.tsv > /test_output/check_status.tsv +[ -s /test_output/check_status.tsv ] || echo -e "success\tNo errors found" > /test_output/check_status.tsv + +# Core dumps +for core in core.*; do + pigz $core + mv $core.gz /test_output/ +done diff --git a/tests/ci/stress.py b/tests/ci/stress.py new file mode 100755 index 00000000000..ae35afbc5fa --- /dev/null +++ b/tests/ci/stress.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from multiprocessing import cpu_count +from subprocess import Popen, call, check_output, STDOUT +import os +import argparse +import logging +import time + + +def get_options(i, upgrade_check): + options = [] + client_options = [] + if 0 < i: + options.append("--order=random") + + if i % 3 == 2 and not upgrade_check: + options.append( + '''--db-engine="Replicated('/test/db/test_{}', 's1', 'r1')"'''.format(i) + ) + client_options.append("allow_experimental_database_replicated=1") + + # If database name is not specified, new database is created for each functional test. + # Run some threads with one database for all tests. + if i % 2 == 1: + options.append(" --database=test_{}".format(i)) + + if i % 3 == 1: + client_options.append("join_use_nulls=1") + + if i % 2 == 1: + join_alg_num = i // 2 + if join_alg_num % 4 == 0: + client_options.append("join_algorithm='parallel_hash'") + if join_alg_num % 4 == 1: + client_options.append("join_algorithm='partial_merge'") + if join_alg_num % 4 == 2: + client_options.append("join_algorithm='full_sorting_merge'") + if join_alg_num % 4 == 3: + client_options.append("join_algorithm='auto'") + client_options.append('max_rows_in_join=1000') + + if i == 13: + client_options.append("memory_tracker_fault_probability=0.001") + + if i % 2 == 1 and not upgrade_check: + client_options.append("group_by_use_nulls=1") + + if client_options: + options.append(" --client-option " + " ".join(client_options)) + + return " ".join(options) + + +def run_func_test( + cmd, + output_prefix, + num_processes, + skip_tests_option, + global_time_limit, + upgrade_check, +): + upgrade_check_option = ( + "--upgrade-check" if upgrade_check else "" + ) + global_time_limit_option = "" + if global_time_limit: + global_time_limit_option = "--global_time_limit={}".format(global_time_limit) + + output_paths = [ + os.path.join(output_prefix, "stress_test_run_{}.txt".format(i)) + for i in range(num_processes) + ] + pipes = [] + for i in range(0, len(output_paths)): + f = open(output_paths[i], "w") + full_command = "{} {} {} {} {} --stress".format( + cmd, + get_options(i, upgrade_check), + global_time_limit_option, + skip_tests_option, + upgrade_check_option, + ) + logging.info("Run func tests '%s'", full_command) + p = Popen(full_command, shell=True, stdout=f, stderr=f) + pipes.append(p) + time.sleep(0.5) + return pipes + + +def compress_stress_logs(output_path, files_prefix): + cmd = f"cd {output_path} && tar -zcf stress_run_logs.tar.gz {files_prefix}* && rm {files_prefix}*" + check_output(cmd, shell=True) + + +def call_with_retry(query, timeout=30, retry_count=5): + for i in range(retry_count): + code = call(query, shell=True, stderr=STDOUT, timeout=timeout) + if code != 0: + time.sleep(i) + else: + break + + +def make_query_command(query): + return f"""clickhouse client -q "{query}" --max_untracked_memory=1Gi --memory_profiler_step=1Gi --max_memory_usage_for_user=0""" + + +def prepare_for_hung_check(drop_databases): + # FIXME this function should not exist, but... + + # We attach gdb to clickhouse-server before running tests + # to print stacktraces of all crashes even if clickhouse cannot print it for some reason. + # However, it obstruct checking for hung queries. + logging.info("Will terminate gdb (if any)") + call_with_retry("kill -TERM $(pidof gdb)") + + # ThreadFuzzer significantly slows down server and causes false-positive hung check failures + call_with_retry("clickhouse client -q 'SYSTEM STOP THREAD FUZZER'") + + call_with_retry(make_query_command("SELECT 1 FORMAT Null")) + + # Some tests execute SYSTEM STOP MERGES or similar queries. + # It may cause some ALTERs to hang. + # Possibly we should fix tests and forbid to use such queries without specifying table. + call_with_retry(make_query_command("SYSTEM START MERGES")) + call_with_retry(make_query_command("SYSTEM START DISTRIBUTED SENDS")) + call_with_retry(make_query_command("SYSTEM START TTL MERGES")) + call_with_retry(make_query_command("SYSTEM START MOVES")) + call_with_retry(make_query_command("SYSTEM START FETCHES")) + call_with_retry(make_query_command("SYSTEM START REPLICATED SENDS")) + call_with_retry(make_query_command("SYSTEM START REPLICATION QUEUES")) + call_with_retry(make_query_command("SYSTEM DROP MARK CACHE")) + + # Issue #21004, live views are experimental, so let's just suppress it + call_with_retry(make_query_command("KILL QUERY WHERE upper(query) LIKE 'WATCH %'")) + + # Kill other queries which known to be slow + # It's query from 01232_preparing_sets_race_condition_long, it may take up to 1000 seconds in slow builds + call_with_retry( + make_query_command("KILL QUERY WHERE query LIKE 'insert into tableB select %'") + ) + # Long query from 00084_external_agregation + call_with_retry( + make_query_command( + "KILL QUERY WHERE query LIKE 'SELECT URL, uniq(SearchPhrase) AS u FROM test.hits GROUP BY URL ORDER BY u %'" + ) + ) + + if drop_databases: + for i in range(5): + try: + # Here we try to drop all databases in async mode. If some queries really hung, than drop will hung too. + # Otherwise we will get rid of queries which wait for background pool. It can take a long time on slow builds (more than 900 seconds). + # + # Also specify max_untracked_memory to allow 1GiB of memory to overcommit. + databases = ( + check_output( + make_query_command("SHOW DATABASES"), shell=True, timeout=30 + ) + .decode("utf-8") + .strip() + .split() + ) + for db in databases: + if db == "system": + continue + command = make_query_command(f'DETACH DATABASE {db}') + # we don't wait for drop + Popen(command, shell=True) + break + except Exception as ex: + logging.error( + "Failed to SHOW or DROP databasese, will retry %s", str(ex) + ) + time.sleep(i) + else: + raise Exception( + "Cannot drop databases after stress tests. Probably server consumed too much memory and cannot execute simple queries" + ) + + # Wait for last queries to finish if any, not longer than 300 seconds + call( + make_query_command( + """ + select sleepEachRow(( + select maxOrDefault(300 - elapsed) + 1 + from system.processes + where query not like '%from system.processes%' and elapsed < 300 + ) / 300) + from numbers(300) + format Null + """ + ), + shell=True, + stderr=STDOUT, + timeout=330, + ) + + # Even if all clickhouse-test processes are finished, there are probably some sh scripts, + # which still run some new queries. Let's ignore them. + try: + query = """clickhouse client -q "SELECT count() FROM system.processes where where elapsed > 300" """ + output = ( + check_output(query, shell=True, stderr=STDOUT, timeout=30) + .decode("utf-8") + .strip() + ) + if int(output) == 0: + return False + except: + pass + return True + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") + parser = argparse.ArgumentParser( + description="ClickHouse script for running stresstest" + ) + parser.add_argument("--test-cmd", default="/usr/bin/clickhouse-test") + parser.add_argument("--skip-func-tests", default="") + parser.add_argument("--client-cmd", default="clickhouse-client") + parser.add_argument("--server-log-folder", default="/var/log/clickhouse-server") + parser.add_argument("--output-folder") + parser.add_argument("--global-time-limit", type=int, default=1800) + parser.add_argument("--num-parallel", type=int, default=cpu_count()) + parser.add_argument("--upgrade-check", action="store_true") + parser.add_argument("--hung-check", action="store_true", default=False) + # make sense only for hung check + parser.add_argument("--drop-databases", action="store_true", default=False) + + args = parser.parse_args() + if args.drop_databases and not args.hung_check: + raise Exception("--drop-databases only used in hung check (--hung-check)") + func_pipes = [] + func_pipes = run_func_test( + args.test_cmd, + args.output_folder, + args.num_parallel, + args.skip_func_tests, + args.global_time_limit, + args.upgrade_check, + ) + + logging.info("Will wait functests to finish") + while True: + retcodes = [] + for p in func_pipes: + if p.poll() is not None: + retcodes.append(p.returncode) + if len(retcodes) == len(func_pipes): + break + logging.info("Finished %s from %s processes", len(retcodes), len(func_pipes)) + time.sleep(5) + + logging.info("All processes finished") + + logging.info("Compressing stress logs") + compress_stress_logs(args.output_folder, "stress_test_run_") + logging.info("Logs compressed") + + if args.hung_check: + try: + have_long_running_queries = prepare_for_hung_check(args.drop_databases) + except Exception as ex: + have_long_running_queries = True + logging.error("Failed to prepare for hung check %s", str(ex)) + logging.info("Checking if some queries hung") + cmd = " ".join( + [ + args.test_cmd, + # Do not track memory allocations up to 1Gi, + # this will allow to ignore server memory limit (max_server_memory_usage) for this query. + # + # NOTE: memory_profiler_step should be also adjusted, because: + # + # untracked_memory_limit = min(settings.max_untracked_memory, settings.memory_profiler_step) + # + # NOTE: that if there will be queries with GROUP BY, this trick + # will not work due to CurrentMemoryTracker::check() from + # Aggregator code. + # But right now it should work, since neither hung check, nor 00001_select_1 has GROUP BY. + "--client-option", + "max_untracked_memory=1Gi", + "max_memory_usage_for_user=0", + "memory_profiler_step=1Gi", + # Use system database to avoid CREATE/DROP DATABASE queries + "--database=system", + "--hung-check", + "--stress", + "00001_select_1", + ] + ) + res = call(cmd, shell=True, stderr=STDOUT) + hung_check_status = "No queries hung\tOK\n" + if res != 0 and have_long_running_queries: + logging.info("Hung check failed with exit code {}".format(res)) + hung_check_status = "Hung check failed\tFAIL\n" + with open( + os.path.join(args.output_folder, "test_results.tsv"), "w+" + ) as results: + results.write(hung_check_status) + + logging.info("Stress test finished") diff --git a/tests/ci/stress_check.py b/tests/ci/stress_check.py index 37277538867..1437d50f9c5 100644 --- a/tests/ci/stress_check.py +++ b/tests/ci/stress_check.py @@ -34,7 +34,6 @@ def get_run_command( "docker run --cap-add=SYS_PTRACE " # a static link, don't use S3_URL or S3_DOWNLOAD "-e S3_URL='https://s3.amazonaws.com/clickhouse-datasets' " - f"-e DISABLE_BC_CHECK={os.environ.get('DISABLE_BC_CHECK', '0')} " # For dmesg and sysctl "--privileged " f"--volume={build_path}:/package_folder " @@ -100,7 +99,7 @@ def process_results( return state, description, test_results, additional_files -if __name__ == "__main__": +def run_stress_test(docker_image_name): logging.basicConfig(level=logging.INFO) stopwatch = Stopwatch() @@ -123,7 +122,7 @@ if __name__ == "__main__": logging.info("Check is already finished according to github status, exiting") sys.exit(0) - docker_image = get_image_with_version(reports_path, "clickhouse/stress-test") + docker_image = get_image_with_version(reports_path, docker_image_name) packages_path = os.path.join(temp_path, "packages") if not os.path.exists(packages_path): @@ -187,3 +186,6 @@ if __name__ == "__main__": if state == "error": sys.exit(1) + +if __name__ == "__main__": + run_stress_test("clickhouse/stress-test") diff --git a/tests/ci/upgrade_check.py b/tests/ci/upgrade_check.py new file mode 100644 index 00000000000..83b6f9e299f --- /dev/null +++ b/tests/ci/upgrade_check.py @@ -0,0 +1,4 @@ +import stress_check + +if __name__ == "__main__": + stress_check.run_stress_test("clickhouse/upgrade-check") diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 13669981daa..8d80bc58f7d 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -404,7 +404,7 @@ class FailureReason(enum.Enum): S3_STORAGE = "s3-storage" STRESS = "stress" BUILD = "not running for current build" - BACKWARD_INCOMPATIBLE = "test is backward incompatible" + NO_UPGRADE_CHECK = "not running for upgrade check" # UNKNOWN reasons NO_REFERENCE = "no reference file" @@ -650,35 +650,6 @@ class TestCase: else "" ) - # Check if test contains tag "no-backward-compatibility-check" and we should skip it - def check_backward_incompatible_tag(self) -> bool: - for tag in self.tags: - if tag.startswith("no-backward-compatibility-check"): - split = tag.split(":") - - # If version is not specified in tag, always skip this test. - if len(split) == 1: - return True - version_from_tag = split[1] - - # Check if extracted string from tag is a real ClickHouse version, if not - always skip test. - if re.match(VERSION_PATTERN, version_from_tag) is None: - return True - - server_version = str( - clickhouse_execute(args, "SELECT version()").decode() - ) - # If server version is less or equal from the version specified in tag, we should skip this test. - version_from_tag_split = list(map(int, version_from_tag.split("."))) - server_version_split = list(map(int, server_version.split("."))) - if ( - server_version_split[: len(version_from_tag_split)] - <= version_from_tag_split - ): - return True - - return False - # should skip test, should increment skipped_total, skip reason def should_skip_test(self, suite) -> Optional[FailureReason]: tags = self.tags @@ -726,10 +697,10 @@ class TestCase: elif tags and ("no-replicated-database" in tags) and args.replicated_database: return FailureReason.REPLICATED_DB - elif ( - args.backward_compatibility_check and self.check_backward_incompatible_tag() - ): - return FailureReason.BACKWARD_INCOMPATIBLE + # TODO: remove checking "no-upgrade-check" after 23.1 + elif args.upgrade_check and ( + "no-upgrade-check" in tags or "no-upgrade-check" in tags): + return FailureReason.NO_UPGRADE_CHECK elif tags and ("no-s3-storage" in tags) and args.s3_storage: return FailureReason.S3_STORAGE @@ -2212,9 +2183,9 @@ if __name__ == "__main__": ) group.add_argument( - "--backward-compatibility-check", + "--upgrade-check", action="store_true", - help="Run tests for further backward compatibility testing by ignoring all" + help="Run tests for further server upgrade testing by ignoring all" "drop queries in tests for collecting data from new version of server", ) parser.add_argument( @@ -2332,7 +2303,7 @@ if __name__ == "__main__": else: args.client_database = "default" - if args.backward_compatibility_check: + if args.upgrade_check: args.client += " --fake-drop" if args.client_option or args.secure: diff --git a/tests/queries/0_stateless/00061_merge_tree_alter.sql b/tests/queries/0_stateless/00061_merge_tree_alter.sql index ee5694518d9..2e46b1e16d6 100644 --- a/tests/queries/0_stateless/00061_merge_tree_alter.sql +++ b/tests/queries/0_stateless/00061_merge_tree_alter.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check DROP TABLE IF EXISTS alter_00061; set allow_deprecated_syntax_for_merge_tree=1; diff --git a/tests/queries/0_stateless/00626_replace_partition_from_table_zookeeper.sh b/tests/queries/0_stateless/00626_replace_partition_from_table_zookeeper.sh index 1aa02864815..8b07d9abe35 100755 --- a/tests/queries/0_stateless/00626_replace_partition_from_table_zookeeper.sh +++ b/tests/queries/0_stateless/00626_replace_partition_from_table_zookeeper.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: zookeeper, no-parallel, no-s3-storage, no-backward-compatibility-check +# Tags: zookeeper, no-parallel, no-s3-storage, no-upgrade-check # Because REPLACE PARTITION does not forces immediate removal of replaced data parts from local filesystem # (it tries to do it as quick as possible, but it still performed in separate thread asynchronously) diff --git a/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql b/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql index e8d923389e5..9c02ac795ed 100644 --- a/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql +++ b/tests/queries/0_stateless/00732_quorum_insert_lost_part_and_alive_part_zookeeper_long.sql @@ -1,4 +1,4 @@ --- Tags: long, zookeeper, no-replicated-database, no-backward-compatibility-check +-- Tags: long, zookeeper, no-replicated-database, no-upgrade-check -- Tag no-replicated-database: Fails due to additional replicas or shards SET send_logs_level = 'fatal'; diff --git a/tests/queries/0_stateless/00942_dataparts_500.sh b/tests/queries/0_stateless/00942_dataparts_500.sh index 7e1a7f15810..a6c3fcd4303 100755 --- a/tests/queries/0_stateless/00942_dataparts_500.sh +++ b/tests/queries/0_stateless/00942_dataparts_500.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-backward-compatibility-check +# Tags: no-upgrade-check # Test fix for issue #5066 CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) diff --git a/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh b/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh index 5ccef802c0c..f143c97bdf4 100755 --- a/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh +++ b/tests/queries/0_stateless/00993_system_parts_race_condition_drop_zookeeper.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: race, zookeeper, no-parallel, no-backward-compatibility-check +# Tags: race, zookeeper, no-parallel, no-upgrade-check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh b/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh index 1f316b4b389..aec27792603 100755 --- a/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh +++ b/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: zookeeper, no-parallel, no-fasttest, no-backward-compatibility-check +# Tags: zookeeper, no-parallel, no-fasttest, no-upgrade-check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh index bbe3a5a51c0..8c9efb75e96 100755 --- a/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh +++ b/tests/queries/0_stateless/01111_create_drop_replicated_db_stress.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: race, zookeeper, no-backward-compatibility-check +# Tags: race, zookeeper, no-upgrade-check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01191_rename_dictionary.sql b/tests/queries/0_stateless/01191_rename_dictionary.sql index ed9bc8af61b..8074e84f0ed 100644 --- a/tests/queries/0_stateless/01191_rename_dictionary.sql +++ b/tests/queries/0_stateless/01191_rename_dictionary.sql @@ -1,4 +1,4 @@ --- Tags: no-parallel, no-backward-compatibility-check +-- Tags: no-parallel, no-upgrade-check DROP DATABASE IF EXISTS test_01191; CREATE DATABASE test_01191 ENGINE=Atomic; diff --git a/tests/queries/0_stateless/01318_long_unsuccessful_mutation_zookeeper.sh b/tests/queries/0_stateless/01318_long_unsuccessful_mutation_zookeeper.sh index f7615974237..f9a2ec8a34c 100755 --- a/tests/queries/0_stateless/01318_long_unsuccessful_mutation_zookeeper.sh +++ b/tests/queries/0_stateless/01318_long_unsuccessful_mutation_zookeeper.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: long, zookeeper, no-parallel, no-backward-compatibility-check +# Tags: long, zookeeper, no-parallel, no-upgrade-check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01378_alter_rename_with_ttl_zookeeper.sql b/tests/queries/0_stateless/01378_alter_rename_with_ttl_zookeeper.sql index 8717c93b468..43c9fa43104 100644 --- a/tests/queries/0_stateless/01378_alter_rename_with_ttl_zookeeper.sql +++ b/tests/queries/0_stateless/01378_alter_rename_with_ttl_zookeeper.sql @@ -1,4 +1,4 @@ --- Tags: zookeeper, no-backward-compatibility-check +-- Tags: zookeeper, no-upgrade-check DROP TABLE IF EXISTS table_rename_with_ttl; diff --git a/tests/queries/0_stateless/01391_join_on_dict_crash.sql b/tests/queries/0_stateless/01391_join_on_dict_crash.sql index 13ebd080621..5321e03767f 100644 --- a/tests/queries/0_stateless/01391_join_on_dict_crash.sql +++ b/tests/queries/0_stateless/01391_join_on_dict_crash.sql @@ -1,4 +1,4 @@ --- Tags: no-parallel, no-backward-compatibility-check +-- Tags: no-parallel, no-upgrade-check DROP DATABASE IF EXISTS db_01391; CREATE DATABASE db_01391; diff --git a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql index bdcde1adbad..d2ae05a5f80 100644 --- a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql +++ b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check -- force data path with the user/pass in it set use_compact_format_in_distributed_parts_names=0; diff --git a/tests/queries/0_stateless/01576_alter_low_cardinality_and_select.sh b/tests/queries/0_stateless/01576_alter_low_cardinality_and_select.sh index 27de10ab16a..4a9b4beee5b 100755 --- a/tests/queries/0_stateless/01576_alter_low_cardinality_and_select.sh +++ b/tests/queries/0_stateless/01576_alter_low_cardinality_and_select.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-backward-compatibility-check +# Tags: no-upgrade-check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql b/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql index 1dae8e7b383..b45a1974611 100644 --- a/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql +++ b/tests/queries/0_stateless/01650_fetch_patition_with_macro_in_zk_path_long.sql @@ -1,4 +1,4 @@ --- Tags: long, no-backward-compatibility-check +-- Tags: long, no-upgrade-check DROP TABLE IF EXISTS test_01640; DROP TABLE IF EXISTS restore_01640; diff --git a/tests/queries/0_stateless/01780_column_sparse_alter.sql b/tests/queries/0_stateless/01780_column_sparse_alter.sql index 925b81ea2c2..bc2f6f7c91f 100644 --- a/tests/queries/0_stateless/01780_column_sparse_alter.sql +++ b/tests/queries/0_stateless/01780_column_sparse_alter.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check SET mutations_sync = 2; diff --git a/tests/queries/0_stateless/02022_storage_filelog_one_file.sh b/tests/queries/0_stateless/02022_storage_filelog_one_file.sh index 2f43423e13e..3abf5c52031 100755 --- a/tests/queries/0_stateless/02022_storage_filelog_one_file.sh +++ b/tests/queries/0_stateless/02022_storage_filelog_one_file.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-backward-compatibility-check +# Tags: no-upgrade-check set -eu diff --git a/tests/queries/0_stateless/02025_storage_filelog_virtual_col.sh b/tests/queries/0_stateless/02025_storage_filelog_virtual_col.sh index e4041b2d755..e0f0114d030 100755 --- a/tests/queries/0_stateless/02025_storage_filelog_virtual_col.sh +++ b/tests/queries/0_stateless/02025_storage_filelog_virtual_col.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-backward-compatibility-check +# Tags: no-upgrade-check set -eu diff --git a/tests/queries/0_stateless/02067_lost_part_s3.sql b/tests/queries/0_stateless/02067_lost_part_s3.sql index c4e69f68a5d..12afdcd4421 100644 --- a/tests/queries/0_stateless/02067_lost_part_s3.sql +++ b/tests/queries/0_stateless/02067_lost_part_s3.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check, no-fasttest +-- Tags: no-upgrade-check, no-fasttest DROP TABLE IF EXISTS partslost_0; DROP TABLE IF EXISTS partslost_1; diff --git a/tests/queries/0_stateless/02222_create_table_without_columns_metadata.sh b/tests/queries/0_stateless/02222_create_table_without_columns_metadata.sh index 261c389c9f2..26646bd91a0 100755 --- a/tests/queries/0_stateless/02222_create_table_without_columns_metadata.sh +++ b/tests/queries/0_stateless/02222_create_table_without_columns_metadata.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-fasttest, no-parallel, no-backward-compatibility-check +# Tags: no-fasttest, no-parallel, no-upgrade-check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/02242_delete_user_race.sh b/tests/queries/0_stateless/02242_delete_user_race.sh index 8c28cdb57bd..f22b7796bd4 100755 --- a/tests/queries/0_stateless/02242_delete_user_race.sh +++ b/tests/queries/0_stateless/02242_delete_user_race.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: race, no-fasttest, no-parallel, no-backward-compatibility-check +# Tags: race, no-fasttest, no-parallel, no-upgrade-check # Test tries to reproduce a race between threads: # - deletes user diff --git a/tests/queries/0_stateless/02243_drop_user_grant_race.sh b/tests/queries/0_stateless/02243_drop_user_grant_race.sh index d36db47e562..e36be96aa02 100755 --- a/tests/queries/0_stateless/02243_drop_user_grant_race.sh +++ b/tests/queries/0_stateless/02243_drop_user_grant_race.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: race, no-fasttest, no-parallel, no-backward-compatibility-check +# Tags: race, no-fasttest, no-parallel, no-upgrade-check set -e diff --git a/tests/queries/0_stateless/02293_hashid.sql b/tests/queries/0_stateless/02293_hashid.sql index 9938154f169..06af0b5e1d8 100644 --- a/tests/queries/0_stateless/02293_hashid.sql +++ b/tests/queries/0_stateless/02293_hashid.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check SET allow_experimental_hash_functions = 1; select number, hashid(number) from system.numbers limit 5; diff --git a/tests/queries/0_stateless/02302_join_auto_lc_nullable_bug.sql b/tests/queries/0_stateless/02302_join_auto_lc_nullable_bug.sql index 469476c82bf..8e0fb4a55a0 100644 --- a/tests/queries/0_stateless/02302_join_auto_lc_nullable_bug.sql +++ b/tests/queries/0_stateless/02302_join_auto_lc_nullable_bug.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check SET max_bytes_in_join = '100', join_algorithm = 'auto'; diff --git a/tests/queries/0_stateless/02306_window_move_row_number_fix.sql b/tests/queries/0_stateless/02306_window_move_row_number_fix.sql index 5bc0c41b3ee..f73525f92be 100644 --- a/tests/queries/0_stateless/02306_window_move_row_number_fix.sql +++ b/tests/queries/0_stateless/02306_window_move_row_number_fix.sql @@ -1,2 +1,2 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check SELECT nth_value(NULL, 1048577) OVER (Rows BETWEEN 1023 FOLLOWING AND UNBOUNDED FOLLOWING) diff --git a/tests/queries/0_stateless/02313_cross_join_dup_col_names.sql b/tests/queries/0_stateless/02313_cross_join_dup_col_names.sql index 44a4797ae3c..08e8843f763 100644 --- a/tests/queries/0_stateless/02313_cross_join_dup_col_names.sql +++ b/tests/queries/0_stateless/02313_cross_join_dup_col_names.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check -- https://github.com/ClickHouse/ClickHouse/issues/37561 diff --git a/tests/queries/0_stateless/02315_pmj_union_ubsan_35857.sql b/tests/queries/0_stateless/02315_pmj_union_ubsan_35857.sql index 47b47101a79..df20e5c42d4 100644 --- a/tests/queries/0_stateless/02315_pmj_union_ubsan_35857.sql +++ b/tests/queries/0_stateless/02315_pmj_union_ubsan_35857.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check SET join_algorithm = 'partial_merge'; 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 35f210be43d..cac7992e305 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,6 +1,3 @@ --- Tags: no-backward-compatibility-check --- TODO: remove no-backward-compatibility-check after new 22.6 release - SET cast_ipv4_ipv6_default_on_conversion_error = 1; DROP TABLE IF EXISTS ipv4_test; diff --git a/tests/queries/0_stateless/02316_const_string_intersact.sql b/tests/queries/0_stateless/02316_const_string_intersact.sql index 18af398aa5d..148d048952b 100644 --- a/tests/queries/0_stateless/02316_const_string_intersact.sql +++ b/tests/queries/0_stateless/02316_const_string_intersact.sql @@ -1,3 +1,3 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check SELECT 'Play ClickHouse' InterSect SELECT 'Play ClickHouse' 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 08651590c76..734c597051e 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,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check select arrayMap(x -> toNullable(1), range(number)) from numbers(3); select arrayFilter(x -> toNullable(1), range(number)) from numbers(3); 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 index 653cb25172a..503b94be715 100755 --- a/tests/queries/0_stateless/02332_dist_insert_send_logs_level.sh +++ b/tests/queries/0_stateless/02332_dist_insert_send_logs_level.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-backward-compatibility-check +# Tags: no-upgrade-check CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL=trace CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) diff --git a/tests/queries/0_stateless/02345_partial_sort_transform_optimization.sql b/tests/queries/0_stateless/02345_partial_sort_transform_optimization.sql index eb395e5ec41..07f705acd84 100644 --- a/tests/queries/0_stateless/02345_partial_sort_transform_optimization.sql +++ b/tests/queries/0_stateless/02345_partial_sort_transform_optimization.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check -- Regression for PartialSortingTransform optimization that requires at least 1500 rows. SELECT * FROM (SELECT * FROM (SELECT 0 a, toNullable(number) b, toString(number) c FROM numbers(1e6)) ORDER BY a DESC, b DESC, c LIMIT 1500) limit 10; diff --git a/tests/queries/0_stateless/02354_annoy.sh b/tests/queries/0_stateless/02354_annoy.sh index 526886ec68d..670b31dc2a4 100755 --- a/tests/queries/0_stateless/02354_annoy.sh +++ b/tests/queries/0_stateless/02354_annoy.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-fasttest, no-ubsan, no-cpu-aarch64, no-backward-compatibility-check +# Tags: no-fasttest, no-ubsan, no-cpu-aarch64, no-upgrade-check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/02363_mapupdate_improve.sql b/tests/queries/0_stateless/02363_mapupdate_improve.sql index 6b7723cc9b4..b4a4b8e5d91 100644 --- a/tests/queries/0_stateless/02363_mapupdate_improve.sql +++ b/tests/queries/0_stateless/02363_mapupdate_improve.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check DROP TABLE IF EXISTS map_test; CREATE TABLE map_test(`tags` Map(String, String)) ENGINE = MergeTree PRIMARY KEY tags ORDER BY tags SETTINGS index_granularity = 8192; INSERT INTO map_test (tags) VALUES (map('fruit','apple','color','red')); diff --git a/tests/queries/0_stateless/02366_direct_dictionary_dict_has.sql b/tests/queries/0_stateless/02366_direct_dictionary_dict_has.sql index 9d6950051f0..cf9f2971cb0 100644 --- a/tests/queries/0_stateless/02366_direct_dictionary_dict_has.sql +++ b/tests/queries/0_stateless/02366_direct_dictionary_dict_has.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check DROP TABLE IF EXISTS test_table; CREATE TABLE test_table diff --git a/tests/queries/0_stateless/02366_with_fill_date.sql b/tests/queries/0_stateless/02366_with_fill_date.sql index 64e23b845f8..4d41facf423 100644 --- a/tests/queries/0_stateless/02366_with_fill_date.sql +++ b/tests/queries/0_stateless/02366_with_fill_date.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check SELECT toDate('2022-02-01') AS d1 FROM numbers(18) AS number diff --git a/tests/queries/0_stateless/02381_compress_marks_and_primary_key.sql b/tests/queries/0_stateless/02381_compress_marks_and_primary_key.sql index 0f1b4f638cb..327a09cd96f 100644 --- a/tests/queries/0_stateless/02381_compress_marks_and_primary_key.sql +++ b/tests/queries/0_stateless/02381_compress_marks_and_primary_key.sql @@ -1,4 +1,4 @@ --- Tags: no-backward-compatibility-check +-- Tags: no-upgrade-check drop table if exists test_02381; create table test_02381(a UInt64, b UInt64) ENGINE = MergeTree order by (a, b) SETTINGS compress_marks=false, compress_primary_key=false; diff --git a/tests/queries/0_stateless/02397_system_parts_race_condition_drop_rm.sh b/tests/queries/0_stateless/02397_system_parts_race_condition_drop_rm.sh index 2372d30497e..548179b94c9 100755 --- a/tests/queries/0_stateless/02397_system_parts_race_condition_drop_rm.sh +++ b/tests/queries/0_stateless/02397_system_parts_race_condition_drop_rm.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: race, zookeeper, no-parallel, no-backward-compatibility-check, disabled +# Tags: race, zookeeper, no-parallel, no-upgrade-check, disabled CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/02429_low_cardinality_trash.sh b/tests/queries/0_stateless/02429_low_cardinality_trash.sh index 258f02b4bb6..91618cb2796 100755 --- a/tests/queries/0_stateless/02429_low_cardinality_trash.sh +++ b/tests/queries/0_stateless/02429_low_cardinality_trash.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: long, no-backward-compatibility-check +# Tags: long, no-upgrade-check CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/02450_kill_distributed_query_deadlock.sh b/tests/queries/0_stateless/02450_kill_distributed_query_deadlock.sh index 11ca3f43d8f..abcf1bf4c5b 100755 --- a/tests/queries/0_stateless/02450_kill_distributed_query_deadlock.sh +++ b/tests/queries/0_stateless/02450_kill_distributed_query_deadlock.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: long, no-backward-compatibility-check +# Tags: long, no-upgrade-check CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From c7366f7906e28c4731234e15e1999a2011fe2e6c Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 3 Jan 2023 19:32:11 +0000 Subject: [PATCH 003/445] Automatic style fix --- tests/ci/stress.py | 8 +++----- tests/ci/stress_check.py | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/ci/stress.py b/tests/ci/stress.py index ae35afbc5fa..a1ad1a3f1c5 100755 --- a/tests/ci/stress.py +++ b/tests/ci/stress.py @@ -38,7 +38,7 @@ def get_options(i, upgrade_check): client_options.append("join_algorithm='full_sorting_merge'") if join_alg_num % 4 == 3: client_options.append("join_algorithm='auto'") - client_options.append('max_rows_in_join=1000') + client_options.append("max_rows_in_join=1000") if i == 13: client_options.append("memory_tracker_fault_probability=0.001") @@ -60,9 +60,7 @@ def run_func_test( global_time_limit, upgrade_check, ): - upgrade_check_option = ( - "--upgrade-check" if upgrade_check else "" - ) + upgrade_check_option = "--upgrade-check" if upgrade_check else "" global_time_limit_option = "" if global_time_limit: global_time_limit_option = "--global_time_limit={}".format(global_time_limit) @@ -165,7 +163,7 @@ def prepare_for_hung_check(drop_databases): for db in databases: if db == "system": continue - command = make_query_command(f'DETACH DATABASE {db}') + command = make_query_command(f"DETACH DATABASE {db}") # we don't wait for drop Popen(command, shell=True) break diff --git a/tests/ci/stress_check.py b/tests/ci/stress_check.py index 1437d50f9c5..66905c60569 100644 --- a/tests/ci/stress_check.py +++ b/tests/ci/stress_check.py @@ -187,5 +187,6 @@ def run_stress_test(docker_image_name): if state == "error": sys.exit(1) + if __name__ == "__main__": run_stress_test("clickhouse/stress-test") From de4aca9c6b7a61e8152f9c3a00747f2a3f7a1442 Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 3 Jan 2023 20:06:43 +0000 Subject: [PATCH 004/445] Update workflows --- .github/workflows/pull_request.yml | 140 ++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index efb7d50dd28..9bdd6a44b21 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -2897,10 +2897,10 @@ jobs: - name: Set envs run: | cat >> "$GITHUB_ENV" << 'EOF' - TEMP_PATH=${{runner.temp}}/stress_thread + TEMP_PATH=${{runner.temp}}/stress_asan REPORTS_PATH=${{runner.temp}}/reports_dir CHECK_NAME=Stress test (asan) - REPO_COPY=${{runner.temp}}/stress_thread/ClickHouse + REPO_COPY=${{runner.temp}}/stress_asan/ClickHouse EOF - name: Download json reports uses: actions/download-artifact@v3 @@ -3059,6 +3059,142 @@ jobs: docker ps --quiet | xargs --no-run-if-empty docker kill ||: docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: sudo rm -fr "$TEMP_PATH" + ############################################################################################## + ######################################### UPGRADE CHECK ###################################### + ############################################################################################## + UpgradeCheckAsan: + needs: [BuilderDebAsan] + runs-on: [self-hosted, stress-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/upgrade_asan + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=Upgrade check (asan) + REPO_COPY=${{runner.temp}}/upgrade_asan/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v3 + with: + path: ${{ env.REPORTS_PATH }} + - name: Check out repository code + uses: ClickHouse/checkout@v1 + with: + clear-repository: true + - name: Upgrade check + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 upgrade_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" + UpgradeCheckTsan: + needs: [BuilderDebTsan] + # same as for stress test with tsan + runs-on: [self-hosted, func-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/upgrade_thread + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=Upgrade check (tsan) + REPO_COPY=${{runner.temp}}/upgrade_thread/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v3 + with: + path: ${{ env.REPORTS_PATH }} + - name: Check out repository code + uses: ClickHouse/checkout@v1 + with: + clear-repository: true + - name: Upgrade check + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 upgrade_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" + UpgradeCheckMsan: + needs: [BuilderDebMsan] + runs-on: [self-hosted, stress-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/upgrade_memory + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=Upgrade check (msan) + REPO_COPY=${{runner.temp}}/upgrade_memory/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v3 + with: + path: ${{ env.REPORTS_PATH }} + - name: Check out repository code + uses: ClickHouse/checkout@v1 + with: + clear-repository: true + - name: Upgrade check + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 upgrade_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" + UpgradeCheckDebug: + needs: [BuilderDebDebug] + runs-on: [self-hosted, stress-tester] + steps: + - name: Set envs + run: | + cat >> "$GITHUB_ENV" << 'EOF' + TEMP_PATH=${{runner.temp}}/upgrade_debug + REPORTS_PATH=${{runner.temp}}/reports_dir + CHECK_NAME=Upgrade check (debug) + REPO_COPY=${{runner.temp}}/upgrade_debug/ClickHouse + EOF + - name: Download json reports + uses: actions/download-artifact@v3 + with: + path: ${{ env.REPORTS_PATH }} + - name: Check out repository code + uses: ClickHouse/checkout@v1 + with: + clear-repository: true + - name: Upgrade check + run: | + sudo rm -fr "$TEMP_PATH" + mkdir -p "$TEMP_PATH" + cp -r "$GITHUB_WORKSPACE" "$TEMP_PATH" + cd "$REPO_COPY/tests/ci" + python3 upgrade_check.py "$CHECK_NAME" + - name: Cleanup + if: always() + run: | + docker ps --quiet | xargs --no-run-if-empty docker kill ||: + docker ps --all --quiet | xargs --no-run-if-empty docker rm -f ||: + sudo rm -fr "$TEMP_PATH" ############################################################################################## ##################################### AST FUZZERS ############################################ ############################################################################################## From 9fb8b42d376fbfd996e12cb927e0f07143a6a94b Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 3 Jan 2023 20:13:12 +0000 Subject: [PATCH 005/445] Fix style --- tests/ci/stress.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ci/stress.py b/tests/ci/stress.py index ae35afbc5fa..337e127cfe6 100755 --- a/tests/ci/stress.py +++ b/tests/ci/stress.py @@ -11,7 +11,7 @@ import time def get_options(i, upgrade_check): options = [] client_options = [] - if 0 < i: + if i > 0: options.append("--order=random") if i % 3 == 2 and not upgrade_check: @@ -72,8 +72,8 @@ def run_func_test( for i in range(num_processes) ] pipes = [] - for i in range(0, len(output_paths)): - f = open(output_paths[i], "w") + for i, path in enumerate(output_paths): + f = open(path, "w") full_command = "{} {} {} {} {} --stress".format( cmd, get_options(i, upgrade_check), @@ -295,7 +295,7 @@ if __name__ == "__main__": res = call(cmd, shell=True, stderr=STDOUT) hung_check_status = "No queries hung\tOK\n" if res != 0 and have_long_running_queries: - logging.info("Hung check failed with exit code {}".format(res)) + logging.info("Hung check failed with exit code %d", res) hung_check_status = "Hung check failed\tFAIL\n" with open( os.path.join(args.output_folder, "test_results.tsv"), "w+" From 399c9aa2354c04ff1b30c60357be26a52d3db0a6 Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 4 Jan 2023 00:04:38 +0000 Subject: [PATCH 006/445] Update images.json --- docker/images.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/images.json b/docker/images.json index 8339205b52f..634c2b0c22c 100644 --- a/docker/images.json +++ b/docker/images.json @@ -55,6 +55,10 @@ "name": "clickhouse/stress-test", "dependent": [] }, + "docker/test/upgrade": { + "name": "clickhouse/upgrade-check", + "dependent": [] + }, "docker/test/split_build_smoke_test": { "name": "clickhouse/split-build-smoke-test", "dependent": [] From f1ca7e54d567ca9e229a041ff152a66b722c3715 Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 4 Jan 2023 15:06:16 +0000 Subject: [PATCH 007/445] Make better --- docker/test/ci | 305 --------------------------------- docker/test/stress/Dockerfile | 3 +- docker/test/upgrade/Dockerfile | 3 +- docker/test/upgrade/run.sh | 1 + 4 files changed, 5 insertions(+), 307 deletions(-) delete mode 100755 docker/test/ci diff --git a/docker/test/ci b/docker/test/ci deleted file mode 100755 index d1860e9e14b..00000000000 --- a/docker/test/ci +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from multiprocessing import cpu_count -from subprocess import Popen, call, check_output, STDOUT -import os -import argparse -import logging -import time - - -def get_options(i, backward_compatibility_check): - options = [] - client_options = [] - if 0 < i: - options.append("--order=random") - - if i % 3 == 2 and not backward_compatibility_check: - options.append( - '''--db-engine="Replicated('/test/db/test_{}', 's1', 'r1')"'''.format(i) - ) - client_options.append("allow_experimental_database_replicated=1") - - # If database name is not specified, new database is created for each functional test. - # Run some threads with one database for all tests. - if i % 2 == 1: - options.append(" --database=test_{}".format(i)) - - if i % 3 == 1: - client_options.append("join_use_nulls=1") - - if i % 2 == 1: - join_alg_num = i // 2 - if join_alg_num % 4 == 0: - client_options.append("join_algorithm='parallel_hash'") - if join_alg_num % 4 == 1: - client_options.append("join_algorithm='partial_merge'") - if join_alg_num % 4 == 2: - client_options.append("join_algorithm='full_sorting_merge'") - if join_alg_num % 4 == 3: - client_options.append("join_algorithm='auto'") - client_options.append('max_rows_in_join=1000') - - if i == 13: - client_options.append("memory_tracker_fault_probability=0.001") - - if i % 2 == 1 and not backward_compatibility_check: - client_options.append("group_by_use_nulls=1") - - if client_options: - options.append(" --client-option " + " ".join(client_options)) - - return " ".join(options) - - -def run_func_test( - cmd, - output_prefix, - num_processes, - skip_tests_option, - global_time_limit, - backward_compatibility_check, -): - backward_compatibility_check_option = ( - "--backward-compatibility-check" if backward_compatibility_check else "" - ) - global_time_limit_option = "" - if global_time_limit: - global_time_limit_option = "--global_time_limit={}".format(global_time_limit) - - output_paths = [ - os.path.join(output_prefix, "stress_test_run_{}.txt".format(i)) - for i in range(num_processes) - ] - pipes = [] - for i in range(0, len(output_paths)): - f = open(output_paths[i], "w") - full_command = "{} {} {} {} {} --stress".format( - cmd, - get_options(i, backward_compatibility_check), - global_time_limit_option, - skip_tests_option, - backward_compatibility_check_option, - ) - logging.info("Run func tests '%s'", full_command) - p = Popen(full_command, shell=True, stdout=f, stderr=f) - pipes.append(p) - time.sleep(0.5) - return pipes - - -def compress_stress_logs(output_path, files_prefix): - cmd = f"cd {output_path} && tar -zcf stress_run_logs.tar.gz {files_prefix}* && rm {files_prefix}*" - check_output(cmd, shell=True) - - -def call_with_retry(query, timeout=30, retry_count=5): - for i in range(retry_count): - code = call(query, shell=True, stderr=STDOUT, timeout=timeout) - if code != 0: - time.sleep(i) - else: - break - - -def make_query_command(query): - return f"""clickhouse client -q "{query}" --max_untracked_memory=1Gi --memory_profiler_step=1Gi --max_memory_usage_for_user=0""" - - -def prepare_for_hung_check(drop_databases): - # FIXME this function should not exist, but... - - # We attach gdb to clickhouse-server before running tests - # to print stacktraces of all crashes even if clickhouse cannot print it for some reason. - # However, it obstruct checking for hung queries. - logging.info("Will terminate gdb (if any)") - call_with_retry("kill -TERM $(pidof gdb)") - - # ThreadFuzzer significantly slows down server and causes false-positive hung check failures - call_with_retry("clickhouse client -q 'SYSTEM STOP THREAD FUZZER'") - - call_with_retry(make_query_command("SELECT 1 FORMAT Null")) - - # Some tests execute SYSTEM STOP MERGES or similar queries. - # It may cause some ALTERs to hang. - # Possibly we should fix tests and forbid to use such queries without specifying table. - call_with_retry(make_query_command("SYSTEM START MERGES")) - call_with_retry(make_query_command("SYSTEM START DISTRIBUTED SENDS")) - call_with_retry(make_query_command("SYSTEM START TTL MERGES")) - call_with_retry(make_query_command("SYSTEM START MOVES")) - call_with_retry(make_query_command("SYSTEM START FETCHES")) - call_with_retry(make_query_command("SYSTEM START REPLICATED SENDS")) - call_with_retry(make_query_command("SYSTEM START REPLICATION QUEUES")) - call_with_retry(make_query_command("SYSTEM DROP MARK CACHE")) - - # Issue #21004, live views are experimental, so let's just suppress it - call_with_retry(make_query_command("KILL QUERY WHERE upper(query) LIKE 'WATCH %'")) - - # Kill other queries which known to be slow - # It's query from 01232_preparing_sets_race_condition_long, it may take up to 1000 seconds in slow builds - call_with_retry( - make_query_command("KILL QUERY WHERE query LIKE 'insert into tableB select %'") - ) - # Long query from 00084_external_agregation - call_with_retry( - make_query_command( - "KILL QUERY WHERE query LIKE 'SELECT URL, uniq(SearchPhrase) AS u FROM test.hits GROUP BY URL ORDER BY u %'" - ) - ) - - if drop_databases: - for i in range(5): - try: - # Here we try to drop all databases in async mode. If some queries really hung, than drop will hung too. - # Otherwise we will get rid of queries which wait for background pool. It can take a long time on slow builds (more than 900 seconds). - # - # Also specify max_untracked_memory to allow 1GiB of memory to overcommit. - databases = ( - check_output( - make_query_command("SHOW DATABASES"), shell=True, timeout=30 - ) - .decode("utf-8") - .strip() - .split() - ) - for db in databases: - if db == "system": - continue - command = make_query_command(f'DETACH DATABASE {db}') - # we don't wait for drop - Popen(command, shell=True) - break - except Exception as ex: - logging.error( - "Failed to SHOW or DROP databasese, will retry %s", str(ex) - ) - time.sleep(i) - else: - raise Exception( - "Cannot drop databases after stress tests. Probably server consumed too much memory and cannot execute simple queries" - ) - - # Wait for last queries to finish if any, not longer than 300 seconds - call( - make_query_command( - """ - select sleepEachRow(( - select maxOrDefault(300 - elapsed) + 1 - from system.processes - where query not like '%from system.processes%' and elapsed < 300 - ) / 300) - from numbers(300) - format Null - """ - ), - shell=True, - stderr=STDOUT, - timeout=330, - ) - - # Even if all clickhouse-test processes are finished, there are probably some sh scripts, - # which still run some new queries. Let's ignore them. - try: - query = """clickhouse client -q "SELECT count() FROM system.processes where where elapsed > 300" """ - output = ( - check_output(query, shell=True, stderr=STDOUT, timeout=30) - .decode("utf-8") - .strip() - ) - if int(output) == 0: - return False - except: - pass - return True - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") - parser = argparse.ArgumentParser( - description="ClickHouse script for running stresstest" - ) - parser.add_argument("--test-cmd", default="/usr/bin/clickhouse-test") - parser.add_argument("--skip-func-tests", default="") - parser.add_argument("--client-cmd", default="clickhouse-client") - parser.add_argument("--server-log-folder", default="/var/log/clickhouse-server") - parser.add_argument("--output-folder") - parser.add_argument("--global-time-limit", type=int, default=1800) - parser.add_argument("--num-parallel", type=int, default=cpu_count()) - parser.add_argument("--backward-compatibility-check", action="store_true") - parser.add_argument("--hung-check", action="store_true", default=False) - # make sense only for hung check - parser.add_argument("--drop-databases", action="store_true", default=False) - - args = parser.parse_args() - if args.drop_databases and not args.hung_check: - raise Exception("--drop-databases only used in hung check (--hung-check)") - func_pipes = [] - func_pipes = run_func_test( - args.test_cmd, - args.output_folder, - args.num_parallel, - args.skip_func_tests, - args.global_time_limit, - args.backward_compatibility_check, - ) - - logging.info("Will wait functests to finish") - while True: - retcodes = [] - for p in func_pipes: - if p.poll() is not None: - retcodes.append(p.returncode) - if len(retcodes) == len(func_pipes): - break - logging.info("Finished %s from %s processes", len(retcodes), len(func_pipes)) - time.sleep(5) - - logging.info("All processes finished") - - logging.info("Compressing stress logs") - compress_stress_logs(args.output_folder, "stress_test_run_") - logging.info("Logs compressed") - - if args.hung_check: - try: - have_long_running_queries = prepare_for_hung_check(args.drop_databases) - except Exception as ex: - have_long_running_queries = True - logging.error("Failed to prepare for hung check %s", str(ex)) - logging.info("Checking if some queries hung") - cmd = " ".join( - [ - args.test_cmd, - # Do not track memory allocations up to 1Gi, - # this will allow to ignore server memory limit (max_server_memory_usage) for this query. - # - # NOTE: memory_profiler_step should be also adjusted, because: - # - # untracked_memory_limit = min(settings.max_untracked_memory, settings.memory_profiler_step) - # - # NOTE: that if there will be queries with GROUP BY, this trick - # will not work due to CurrentMemoryTracker::check() from - # Aggregator code. - # But right now it should work, since neither hung check, nor 00001_select_1 has GROUP BY. - "--client-option", - "max_untracked_memory=1Gi", - "max_memory_usage_for_user=0", - "memory_profiler_step=1Gi", - # Use system database to avoid CREATE/DROP DATABASE queries - "--database=system", - "--hung-check", - "--stress", - "00001_select_1", - ] - ) - res = call(cmd, shell=True, stderr=STDOUT) - hung_check_status = "No queries hung\tOK\n" - if res != 0 and have_long_running_queries: - logging.info("Hung check failed with exit code {}".format(res)) - hung_check_status = "Hung check failed\tFAIL\n" - with open( - os.path.join(args.output_folder, "test_results.tsv"), "w+" - ) as results: - results.write(hung_check_status) - - logging.info("Stress test finished") diff --git a/docker/test/stress/Dockerfile b/docker/test/stress/Dockerfile index 2778b63774d..1cabea58a65 100644 --- a/docker/test/stress/Dockerfile +++ b/docker/test/stress/Dockerfile @@ -22,7 +22,8 @@ RUN apt-get update -y \ netcat-openbsd \ telnet \ llvm-9 \ - brotli + brotli \ + && apt-get clean COPY run.sh / diff --git a/docker/test/upgrade/Dockerfile b/docker/test/upgrade/Dockerfile index c98220b3403..a91088fb01e 100644 --- a/docker/test/upgrade/Dockerfile +++ b/docker/test/upgrade/Dockerfile @@ -22,7 +22,8 @@ RUN apt-get update -y \ netcat-openbsd \ telnet \ llvm-9 \ - brotli + brotli \ + && apt-get clean COPY run.sh / diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index 1a107b6df2a..c0a7f279588 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -30,6 +30,7 @@ function configure() /usr/share/clickhouse-test/config/install.sh # we mount tests folder from repo to /usr/share + ln -s /usr/share/clickhouse-test/ci/stress.py /usr/bin/stress ln -s /usr/share/clickhouse-test/clickhouse-test /usr/bin/clickhouse-test ln -s /usr/share/clickhouse-test/ci/download_release_packages.py /usr/bin/download_release_packages ln -s /usr/share/clickhouse-test/ci/get_previous_release_tag.py /usr/bin/get_previous_release_tag From c5ea27446ee15362ba16158511a6e501c7d087ac Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Sat, 21 Jan 2023 01:38:59 +0800 Subject: [PATCH 008/445] Fix query in stress script Signed-off-by: Pradeep Chhetri --- docker/test/stress/stress | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/stress/stress b/docker/test/stress/stress index 4afd2745526..0539ba2ce83 100755 --- a/docker/test/stress/stress +++ b/docker/test/stress/stress @@ -200,7 +200,7 @@ def prepare_for_hung_check(drop_databases): # Even if all clickhouse-test processes are finished, there are probably some sh scripts, # which still run some new queries. Let's ignore them. try: - query = """clickhouse client -q "SELECT count() FROM system.processes where where elapsed > 300" """ + query = """clickhouse client -q "SELECT count() FROM system.processes where elapsed > 300" """ output = ( check_output(query, shell=True, stderr=STDOUT, timeout=30) .decode("utf-8") From fd9b735505ce7bcd090e5a6edada8314c4ea05b1 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 20 Jan 2023 20:22:03 +0000 Subject: [PATCH 009/445] Merge with master, add some fixes --- docker/test/stress/run.sh | 2 +- docker/test/upgrade/run.sh | 62 +++++++++++++++++++++----------------- tests/ci/ci_config.py | 12 ++++++++ 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 01da3e014e2..5fc0705412f 100644 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -362,7 +362,7 @@ rg -Fa " received signal " /test_output/gdb.log > /dev/null \ for table in query_log trace_log do - clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | pigz > /test_output/$table.tsv.gz ||: + clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | zstd --threads=0 > /test_output/$table.tsv.zst ||: done tar -chf /test_output/coordination.tar /var/lib/clickhouse/coordination ||: diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index c0a7f279588..9f6ead84de9 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -53,9 +53,11 @@ function configure() echo "1" \ > /etc/clickhouse-server/config.d/asynchronous_metrics_update_period_s.xml + local total_mem total_mem=$(awk '/MemTotal/ { print $(NF-1) }' /proc/meminfo) # KiB total_mem=$(( total_mem*1024 )) # bytes + # Set maximum memory usage as half of total memory (less chance of OOM). # # But not via max_server_memory_usage but via max_memory_usage_for_user, @@ -68,21 +70,23 @@ function configure() # max_server_memory_usage will be hard limit, and queries that should be # executed regardless memory limits will use max_memory_usage_for_user=0, # instead of relying on max_untracked_memory - local max_server_mem - max_server_mem=$((total_mem*75/100)) # 75% - echo "Setting max_server_memory_usage=$max_server_mem" + + max_server_memory_usage_to_ram_ratio=0.5 + echo "Setting max_server_memory_usage_to_ram_ratio to ${max_server_memory_usage_to_ram_ratio}" cat > /etc/clickhouse-server/config.d/max_server_memory_usage.xml < - ${max_server_mem} + ${max_server_memory_usage_to_ram_ratio} EOL + local max_users_mem - max_users_mem=$((total_mem*50/100)) # 50% - echo "Setting max_memory_usage_for_user=$max_users_mem" + max_users_mem=$((total_mem*30/100)) # 30% + echo "Setting max_memory_usage_for_user=$max_users_mem and max_memory_usage for queries to 10G" cat > /etc/clickhouse-server/users.d/max_memory_usage_for_user.xml < + 10G ${max_users_mem} @@ -100,6 +104,13 @@ EOL --> $PWD +EOL + + # Let OOM killer terminate other processes before clickhouse-server: + cat > /etc/clickhouse-server/config.d/oom_score.xml < + -1000 + EOL # Analyzer is not yet ready for testing @@ -121,18 +132,12 @@ EOL function stop() { + local max_tries="${1:-90}" local pid # Preserve the pid, since the server can hung after the PID will be deleted. pid="$(cat /var/run/clickhouse-server/clickhouse-server.pid)" - clickhouse stop $max_tries --do-not-kill && return - - if [ -n "$1" ] - then - # temporarily disable it in BC check - clickhouse stop --force - return - fi + clickhouse stop --max-tries "$max_tries" --do-not-kill && return # We failed to stop the server with SIGTERM. Maybe it hang, let's collect stacktraces. kill -TERM "$(pidof gdb)" ||: @@ -322,7 +327,8 @@ else clickhouse stop --force ) - stop 1 + # Use bigger timeout for previous version + stop 300 mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.stress.log # Start new server @@ -334,7 +340,7 @@ else start 500 clickhouse-client --query "SELECT 'Server successfully started', 'OK'" >> /test_output/test_results.tsv \ || (echo -e 'Server failed to start\tFAIL' >> /test_output/test_results.tsv \ - && grep -a ".*Application" /var/log/clickhouse-server/clickhouse-server.log >> /test_output/application_errors.txt) + && rg --text ".*Application" /var/log/clickhouse-server/clickhouse-server.log >> /test_output/application_errors.txt) # Remove file application_errors.txt if it's empty [ -s /test_output/application_errors.txt ] || rm /test_output/application_errors.txt @@ -357,7 +363,7 @@ else # ("This engine is deprecated and is not supported in transactions", "[Queue = DB::MergeMutateRuntimeQueue]: Code: 235. DB::Exception: Part") # FIXME https://github.com/ClickHouse/ClickHouse/issues/39174 - bad mutation does not indicate backward incompatibility echo "Check for Error messages in server log:" - zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" \ + rg -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" \ -e "Code: 236. DB::Exception: Cancelled mutating parts" \ -e "REPLICA_IS_ALREADY_ACTIVE" \ -e "REPLICA_ALREADY_EXISTS" \ @@ -401,21 +407,21 @@ else [ -s /test_output/upgrade_error_messages.txt ] || rm /test_output/upgrade_error_messages.txt # Sanitizer asserts - zgrep -Fa "==================" /var/log/clickhouse-server/stderr.log >> /test_output/tmp - zgrep -Fa "WARNING" /var/log/clickhouse-server/stderr.log >> /test_output/tmp - zgrep -Fav -e "ASan doesn't fully support makecontext/swapcontext functions" -e "DB::Exception" /test_output/tmp > /dev/null \ + rg -Fa "==================" /var/log/clickhouse-server/stderr.log >> /test_output/tmp + rg -Fa "WARNING" /var/log/clickhouse-server/stderr.log >> /test_output/tmp + rg -Fav -e "ASan doesn't fully support makecontext/swapcontext functions" -e "DB::Exception" /test_output/tmp > /dev/null \ && echo -e 'Sanitizer assert (in stderr.log)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'No sanitizer asserts\tOK' >> /test_output/test_results.tsv rm -f /test_output/tmp # OOM - zgrep -Fa " Application: Child process was terminated by signal 9" /var/log/clickhouse-server/clickhouse-server.*.log > /dev/null \ + rg -Fa " Application: Child process was terminated by signal 9" /var/log/clickhouse-server/clickhouse-server.*.log > /dev/null \ && echo -e 'OOM killer (or signal 9) in clickhouse-server.log\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'No OOM messages in clickhouse-server.log\tOK' >> /test_output/test_results.tsv # Logical errors echo "Check for Logical errors in server log:" - zgrep -Fa -A20 "Code: 49, e.displayText() = DB::Exception:" /var/log/clickhouse-server/clickhouse-server.*.log > /test_output/logical_errors.txt \ + rg -Fa -A20 "Code: 49, e.displayText() = DB::Exception:" /var/log/clickhouse-server/clickhouse-server.*.log > /test_output/logical_errors.txt \ && echo -e 'Logical error thrown (see server logs or logical_errors.txt)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'No logical errors\tOK' >> /test_output/test_results.tsv @@ -423,13 +429,13 @@ else [ -s /test_output/logical_errors.txt ] || rm /test_output/logical_errors.txt # Crash - zgrep -Fa "########################################" /var/log/clickhouse-server/clickhouse-server.*.log > /dev/null \ + rg -Fa "########################################" /var/log/clickhouse-server/clickhouse-server.*.log > /dev/null \ && echo -e 'Killed by signal (in server logs)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'Not crashed\tOK' >> /test_output/test_results.tsv # It also checks for crash without stacktrace (printed by watchdog) echo "Check for Fatal message in server log:" - zgrep -Fa " " /var/log/clickhouse-server/clickhouse-server.*.log > /test_output/fatal_messages.txt \ + rg -Fa " " /var/log/clickhouse-server/clickhouse-server.*.log > /test_output/fatal_messages.txt \ && echo -e 'Fatal message in server logs (see bc_check_fatal_messages.txt)\tFAIL' >> /test_output/test_results.tsv \ || echo -e 'No fatal messages in server logs\tOK' >> /test_output/test_results.tsv @@ -439,7 +445,7 @@ else tar -chf /test_output/coordination.tar /var/lib/clickhouse/coordination ||: for table in query_log trace_log do - clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | pigz > /test_output/$table.backward.tsv.gz ||: + clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" | zstd --threads=0 > /test_output/$table.backward.tsv.zst ||: done fi @@ -457,7 +463,7 @@ clickhouse-local --structure "test String, res String" -q "SELECT 'failure', tes [ -s /test_output/check_status.tsv ] || echo -e "success\tNo errors found" > /test_output/check_status.tsv # Core dumps -for core in core.*; do - pigz $core - mv $core.gz /test_output/ +find . -type f -maxdepth 1 -name 'core.*' | while read core; do + zstd --threads=0 $core + mv $core.zst /test_output/ done diff --git a/tests/ci/ci_config.py b/tests/ci/ci_config.py index c77acfb679f..11a9a24d8a4 100644 --- a/tests/ci/ci_config.py +++ b/tests/ci/ci_config.py @@ -263,6 +263,18 @@ CI_CONFIG = { "Stress test (debug)": { "required_build": "package_debug", }, + "Upgrade check (asan)": { + "required_build": "package_asan", + }, + "Upgrade check (tsan)": { + "required_build": "package_tsan", + }, + "Upgrade check (msan)": { + "required_build": "package_msan", + }, + "Upgrade check (debug)": { + "required_build": "package_debug", + }, "Integration tests (asan)": { "required_build": "package_asan", }, From 09951c70c4194b1e9265c2757a159a9e84068581 Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 24 Jan 2023 16:45:07 +0000 Subject: [PATCH 010/445] Fix --- docker/test/upgrade/run.sh | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index 9f6ead84de9..503744e8baf 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -29,12 +29,6 @@ function configure() export EXPORT_S3_STORAGE_POLICIES=1 /usr/share/clickhouse-test/config/install.sh - # we mount tests folder from repo to /usr/share - ln -s /usr/share/clickhouse-test/ci/stress.py /usr/bin/stress - ln -s /usr/share/clickhouse-test/clickhouse-test /usr/bin/clickhouse-test - ln -s /usr/share/clickhouse-test/ci/download_release_packages.py /usr/bin/download_release_packages - ln -s /usr/share/clickhouse-test/ci/get_previous_release_tag.py /usr/bin/get_previous_release_tag - # avoid too slow startup sudo cat /etc/clickhouse-server/config.d/keeper_port.xml | sed "s|100000|10000|" > /etc/clickhouse-server/config.d/keeper_port.xml.tmp sudo mv /etc/clickhouse-server/config.d/keeper_port.xml.tmp /etc/clickhouse-server/config.d/keeper_port.xml @@ -241,6 +235,12 @@ mv /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml.tmp /etc/cli sudo chown clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml sudo chgrp clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml +# we mount tests folder from repo to /usr/share +ln -s /usr/share/clickhouse-test/ci/stress.py /usr/bin/stress +ln -s /usr/share/clickhouse-test/clickhouse-test /usr/bin/clickhouse-test +ln -s /usr/share/clickhouse-test/ci/download_release_packages.py /usr/bin/download_release_packages +ln -s /usr/share/clickhouse-test/ci/get_previous_release_tag.py /usr/bin/get_previous_release_tag + echo "Get previous release tag" previous_release_tag=$(dpkg --info package_folder/clickhouse-client*.deb | grep "Version: " | awk '{print $2}' | get_previous_release_tag) echo $previous_release_tag @@ -294,8 +294,7 @@ else clickhouse-client --query="SELECT 'Server version: ', version()" - # Install new package before running stress test because we should use new - # clickhouse-client and new clickhouse-test. + # Install new package before running stress test because we should use new clickhouse-client. # # But we should leave old binary in /usr/bin/ and debug symbols in # /usr/lib/debug/usr/bin (if any) for gdb and internal DWARF parser, so it @@ -458,6 +457,14 @@ grep -q -F -e 'Out of memory: Killed process' -e 'oom_reaper: reaped process' -e mv /var/log/clickhouse-server/stderr.log /test_output/ +# If we failed to clone repo or download previous release packages, +# we don't have any packages installed, but we need clickhouse-local +# to be installed to create check_status.tsv. +if ! command -v clickhouse-local &> /dev/null +then + install_packages package_folder +fi + # Write check result into check_status.tsv clickhouse-local --structure "test String, res String" -q "SELECT 'failure', test FROM table WHERE res != 'OK' order by (lower(test) like '%hung%'), rowNumberInAllBlocks() LIMIT 1" < /test_output/test_results.tsv > /test_output/check_status.tsv [ -s /test_output/check_status.tsv ] || echo -e "success\tNo errors found" > /test_output/check_status.tsv From 1202f12d01bc9268be93b2f5f6c9d624962c183c Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Wed, 25 Jan 2023 22:29:23 +0800 Subject: [PATCH 011/445] Add join_algorithm='grace_hash' to stress tests --- docker/test/stress/stress | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docker/test/stress/stress b/docker/test/stress/stress index 4afd2745526..49454b86e64 100755 --- a/docker/test/stress/stress +++ b/docker/test/stress/stress @@ -30,13 +30,15 @@ def get_options(i, backward_compatibility_check): if i % 2 == 1: join_alg_num = i // 2 - if join_alg_num % 4 == 0: + if join_alg_num % 5 == 0: client_options.append("join_algorithm='parallel_hash'") - if join_alg_num % 4 == 1: + if join_alg_num % 5 == 1: client_options.append("join_algorithm='partial_merge'") - if join_alg_num % 4 == 2: + if join_alg_num % 5 == 2: client_options.append("join_algorithm='full_sorting_merge'") - if join_alg_num % 4 == 3: + if join_alg_num % 5 == 3: + client_options.append("join_algorithm='grace_hash'") + if join_alg_num % 5 == 4: client_options.append("join_algorithm='auto'") client_options.append('max_rows_in_join=1000') From 61d836b79d32ac9aa1cd0970ef0c889a534bf113 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 25 Jan 2023 18:59:50 +0100 Subject: [PATCH 012/445] Update run.sh --- docker/test/upgrade/run.sh | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index 503744e8baf..95ed588e054 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -242,7 +242,7 @@ ln -s /usr/share/clickhouse-test/ci/download_release_packages.py /usr/bin/downlo ln -s /usr/share/clickhouse-test/ci/get_previous_release_tag.py /usr/bin/get_previous_release_tag echo "Get previous release tag" -previous_release_tag=$(dpkg --info package_folder/clickhouse-client*.deb | grep "Version: " | awk '{print $2}' | get_previous_release_tag) +previous_release_tag=$(dpkg --info package_folder/clickhouse-client*.deb | grep "Version: " | awk '{print $2}' | cut -f1 -d'+' | get_previous_release_tag) echo $previous_release_tag echo "Clone previous release repository" @@ -293,24 +293,7 @@ else start clickhouse-client --query="SELECT 'Server version: ', version()" - - # Install new package before running stress test because we should use new clickhouse-client. - # - # But we should leave old binary in /usr/bin/ and debug symbols in - # /usr/lib/debug/usr/bin (if any) for gdb and internal DWARF parser, so it - # will print sane stacktraces and also to avoid possible crashes. - # - # FIXME: those files can be extracted directly from debian package, but - # actually better solution will be to use different PATH instead of playing - # games with files from packages. - mv /usr/bin/clickhouse previous_release_package_folder/ - mv /usr/lib/debug/usr/bin/clickhouse.debug previous_release_package_folder/ - install_packages package_folder - mv /usr/bin/clickhouse package_folder/ - mv /usr/lib/debug/usr/bin/clickhouse.debug package_folder/ - mv previous_release_package_folder/clickhouse /usr/bin/ - mv previous_release_package_folder/clickhouse.debug /usr/lib/debug/usr/bin/clickhouse.debug - + mkdir tmp_stress_output stress --test-cmd="/usr/bin/clickhouse-test --queries=\"previous_release_repository/tests/queries\"" --upgrade-check --output-folder tmp_stress_output --global-time-limit=1200 \ @@ -330,9 +313,8 @@ else stop 300 mv /var/log/clickhouse-server/clickhouse-server.log /var/log/clickhouse-server/clickhouse-server.stress.log - # Start new server - mv package_folder/clickhouse /usr/bin/ - mv package_folder/clickhouse.debug /usr/lib/debug/usr/bin/clickhouse.debug + # Install and start new server + install_packages package_folder # Disable fault injections on start (we don't test them here, and it can lead to tons of requests in case of huge number of tables). export ZOOKEEPER_FAULT_INJECTION=0 configure From dc0c1c33bb6364e0caafed2cc6938c8f7c1d1a13 Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 26 Jan 2023 14:50:59 +0000 Subject: [PATCH 013/445] Update --- docker/test/upgrade/run.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index 95ed588e054..37357297d08 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -152,7 +152,7 @@ function start() echo -e "Cannot start clickhouse-server\tFAIL" >> /test_output/test_results.tsv cat /var/log/clickhouse-server/stdout.log tail -n1000 /var/log/clickhouse-server/stderr.log - tail -n100000 /var/log/clickhouse-server/clickhouse-server.log | grep -F -v -e ' RaftInstance:' -e ' RaftInstance' | tail -n1000 + tail -n100000 /var/log/clickhouse-server/clickhouse-server.log | rg -F -v -e ' RaftInstance:' -e ' RaftInstance' | tail -n1000 break fi # use root to match with current uid @@ -229,12 +229,6 @@ fi azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --debug /azurite_log & ./setup_minio.sh stateless # to have a proper environment -# But we still need default disk because some tables loaded only into it -sudo cat /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml | sed "s|
s3
|
s3
default|" > /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml.tmp -mv /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml.tmp /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml -sudo chown clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml -sudo chgrp clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml - # we mount tests folder from repo to /usr/share ln -s /usr/share/clickhouse-test/ci/stress.py /usr/bin/stress ln -s /usr/share/clickhouse-test/clickhouse-test /usr/bin/clickhouse-test @@ -277,6 +271,12 @@ else export ZOOKEEPER_FAULT_INJECTION=0 configure + # But we still need default disk because some tables loaded only into it + sudo cat /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml | sed "s|
s3
|
s3
default|" > /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml.tmp + mv /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml.tmp /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml + sudo chown clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml + sudo chgrp clickhouse /etc/clickhouse-server/config.d/s3_storage_policy_by_default.xml + # Avoid "Setting s3_check_objects_after_upload is neither a builtin setting..." rm -f /etc/clickhouse-server/users.d/enable_blobs_check.xml ||: rm -f /etc/clickhouse-server/users.d/marks.xml ||: From 0a99d421f58a0476ba035a13a14308615c093374 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 27 Jan 2023 13:22:50 +0000 Subject: [PATCH 014/445] Fix formats parser resetting, test processing bad messages in kafka --- .../Executors/StreamingFormatExecutor.cpp | 6 + .../Executors/StreamingFormatExecutor.h | 2 +- .../Impl/BSONEachRowRowInputFormat.cpp | 8 + .../Formats/Impl/BSONEachRowRowInputFormat.h | 2 +- .../Formats/Impl/CSVRowInputFormat.cpp | 6 + .../Formats/Impl/CSVRowInputFormat.h | 1 + .../Formats/Impl/ProtobufRowInputFormat.cpp | 40 +++-- .../Formats/Impl/ProtobufRowInputFormat.h | 8 + .../Impl/TabSeparatedRowInputFormat.cpp | 6 + .../Formats/Impl/TabSeparatedRowInputFormat.h | 1 + src/Storages/Kafka/KafkaSource.cpp | 2 +- tests/integration/helpers/cluster.py | 4 + tests/integration/test_storage_kafka/test.py | 165 ++++++++++++++++++ 13 files changed, 237 insertions(+), 14 deletions(-) diff --git a/src/Processors/Executors/StreamingFormatExecutor.cpp b/src/Processors/Executors/StreamingFormatExecutor.cpp index 91db2c09a20..cfe88344198 100644 --- a/src/Processors/Executors/StreamingFormatExecutor.cpp +++ b/src/Processors/Executors/StreamingFormatExecutor.cpp @@ -93,6 +93,12 @@ size_t StreamingFormatExecutor::execute() format->resetParser(); return on_error(result_columns, e); } + catch (std::exception & e) + { + format->resetParser(); + auto exception = Exception(Exception::CreateFromSTDTag{}, e); + return on_error(result_columns, exception); + } } } diff --git a/src/Processors/Executors/StreamingFormatExecutor.h b/src/Processors/Executors/StreamingFormatExecutor.h index f948d833095..46f22d6deb6 100644 --- a/src/Processors/Executors/StreamingFormatExecutor.h +++ b/src/Processors/Executors/StreamingFormatExecutor.h @@ -24,7 +24,7 @@ public: StreamingFormatExecutor( const Block & header_, InputFormatPtr format_, - ErrorCallback on_error_ = [](const MutableColumns &, Exception &) -> size_t { throw; }, + ErrorCallback on_error_ = [](const MutableColumns &, Exception & e) -> size_t { throw e; }, SimpleTransformPtr adding_defaults_transform_ = nullptr); /// Returns numbers of new read rows. diff --git a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp index 349b063fabe..7ff27c0fd7d 100644 --- a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.cpp @@ -773,6 +773,14 @@ bool BSONEachRowRowInputFormat::readRow(MutableColumns & columns, RowReadExtensi return true; } +void BSONEachRowRowInputFormat::resetParser() +{ + IRowInputFormat::resetParser(); + read_columns.clear(); + seen_columns.clear(); + prev_positions.clear(); +} + BSONEachRowSchemaReader::BSONEachRowSchemaReader(ReadBuffer & in_, const FormatSettings & settings_) : IRowWithNamesSchemaReader(in_, settings_) { diff --git a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h index d0830ca2781..ad6d712b6dd 100644 --- a/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h +++ b/src/Processors/Formats/Impl/BSONEachRowRowInputFormat.h @@ -54,7 +54,7 @@ public: ReadBuffer & in_, const Block & header_, Params params_, const FormatSettings & format_settings_); String getName() const override { return "BSONEachRowRowInputFormat"; } - void resetParser() override { } + void resetParser() override; private: void readPrefix() override { } diff --git a/src/Processors/Formats/Impl/CSVRowInputFormat.cpp b/src/Processors/Formats/Impl/CSVRowInputFormat.cpp index 20b0c204c8e..5f18d1f0c14 100644 --- a/src/Processors/Formats/Impl/CSVRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/CSVRowInputFormat.cpp @@ -102,6 +102,12 @@ void CSVRowInputFormat::setReadBuffer(ReadBuffer & in_) buf->setSubBuffer(in_); } +void CSVRowInputFormat::resetParser() +{ + RowInputFormatWithNamesAndTypes::resetParser(); + buf->reset(); +} + static void skipEndOfLine(ReadBuffer & in) { /// \n (Unix) or \r\n (DOS/Windows) or \n\r (Mac OS Classic) diff --git a/src/Processors/Formats/Impl/CSVRowInputFormat.h b/src/Processors/Formats/Impl/CSVRowInputFormat.h index 86f7fe3466c..f51f674e4af 100644 --- a/src/Processors/Formats/Impl/CSVRowInputFormat.h +++ b/src/Processors/Formats/Impl/CSVRowInputFormat.h @@ -27,6 +27,7 @@ public: String getName() const override { return "CSVRowInputFormat"; } void setReadBuffer(ReadBuffer & in_) override; + void resetParser() override; protected: CSVRowInputFormat(const Block & header_, std::shared_ptr in_, const Params & params_, diff --git a/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp b/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp index 40f6a2a54a7..ee60501dba5 100644 --- a/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/ProtobufRowInputFormat.cpp @@ -13,21 +13,31 @@ namespace DB ProtobufRowInputFormat::ProtobufRowInputFormat(ReadBuffer & in_, const Block & header_, const Params & params_, const FormatSchemaInfo & schema_info_, bool with_length_delimiter_, bool flatten_google_wrappers_) : IRowInputFormat(header_, in_, params_) - , reader(std::make_unique(in_)) - , serializer(ProtobufSerializer::create( - header_.getNames(), - header_.getDataTypes(), - missing_column_indices, - *ProtobufSchemas::instance().getMessageTypeForFormatSchema(schema_info_, ProtobufSchemas::WithEnvelope::No), - with_length_delimiter_, - /* with_envelope = */ false, - flatten_google_wrappers_, - *reader)) + , message_descriptor(ProtobufSchemas::instance().getMessageTypeForFormatSchema(schema_info_, ProtobufSchemas::WithEnvelope::No)) + , with_length_delimiter(with_length_delimiter_) + , flatten_google_wrappers(flatten_google_wrappers_) { } +void ProtobufRowInputFormat::createReaderAndSerializer() +{ + reader = std::make_unique(*in); + serializer = ProtobufSerializer::create( + getPort().getHeader().getNames(), + getPort().getHeader().getDataTypes(), + missing_column_indices, + *message_descriptor, + with_length_delimiter, + /* with_envelope = */ false, + flatten_google_wrappers, + *reader); +} + bool ProtobufRowInputFormat::readRow(MutableColumns & columns, RowReadExtension & row_read_extension) { + if (!reader) + createReaderAndSerializer(); + if (reader->eof()) return false; @@ -46,7 +56,8 @@ bool ProtobufRowInputFormat::readRow(MutableColumns & columns, RowReadExtension void ProtobufRowInputFormat::setReadBuffer(ReadBuffer & in_) { - reader->setReadBuffer(in_); + if (reader) + reader->setReadBuffer(in_); IRowInputFormat::setReadBuffer(in_); } @@ -60,6 +71,13 @@ void ProtobufRowInputFormat::syncAfterError() reader->endMessage(true); } +void ProtobufRowInputFormat::resetParser() +{ + IRowInputFormat::resetParser(); + serializer.reset(); + reader.reset(); +} + void registerInputFormatProtobuf(FormatFactory & factory) { for (bool with_length_delimiter : {false, true}) diff --git a/src/Processors/Formats/Impl/ProtobufRowInputFormat.h b/src/Processors/Formats/Impl/ProtobufRowInputFormat.h index 2e0ed49b768..5c042f7c5ab 100644 --- a/src/Processors/Formats/Impl/ProtobufRowInputFormat.h +++ b/src/Processors/Formats/Impl/ProtobufRowInputFormat.h @@ -6,6 +6,7 @@ # include # include # include +# include namespace DB { @@ -39,15 +40,22 @@ public: String getName() const override { return "ProtobufRowInputFormat"; } void setReadBuffer(ReadBuffer & in_) override; + void resetParser() override; private: bool readRow(MutableColumns & columns, RowReadExtension & row_read_extension) override; bool allowSyncAfterError() const override; void syncAfterError() override; + void createReaderAndSerializer(); + std::unique_ptr reader; std::vector missing_column_indices; std::unique_ptr serializer; + + const google::protobuf::Descriptor * message_descriptor; + bool with_length_delimiter; + bool flatten_google_wrappers; }; class ProtobufSchemaReader : public IExternalSchemaReader diff --git a/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.cpp b/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.cpp index 868639e66c2..6d86d710d21 100644 --- a/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.cpp @@ -70,6 +70,12 @@ void TabSeparatedRowInputFormat::setReadBuffer(ReadBuffer & in_) buf->setSubBuffer(in_); } +void TabSeparatedRowInputFormat::resetParser() +{ + RowInputFormatWithNamesAndTypes::resetParser(); + buf->reset(); +} + TabSeparatedFormatReader::TabSeparatedFormatReader(PeekableReadBuffer & in_, const FormatSettings & format_settings_, bool is_raw_) : FormatWithNamesAndTypesReader(in_, format_settings_), buf(&in_), is_raw(is_raw_) { diff --git a/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.h b/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.h index 9edcf86b5de..0f4bff8d7d0 100644 --- a/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.h +++ b/src/Processors/Formats/Impl/TabSeparatedRowInputFormat.h @@ -23,6 +23,7 @@ public: String getName() const override { return "TabSeparatedRowInputFormat"; } void setReadBuffer(ReadBuffer & in_) override; + void resetParser() override; private: TabSeparatedRowInputFormat(const Block & header_, std::unique_ptr in_, const Params & params_, diff --git a/src/Storages/Kafka/KafkaSource.cpp b/src/Storages/Kafka/KafkaSource.cpp index c456ab1550a..880935b8c8f 100644 --- a/src/Storages/Kafka/KafkaSource.cpp +++ b/src/Storages/Kafka/KafkaSource.cpp @@ -133,7 +133,7 @@ Chunk KafkaSource::generateImpl() { e.addMessage("while parsing Kafka message (topic: {}, partition: {}, offset: {})'", consumer->currentTopic(), consumer->currentPartition(), consumer->currentOffset()); - throw; + throw e; } }; diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 3a65a517d9c..548382a7050 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -4386,6 +4386,10 @@ class ClickHouseInstance: objects = objects + self.get_s3_data_objects(path) return objects + def create_format_schema(self, file_name, content): + self.exec_in_container( + ["bash", "-c", "echo '{}' > {}".format(content, "/var/lib/clickhouse/format_schemas/" + file_name)] + ) class ClickHouseKiller(object): def __init__(self, clickhouse_node): diff --git a/tests/integration/test_storage_kafka/test.py b/tests/integration/test_storage_kafka/test.py index 9f617369859..9e79b0bc55f 100644 --- a/tests/integration/test_storage_kafka/test.py +++ b/tests/integration/test_storage_kafka/test.py @@ -4440,6 +4440,171 @@ def test_block_based_formats_2(kafka_cluster): kafka_delete_topic(admin_client, format_name) +def test_bad_messages_parsing(kafka_cluster): + admin_client = KafkaAdminClient( + bootstrap_servers="localhost:{}".format(kafka_cluster.kafka_port) + ) + + for format_name in [ + "TSV", + "TSKV", + "CSV", + "Values", + "JSON", + "JSONEachRow", + "JSONCompactEachRow", + "JSONObjectEachRow", + "Avro", + "RowBinary", + "JSONColumns", + "JSONColumnsWithMetadata", + "Native", + "Arrow", + "ArrowStream", + "Parquet", + "ORC", + "JSONCompactColumns", + "BSONEachRow", + "MySQLDump", + ]: + + print(format_name) + + kafka_create_topic(admin_client, f'{format_name}_err') + + instance.query( + f""" + DROP TABLE IF EXISTS test.view; + DROP TABLE IF EXISTS test.kafka; + + CREATE TABLE test.kafka (key UInt64, value UInt64) + ENGINE = Kafka + SETTINGS kafka_broker_list = 'kafka1:19092', + kafka_topic_list = '{format_name}_err', + kafka_group_name = '{format_name}', + kafka_format = '{format_name}', + kafka_handle_error_mode='stream'; + + CREATE MATERIALIZED VIEW test.view Engine=Log AS + SELECT _error FROM test.kafka WHERE length(_error) != 0 ; + """ + ) + + messages = ["qwertyuiop", "asdfghjkl", "zxcvbnm"] + kafka_produce(kafka_cluster, f'{format_name}_err', messages) + + attempt = 0 + rows = 0 + while attempt < 500: + rows = int(instance.query("SELECT count() FROM test.view")) + if rows == len(messages): + break + attempt += 1 + + assert rows == len(messages) + + kafka_delete_topic(admin_client, f'{format_name}_err') + + protobuf_schema=""" +syntax = "proto3"; + +message Message { + uint64 key = 1; + uint64 value = 2; +}; +""" + + instance.create_format_schema("schema_test_errors.proto", protobuf_schema) + + for format_name in ["Protobuf", "ProtobufSingle", "ProtobufList"]: + instance.query( + f""" + DROP TABLE IF EXISTS test.view; + DROP TABLE IF EXISTS test.kafka; + + CREATE TABLE test.kafka (key UInt64, value UInt64) + ENGINE = Kafka + SETTINGS kafka_broker_list = 'kafka1:19092', + kafka_topic_list = '{format_name}_err', + kafka_group_name = '{format_name}', + kafka_format = '{format_name}', + kafka_handle_error_mode='stream', + kafka_schema='schema_test_errors:Message'; + + CREATE MATERIALIZED VIEW test.view Engine=Log AS + SELECT _error FROM test.kafka WHERE length(_error) != 0 ; + """ + ) + + print(format_name) + + kafka_create_topic(admin_client, f'{format_name}_err') + + messages = ["qwertyuiop", "poiuytrewq", "zxcvbnm"] + kafka_produce(kafka_cluster, f'{format_name}_err', messages) + + attempt = 0 + rows = 0 + while attempt < 500: + rows = int(instance.query("SELECT count() FROM test.view")) + if rows == len(messages): + break + attempt += 1 + + assert rows == len(messages) + + kafka_delete_topic(admin_client, f'{format_name}_err') + + capn_proto_schema=""" +@0xd9dd7b35452d1c4f; + +struct Message +{ + key @0 : UInt64; + value @1 : UInt64; +} +""" + + instance.create_format_schema("schema_test_errors.capnp", capn_proto_schema) + instance.query( + f""" + DROP TABLE IF EXISTS test.view; + DROP TABLE IF EXISTS test.kafka; + + CREATE TABLE test.kafka (key UInt64, value UInt64) + ENGINE = Kafka + SETTINGS kafka_broker_list = 'kafka1:19092', + kafka_topic_list = 'CapnProto_err', + kafka_group_name = 'CapnProto', + kafka_format = 'CapnProto', + kafka_handle_error_mode='stream', + kafka_schema='schema_test_errors:Message'; + + CREATE MATERIALIZED VIEW test.view Engine=Log AS + SELECT _error FROM test.kafka WHERE length(_error) != 0 ; + """ + ) + + print("CapnProto") + + kafka_create_topic(admin_client, 'CapnProto_err') + + messages = ["qwertyuiop", "asdfghjkl", "zxcvbnm"] + kafka_produce(kafka_cluster, 'CapnProto_err', messages) + + attempt = 0 + rows = 0 + while attempt < 500: + rows = int(instance.query("SELECT count() FROM test.view")) + if rows == len(messages): + break + attempt += 1 + + assert rows == len(messages) + + kafka_delete_topic(admin_client, 'CapnProto_err') + + if __name__ == "__main__": cluster.start() input("Cluster created, press any key to destroy...") From 81875fe5591ee37f45b6681e1ff46714d96304ec Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Fri, 27 Jan 2023 14:45:10 +0100 Subject: [PATCH 015/445] Update run.sh --- docker/test/upgrade/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index 37357297d08..89731940dd2 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -267,6 +267,8 @@ else install_packages previous_release_package_folder # Start server from previous release + # Let's enable S3 storage by default + export USE_S3_STORAGE_FOR_MERGE_TREE=1 # Previous version may not be ready for fault injections export ZOOKEEPER_FAULT_INJECTION=0 configure From 8067f98103e6c366ecd5a976c85cfa69d199b715 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 1 Feb 2023 18:19:09 +0100 Subject: [PATCH 016/445] Add test which reproduce rename bug --- .../02538_alter_rename_sequence.reference | 0 .../02538_alter_rename_sequence.sql | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/queries/0_stateless/02538_alter_rename_sequence.reference create mode 100644 tests/queries/0_stateless/02538_alter_rename_sequence.sql diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.reference b/tests/queries/0_stateless/02538_alter_rename_sequence.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.sql b/tests/queries/0_stateless/02538_alter_rename_sequence.sql new file mode 100644 index 00000000000..efdb1d469eb --- /dev/null +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS wrong_metadata; + +CREATE TABLE wrong_metadata( + column1 UInt64, + column2 UInt64, + column3 UInt64 +) +ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata', '1') +ORDER BY tuple(); + +INSERT INTO wrong_metadata VALUES (1, 2, 3); + +SYSTEM STOP REPLICATION QUEUES wrong_metadata; + +ALTER TABLE wrong_metadata RENAME COLUMN column1 TO column1_renamed SETTINGS replication_alter_partitions_sync = 0; + +INSERT INTO wrong_metadata VALUES (4, 5, 6); + +SYSTEM START REPLICATION QUEUES wrong_metadata; + +SYSTEM SYNC REPLICA wrong_metadata; + +ALTER TABLE wrong_metadata RENAME COLUMN column2 to column2_renamed SETTINGS replication_alter_partitions_sync = 2; + +SELECT * FROM wrong_metadata ORDER BY column1_renamed FORMAT JSONEachRow; + +DROP TABLE IF EXISTS wrong_metadata; From 174bdba912565e05a5e6d41ce8d2f44c4848606f Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 16:57:46 +0100 Subject: [PATCH 017/445] Fix bugs in alter rename column --- src/Storages/MergeTree/AlterConversions.h | 2 + src/Storages/MergeTree/MergeTreeData.cpp | 2 + src/Storages/MergeTree/MutateTask.cpp | 60 +++++++++++++++++-- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 11 ++-- ...1_alter_rename_and_other_renames.reference | 6 +- .../01281_alter_rename_and_other_renames.sql | 2 +- .../02538_alter_rename_sequence.reference | 8 +++ .../02538_alter_rename_sequence.sql | 32 ++++++++++ 8 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/Storages/MergeTree/AlterConversions.h b/src/Storages/MergeTree/AlterConversions.h index 0d58499d424..6f5d5157138 100644 --- a/src/Storages/MergeTree/AlterConversions.h +++ b/src/Storages/MergeTree/AlterConversions.h @@ -17,6 +17,8 @@ struct AlterConversions /// Rename map new_name -> old_name std::unordered_map rename_map; + bool columnHasNewName(const std::string & old_name) const; + std::string getColumnNewName(const std::string & old_name) const; bool isColumnRenamed(const std::string & new_name) const { return rename_map.count(new_name) > 0; } std::string getColumnOldName(const std::string & new_name) const { return rename_map.at(new_name); } }; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index b2e0c14489a..99e84a6c783 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7526,11 +7526,13 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa AlterConversions result{}; for (const auto & command : commands) + { /// Currently we need explicit conversions only for RENAME alter /// all other conversions can be deduced from diff between part columns /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) result.rename_map[command.rename_to] = command.column_name; + } return result; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 47df0cfe42e..da87fcaad9a 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -109,13 +109,29 @@ static void splitMutationCommands( } } } + + auto alter_conversions = part->storage.getAlterConversionsForPart(part); /// If it's compact part, then we don't need to actually remove files /// from disk we just don't read dropped columns for (const auto & column : part->getColumns()) { if (!mutated_columns.contains(column.name)) - for_interpreter.emplace_back( - MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); + { + if (alter_conversions.columnHasNewName(column.name)) + { + for_interpreter.push_back( + { + .type = MutationCommand::Type::READ_COLUMN, + .column_name = alter_conversions.getColumnNewName(column.name), + .data_type = column.type + }); + } + else + { + for_interpreter.emplace_back( + MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); + } + } } } else @@ -147,6 +163,22 @@ static void splitMutationCommands( for_file_renames.push_back(command); } } + + auto alter_conversions = part->storage.getAlterConversionsForPart(part); + for (const auto & part_column : part_columns) + { + if (alter_conversions.columnHasNewName(part_column.name)) + { + auto new_column_name = alter_conversions.getColumnNewName(part_column.name); + for_file_renames.push_back({ + .type = MutationCommand::Type::RENAME_COLUMN, + .column_name = part_column.name, + .rename_to = new_column_name, + }); + + part_columns.rename(part_column.name, new_column_name); + } + } } } @@ -276,12 +308,30 @@ getColumnsForNewDataPart( /// Column was renamed and no other column renamed to it's name /// or column is dropped. if (!renamed_columns_to_from.contains(it->name) && (was_renamed || was_removed)) + { it = storage_columns.erase(it); + } else { - /// Take a type from source part column. - /// It may differ from column type in storage. - it->type = source_col->second; + + if (was_removed) + { /// DROP COLUMN xxx, RENAME COLUMN yyy TO xxx + auto renamed_from = renamed_columns_to_from.at(it->name); + auto maybe_name_and_type = source_columns.tryGetByName(renamed_from); + if (!maybe_name_and_type) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Got incorrect mutation commands, column {} was renamed from {}, but it doesn't exist in source columns {}", + it->name, renamed_from, source_columns.toString()); + + it->type = maybe_name_and_type->type; + } + else + { + /// Take a type from source part column. + /// It may differ from column type in storage. + it->type = source_col->second; + } ++it; } } diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index e47dddb9795..e4f7f67ee02 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1759,12 +1759,15 @@ MutationCommands ReplicatedMergeTreeQueue::getFirstAlterMutationCommandsForPart( if (in_partition == mutations_by_partition.end()) return MutationCommands{}; - Int64 part_version = part->info.getDataVersion(); + Int64 part_mutation_version = part->info.getMutationVersion(); + MutationCommands result; for (auto [mutation_version, mutation_status] : in_partition->second) - if (mutation_version > part_version && mutation_status->entry->alter_version != -1) - return mutation_status->entry->commands; + { + if (mutation_version > part_mutation_version && mutation_status->entry->alter_version != -1) + result.insert(result.end(), mutation_status->entry->commands.begin(), mutation_status->entry->commands.end()); + } - return MutationCommands{}; + return result; } MutationCommands ReplicatedMergeTreeQueue::getMutationCommands( diff --git a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference index bf3358aea60..532b8ce8712 100644 --- a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference +++ b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.reference @@ -1,11 +1,11 @@ -CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key value1_string value2 1 2 3 -CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int32,\n `value2` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int32,\n `value2` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key value1_string value2_old value2 1 2 3 7 4 5 6 7 -CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table_multiple\n(\n `key` Int32,\n `value1_string` String,\n `value2_old` Int64 DEFAULT 7\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key value1_string value2_old 1 2 7 4 5 7 diff --git a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql index f9462f0478e..b0ccd7751ab 100644 --- a/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql +++ b/tests/queries/0_stateless/01281_alter_rename_and_other_renames.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS rename_table_multiple; -CREATE TABLE rename_table_multiple (key Int32, value1 String, value2 Int32) ENGINE = MergeTree ORDER BY tuple(); +CREATE TABLE rename_table_multiple (key Int32, value1 String, value2 Int32) ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part=0; INSERT INTO rename_table_multiple VALUES (1, 2, 3); diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.reference b/tests/queries/0_stateless/02538_alter_rename_sequence.reference index e69de29bb2d..73aa1b7e8d8 100644 --- a/tests/queries/0_stateless/02538_alter_rename_sequence.reference +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.reference @@ -0,0 +1,8 @@ +1 2 3 +4 5 6 +{"column1_renamed":"1","column2_renamed":"2","column3":"3"} +{"column1_renamed":"4","column2_renamed":"5","column3":"6"} +1 2 3 +4 5 6 +{"column1_renamed":"1","column2_renamed":"2","column3":"3"} +{"column1_renamed":"4","column2_renamed":"5","column3":"6"} diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.sql b/tests/queries/0_stateless/02538_alter_rename_sequence.sql index efdb1d469eb..0eb839ebe59 100644 --- a/tests/queries/0_stateless/02538_alter_rename_sequence.sql +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.sql @@ -16,6 +16,8 @@ ALTER TABLE wrong_metadata RENAME COLUMN column1 TO column1_renamed SETTINGS rep INSERT INTO wrong_metadata VALUES (4, 5, 6); +SELECT * FROM wrong_metadata ORDER BY column1; + SYSTEM START REPLICATION QUEUES wrong_metadata; SYSTEM SYNC REPLICA wrong_metadata; @@ -25,3 +27,33 @@ ALTER TABLE wrong_metadata RENAME COLUMN column2 to column2_renamed SETTINGS rep SELECT * FROM wrong_metadata ORDER BY column1_renamed FORMAT JSONEachRow; DROP TABLE IF EXISTS wrong_metadata; + + +CREATE TABLE wrong_metadata_wide( + column1 UInt64, + column2 UInt64, + column3 UInt64 +) +ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata_wide', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO wrong_metadata_wide VALUES (1, 2, 3); + +SYSTEM STOP REPLICATION QUEUES wrong_metadata_wide; + +ALTER TABLE wrong_metadata_wide RENAME COLUMN column1 TO column1_renamed SETTINGS replication_alter_partitions_sync = 0; + +INSERT INTO wrong_metadata_wide VALUES (4, 5, 6); + +SELECT * FROM wrong_metadata_wide ORDER by column1; + +SYSTEM START REPLICATION QUEUES wrong_metadata_wide; + +SYSTEM SYNC REPLICA wrong_metadata_wide; + +ALTER TABLE wrong_metadata_wide RENAME COLUMN column2 to column2_renamed SETTINGS replication_alter_partitions_sync = 2; + +SELECT * FROM wrong_metadata_wide ORDER BY column1_renamed FORMAT JSONEachRow; + +DROP TABLE IF EXISTS wrong_metadata_wide; From dea46e58fbc203c674f2322ce8936614e3acc98d Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:03:24 +0100 Subject: [PATCH 018/445] Fixes for ordinary merge tree --- src/Storages/MergeTree/AlterConversions.cpp | 34 +++++++++++++++++++++ src/Storages/StorageMergeTree.cpp | 13 +++++--- 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/Storages/MergeTree/AlterConversions.cpp diff --git a/src/Storages/MergeTree/AlterConversions.cpp b/src/Storages/MergeTree/AlterConversions.cpp new file mode 100644 index 00000000000..e2eea2e68f6 --- /dev/null +++ b/src/Storages/MergeTree/AlterConversions.cpp @@ -0,0 +1,34 @@ +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +bool AlterConversions::columnHasNewName(const std::string & old_name) const +{ + for (const auto & [new_name, prev_name] : rename_map) + { + if (old_name == prev_name) + return true; + } + + return false; +} + +std::string AlterConversions::getColumnNewName(const std::string & old_name) const +{ + for (const auto & [new_name, prev_name] : rename_map) + { + if (old_name == prev_name) + return new_name; + } + + throw Exception(ErrorCodes::LOGICAL_ERROR, "Column {} was not renamed", old_name); +} + +} diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 4ef34ae91d5..e3d4ca072ca 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2060,10 +2060,15 @@ MutationCommands StorageMergeTree::getFirstAlterMutationCommandsForPart(const Da { std::lock_guard lock(currently_processing_in_background_mutex); - auto it = current_mutations_by_version.upper_bound(part->info.getDataVersion()); - if (it == current_mutations_by_version.end()) - return {}; - return it->second.commands; + Int64 part_mutation_version = part->info.getMutationVersion(); + + MutationCommands result; + for (const auto & current_mutation_by_version : current_mutations_by_version) + { + if (static_cast(current_mutation_by_version.first) > part_mutation_version) + result.insert(result.end(), current_mutation_by_version.second.commands.begin(), current_mutation_by_version.second.commands.end()); + } + return result; } void StorageMergeTree::startBackgroundMovesIfNeeded() From 8477b811308014674e9f5bfb16c1e3a6a40f0196 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:17:57 +0100 Subject: [PATCH 019/445] Add working test --- .../02543_alter_rename_modify_stuck.reference | 1 + .../02543_alter_rename_modify_stuck.sh | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference create mode 100755 tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh diff --git a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference new file mode 100644 index 00000000000..156128e3dd2 --- /dev/null +++ b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.reference @@ -0,0 +1 @@ +{"v":"1","v2":"77"} diff --git a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh new file mode 100755 index 00000000000..0e9b39d6dae --- /dev/null +++ b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS table_to_rename" + +$CLICKHOUSE_CLIENT --query="CREATE TABLE table_to_rename(v UInt64, v1 UInt64)ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0" + +$CLICKHOUSE_CLIENT --query="INSERT INTO table_to_rename VALUES (1, 1)" + + +# we want to following mutations to stuck +# That is why we stop merges and wait in loops until they actually start +$CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES table_to_rename" + +$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename RENAME COLUMN v1 to v2" & + +counter=0 retries=60 + +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table table_to_rename") + if [[ $result == *"v2"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + + +$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename UPDATE v2 = 77 WHERE 1 = 1" & + +counter=0 retries=60 + +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "SELECT count() from system.mutations where database='${CLICKHOUSE_DATABASE}' and table='table_to_rename'") + if [[ $result == "2" ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + + +$CLICKHOUSE_CLIENT --query="SYSTEM START MERGES table_to_rename" + +wait + +$CLICKHOUSE_CLIENT --query="SELECT * FROM table_to_rename FORMAT JSONEachRow" + + + $CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS table_to_rename" From bdc530dead710a4dd108e5effb1c561f57913174 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:30:38 +0100 Subject: [PATCH 020/445] Fix style --- src/Storages/MergeTree/MergeTreeData.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.h | 2 +- src/Storages/MergeTree/MutateTask.cpp | 6 +----- src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp | 6 +++++- src/Storages/MergeTree/ReplicatedMergeTreeQueue.h | 6 +++--- src/Storages/StorageMergeTree.cpp | 2 +- src/Storages/StorageMergeTree.h | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 4 ++-- src/Storages/StorageReplicatedMergeTree.h | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 99e84a6c783..c8b1020bd82 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7522,7 +7522,7 @@ bool MergeTreeData::canUsePolymorphicParts(const MergeTreeSettings & settings, S AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPartPtr part) const { - MutationCommands commands = getFirstAlterMutationCommandsForPart(part); + MutationCommands commands = getAlterMutationCommandsForPart(part); AlterConversions result{}; for (const auto & command : commands) diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 7dcd0c40553..0dfeb44524e 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -1303,7 +1303,7 @@ protected: /// Used to receive AlterConversions for part and apply them on fly. This /// method has different implementations for replicated and non replicated /// MergeTree because they store mutations in different way. - virtual MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; + virtual MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; /// Moves part to specified space, used in ALTER ... MOVE ... queries bool movePartsToSpace(const DataPartsVector & parts, SpacePtr space); diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index da87fcaad9a..44ec321681b 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -170,11 +170,7 @@ static void splitMutationCommands( if (alter_conversions.columnHasNewName(part_column.name)) { auto new_column_name = alter_conversions.getColumnNewName(part_column.name); - for_file_renames.push_back({ - .type = MutationCommand::Type::RENAME_COLUMN, - .column_name = part_column.name, - .rename_to = new_column_name, - }); + for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = part_column.name, .rename_to = new_column_name}); part_columns.rename(part_column.name, new_column_name); } diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index e4f7f67ee02..e8ed0888fa1 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1752,7 +1752,7 @@ ReplicatedMergeTreeMergePredicate ReplicatedMergeTreeQueue::getMergePredicate(zk } -MutationCommands ReplicatedMergeTreeQueue::getFirstAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const +MutationCommands ReplicatedMergeTreeQueue::getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const { std::lock_guard lock(state_mutex); auto in_partition = mutations_by_partition.find(part->info.partition_id); @@ -1761,6 +1761,10 @@ MutationCommands ReplicatedMergeTreeQueue::getFirstAlterMutationCommandsForPart( Int64 part_mutation_version = part->info.getMutationVersion(); MutationCommands result; + /// Here we return mutation commands for part which has bigger mutation version than part mutation version. + /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions + /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but + /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. for (auto [mutation_version, mutation_status] : in_partition->second) { if (mutation_version > part_mutation_version && mutation_status->entry->alter_version != -1) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h index 36f1ee07ad4..2c566668877 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h @@ -393,10 +393,10 @@ public: MutationCommands getMutationCommands(const MergeTreeData::DataPartPtr & part, Int64 desired_mutation_version) const; - /// Return mutation commands for part with smallest mutation version bigger - /// than data part version. Used when we apply alter commands on fly, + /// Return mutation commands for part which could be not applied to + /// it according to part mutation version. Used when we apply alter commands on fly, /// without actual data modification on disk. - MutationCommands getFirstAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; + MutationCommands getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; /// Mark finished mutations as done. If the function needs to be called again at some later time /// (because some mutations are probably done but we are not sure yet), returns true. diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index e3d4ca072ca..a9664007614 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2056,7 +2056,7 @@ void StorageMergeTree::attachRestoredParts(MutableDataPartsVector && parts) } -MutationCommands StorageMergeTree::getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const +MutationCommands StorageMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { std::lock_guard lock(currently_processing_in_background_mutex); diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index 1dff6323e4c..957d7804b96 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -265,7 +265,7 @@ private: protected: - MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const override; + MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; }; } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 7a4b97f6e49..58adc4be594 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -7892,9 +7892,9 @@ bool StorageReplicatedMergeTree::canUseAdaptiveGranularity() const } -MutationCommands StorageReplicatedMergeTree::getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const +MutationCommands StorageReplicatedMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { - return queue.getFirstAlterMutationCommandsForPart(part); + return queue.getAlterMutationCommandsForPart(part); } diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index 042e6acf4e2..3dc5ab75891 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -830,7 +830,7 @@ private: void waitMutationToFinishOnReplicas( const Strings & replicas, const String & mutation_id) const; - MutationCommands getFirstAlterMutationCommandsForPart(const DataPartPtr & part) const override; + MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; void startBackgroundMovesIfNeeded() override; From fc6c62e2ff62ce754362f9a44731bed0598168a4 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 17:41:36 +0100 Subject: [PATCH 021/445] Add comment --- src/Storages/MergeTree/AlterConversions.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Storages/MergeTree/AlterConversions.h b/src/Storages/MergeTree/AlterConversions.h index 6f5d5157138..47f964e85fe 100644 --- a/src/Storages/MergeTree/AlterConversions.h +++ b/src/Storages/MergeTree/AlterConversions.h @@ -17,9 +17,13 @@ struct AlterConversions /// Rename map new_name -> old_name std::unordered_map rename_map; + /// Column was renamed (lookup by value in rename_map) bool columnHasNewName(const std::string & old_name) const; + /// Get new name for column (lookup by value in rename_map) std::string getColumnNewName(const std::string & old_name) const; + /// Is this name is new name of column (lookup by key in rename_map) bool isColumnRenamed(const std::string & new_name) const { return rename_map.count(new_name) > 0; } + /// Get column old name before rename (lookup by key in rename_map) std::string getColumnOldName(const std::string & new_name) const { return rename_map.at(new_name); } }; From b8b0f1c3d3a2df7d0d23e4f67d6f6afe3c9dc5ca Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 2 Feb 2023 21:11:19 +0100 Subject: [PATCH 022/445] Make RENAME COLUMN barrier --- .../MergeTree/MergeTreeMarksLoader.cpp | 16 +++++++++- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 25 ++++++++++++++- src/Storages/StorageMergeTree.cpp | 31 +++++++++++++++++-- .../02543_alter_rename_modify_stuck.sh | 2 +- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeMarksLoader.cpp b/src/Storages/MergeTree/MergeTreeMarksLoader.cpp index 3fc7ff54c35..1d85ac1bd34 100644 --- a/src/Storages/MergeTree/MergeTreeMarksLoader.cpp +++ b/src/Storages/MergeTree/MergeTreeMarksLoader.cpp @@ -102,6 +102,15 @@ MarkCache::MappedPtr MergeTreeMarksLoader::loadMarksImpl() auto res = std::make_shared(marks_count * columns_in_mark); + if (file_size == 0 && marks_count != 0) + { + throw Exception( + ErrorCodes::CORRUPTED_DATA, + "Empty marks file '{}': {}, must be: {}", + std::string(fs::path(data_part_storage->getFullPath()) / mrk_path), + file_size, expected_uncompressed_size); + } + if (!index_granularity_info.mark_type.compressed && expected_uncompressed_size != file_size) throw Exception( ErrorCodes::CORRUPTED_DATA, @@ -138,7 +147,12 @@ MarkCache::MappedPtr MergeTreeMarksLoader::loadMarksImpl() } if (i * mark_size != expected_uncompressed_size) - throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, "Cannot read all marks from file {}", mrk_path); + { + throw Exception( + ErrorCodes::CANNOT_READ_ALL_DATA, + "Cannot read all marks from file {}, marks expected {} (bytes size {}), marks read {} (bytes size {})", + mrk_path, marks_count, expected_uncompressed_size, i, reader->count()); + } } res->protect(); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index e8ed0888fa1..2c957132918 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1813,7 +1813,30 @@ MutationCommands ReplicatedMergeTreeQueue::getMutationCommands( MutationCommands commands; for (auto it = begin; it != end; ++it) - commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + { + bool rename_command = false; + if (it->second->entry->isAlterMutation()) + { + const auto & single_mutation_commands = it->second->entry->commands; + for (const auto & command : single_mutation_commands) + { + if (command.type == MutationCommand::Type::RENAME_COLUMN) + { + rename_command = true; + break; + } + } + } + + if (rename_command) + { + if (commands.empty()) + commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + break; + } + else + commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + } return commands; } diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index a9664007614..73585a630cb 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1114,9 +1114,34 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( if (current_ast_elements + commands_size >= max_ast_elements) break; - current_ast_elements += commands_size; - commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); - last_mutation_to_apply = it; + bool rename_command = false; + const auto & single_mutation_commands = it->second.commands; + for (const auto & command : single_mutation_commands) + { + if (command.type == MutationCommand::Type::RENAME_COLUMN) + { + rename_command = true; + break; + } + } + + if (rename_command) + { + if (commands->empty()) + { + current_ast_elements += commands_size; + commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + last_mutation_to_apply = it; + } + break; + } + else + { + current_ast_elements += commands_size; + commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + last_mutation_to_apply = it; + } + } assert(commands->empty() == (last_mutation_to_apply == mutations_end_it)); diff --git a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh index 0e9b39d6dae..adaf1846552 100755 --- a/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh +++ b/tests/queries/0_stateless/02543_alter_rename_modify_stuck.sh @@ -32,7 +32,7 @@ while [[ $counter -lt $retries ]]; do done -$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename UPDATE v2 = 77 WHERE 1 = 1" & +$CLICKHOUSE_CLIENT --query="ALTER TABLE table_to_rename UPDATE v2 = 77 WHERE 1 = 1 SETTINGS mutations_sync = 2" & counter=0 retries=60 From 15ddcdc4a6606f62b4333333144231300243260c Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 12:34:47 +0100 Subject: [PATCH 023/445] Fix clang tidy --- src/Storages/StorageMergeTree.cpp | 1 - tests/queries/0_stateless/02538_alter_rename_sequence.sql | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 73585a630cb..e36bb31551d 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1129,7 +1129,6 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( { if (commands->empty()) { - current_ast_elements += commands_size; commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); last_mutation_to_apply = it; } diff --git a/tests/queries/0_stateless/02538_alter_rename_sequence.sql b/tests/queries/0_stateless/02538_alter_rename_sequence.sql index 0eb839ebe59..d7df27dc702 100644 --- a/tests/queries/0_stateless/02538_alter_rename_sequence.sql +++ b/tests/queries/0_stateless/02538_alter_rename_sequence.sql @@ -5,7 +5,7 @@ CREATE TABLE wrong_metadata( column2 UInt64, column3 UInt64 ) -ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata', '1') +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') ORDER BY tuple(); INSERT INTO wrong_metadata VALUES (1, 2, 3); @@ -34,7 +34,7 @@ CREATE TABLE wrong_metadata_wide( column2 UInt64, column3 UInt64 ) -ENGINE ReplicatedMergeTree('/test/tables/wrong_metadata_wide', '1') +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_wide', '1') ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0; From 7a0d0f0c5324cddce2131a2af6f6e5535db65f30 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 19:14:49 +0100 Subject: [PATCH 024/445] Some code which doesn't work --- src/Storages/MergeTree/MergeTreeData.cpp | 42 +++++-- src/Storages/MergeTree/MergeTreeData.h | 2 +- src/Storages/MergeTree/MutateTask.cpp | 116 ++++++++++-------- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 28 +++-- .../MergeTree/ReplicatedMergeTreeQueue.h | 2 +- src/Storages/StorageMergeTree.cpp | 6 +- src/Storages/StorageMergeTree.h | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 2 +- src/Storages/StorageReplicatedMergeTree.h | 2 +- .../01278_alter_rename_combination.reference | 4 +- .../01278_alter_rename_combination.sql | 2 +- 11 files changed, 128 insertions(+), 80 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index c8b1020bd82..27b9881bcf3 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7522,16 +7522,44 @@ bool MergeTreeData::canUsePolymorphicParts(const MergeTreeSettings & settings, S AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPartPtr part) const { - MutationCommands commands = getAlterMutationCommandsForPart(part); + std::map commands_map = getAlterMutationCommandsForPart(part); + auto part_columns = part->getColumnsDescription(); AlterConversions result{}; - for (const auto & command : commands) + auto & rename_map = result.rename_map; + for (const auto & [version, commands] : commands_map) { - /// Currently we need explicit conversions only for RENAME alter - /// all other conversions can be deduced from diff between part columns - /// and columns in storage. - if (command.type == MutationCommand::Type::RENAME_COLUMN) - result.rename_map[command.rename_to] = command.column_name; + for (const auto & command : commands) + { + /// Currently we need explicit conversions only for RENAME alter + /// all other conversions can be deduced from diff between part columns + /// and columns in storage. + if (command.type == MutationCommand::Type::RENAME_COLUMN) + { + if (!part_columns.has(command.column_name)) + continue; + + part_columns.rename(command.column_name, command.rename_to); + + if (auto it = rename_map.find(command.column_name); it != rename_map.end()) + { + auto rename_source = it->second; + rename_map.erase(it); + + rename_map[command.rename_to] = rename_source; + } + else + rename_map[command.rename_to] = command.column_name; + } + } + } + + for (auto it = rename_map.begin(); it != rename_map.end();) + { + if (it->first == it->second) + it = rename_map.erase(it); + else + ++it; } return result; diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 0dfeb44524e..a60f4721661 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -1303,7 +1303,7 @@ protected: /// Used to receive AlterConversions for part and apply them on fly. This /// method has different implementations for replicated and non replicated /// MergeTree because they store mutations in different way. - virtual MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; + virtual std::map getAlterMutationCommandsForPart(const DataPartPtr & part) const = 0; /// Moves part to specified space, used in ALTER ... MOVE ... queries bool movePartsToSpace(const DataPartsVector & parts, SpacePtr space); diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 57036d4ba30..85dc9f7f0d6 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -97,40 +97,33 @@ static void splitMutationCommands( else mutated_columns.emplace(command.column_name); } - - if (command.type == MutationCommand::Type::RENAME_COLUMN) - { - for_interpreter.push_back( - { - .type = MutationCommand::Type::READ_COLUMN, - .column_name = command.rename_to, - }); - part_columns.rename(command.column_name, command.rename_to); - } } } auto alter_conversions = part->storage.getAlterConversionsForPart(part); /// If it's compact part, then we don't need to actually remove files /// from disk we just don't read dropped columns - for (const auto & column : part->getColumns()) + + for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) + { + if (part_columns.has(rename_from)) + { + for_interpreter.push_back( + { + .type = MutationCommand::Type::READ_COLUMN, + .column_name = rename_to, + }); + + part_columns.rename(rename_from, rename_to); + } + } + + for (const auto & column : part_columns) { if (!mutated_columns.contains(column.name)) { - if (alter_conversions.columnHasNewName(column.name)) - { - for_interpreter.push_back( - { - .type = MutationCommand::Type::READ_COLUMN, - .column_name = alter_conversions.getColumnNewName(column.name), - .data_type = column.type - }); - } - else - { - for_interpreter.emplace_back( - MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); - } + for_interpreter.emplace_back( + MutationCommand{.type = MutationCommand::Type::READ_COLUMN, .column_name = column.name, .data_type = column.type}); } } } @@ -157,22 +150,18 @@ static void splitMutationCommands( { if (command.type == MutationCommand::Type::READ_COLUMN) for_interpreter.push_back(command); - else if (command.type == MutationCommand::Type::RENAME_COLUMN) - part_columns.rename(command.column_name, command.rename_to); for_file_renames.push_back(command); } } auto alter_conversions = part->storage.getAlterConversionsForPart(part); - for (const auto & part_column : part_columns) + for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { - if (alter_conversions.columnHasNewName(part_column.name)) + if (part_columns.has(rename_from)) { - auto new_column_name = alter_conversions.getColumnNewName(part_column.name); - for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = part_column.name, .rename_to = new_column_name}); - - part_columns.rename(part_column.name, new_column_name); + for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); + part_columns.rename(rename_from, rename_to); } } } @@ -585,6 +574,13 @@ static NameToNameVector collectFilesForRenames( /// Collect counts for shared streams of different columns. As an example, Nested columns have shared stream with array sizes. auto stream_counts = getStreamCounts(source_part, source_part->getColumns().getNames()); NameToNameVector rename_vector; + NameSet collected_names; + + auto add_rename = [&rename_vector, &collected_names] (const std::string & file_rename_from, const std::string & file_rename_to) + { + if (collected_names.emplace(file_rename_from).second) + rename_vector.emplace_back(file_rename_from, file_rename_to); + }; /// Remove old data for (const auto & command : commands_for_removes) @@ -593,19 +589,19 @@ static NameToNameVector collectFilesForRenames( { if (source_part->checksums.has(INDEX_FILE_PREFIX + command.column_name + ".idx2")) { - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + ".idx2", ""); - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + ".idx2", ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); } else if (source_part->checksums.has(INDEX_FILE_PREFIX + command.column_name + ".idx")) { - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + ".idx", ""); - rename_vector.emplace_back(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + ".idx", ""); + add_rename(INDEX_FILE_PREFIX + command.column_name + mrk_extension, ""); } } else if (command.type == MutationCommand::Type::DROP_PROJECTION) { if (source_part->checksums.has(command.column_name + ".proj")) - rename_vector.emplace_back(command.column_name + ".proj", ""); + add_rename(command.column_name + ".proj", ""); } else if (command.type == MutationCommand::Type::DROP_COLUMN) { @@ -615,8 +611,8 @@ static NameToNameVector collectFilesForRenames( /// Delete files if they are no longer shared with another column. if (--stream_counts[stream_name] == 0) { - rename_vector.emplace_back(stream_name + ".bin", ""); - rename_vector.emplace_back(stream_name + mrk_extension, ""); + add_rename(stream_name + ".bin", ""); + add_rename(stream_name + mrk_extension, ""); } }; @@ -628,6 +624,7 @@ static NameToNameVector collectFilesForRenames( String escaped_name_from = escapeForFileName(command.column_name); String escaped_name_to = escapeForFileName(command.rename_to); + ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) { String stream_from = ISerialization::getFileNameForStream(command.column_name, substream_path); @@ -635,8 +632,8 @@ static NameToNameVector collectFilesForRenames( if (stream_from != stream_to) { - rename_vector.emplace_back(stream_from + ".bin", stream_to + ".bin"); - rename_vector.emplace_back(stream_from + mrk_extension, stream_to + mrk_extension); + add_rename(stream_from + ".bin", stream_to + ".bin"); + add_rename(stream_from + mrk_extension, stream_to + mrk_extension); } }; @@ -656,8 +653,8 @@ static NameToNameVector collectFilesForRenames( { if (!new_streams.contains(old_stream) && --stream_counts[old_stream] == 0) { - rename_vector.emplace_back(old_stream + ".bin", ""); - rename_vector.emplace_back(old_stream + mrk_extension, ""); + add_rename(old_stream + ".bin", ""); + add_rename(old_stream + mrk_extension, ""); } } } @@ -1358,13 +1355,27 @@ private: ctx->new_data_part->storeVersionMetadata(); NameSet hardlinked_files; + + /// NOTE: Renames must be done in order + for (const auto & [rename_from, rename_to] : ctx->files_to_rename) + { + if (rename_to.empty()) /// It's DROP COLUMN + { + /// pass + } + else + { + ctx->new_data_part->getDataPartStorage().createHardLinkFrom( + ctx->source_part->getDataPartStorage(), rename_from, rename_to); + hardlinked_files.insert(rename_from); + } + } /// Create hardlinks for unchanged files for (auto it = ctx->source_part->getDataPartStorage().iterate(); it->isValid(); it->next()) { if (ctx->files_to_skip.contains(it->name())) continue; - String destination; String file_name = it->name(); auto rename_it = std::find_if(ctx->files_to_rename.begin(), ctx->files_to_rename.end(), [&file_name](const auto & rename_pair) @@ -1374,20 +1385,17 @@ private: if (rename_it != ctx->files_to_rename.end()) { - if (rename_it->second.empty()) - continue; - destination = rename_it->second; - } - else - { - destination = it->name(); + /// RENAMEs and DROPs already processed + continue; } + String destination = it->name(); + if (it->isFile()) { ctx->new_data_part->getDataPartStorage().createHardLinkFrom( - ctx->source_part->getDataPartStorage(), it->name(), destination); - hardlinked_files.insert(it->name()); + ctx->source_part->getDataPartStorage(), file_name, destination); + hardlinked_files.insert(file_name); } else if (!endsWith(it->name(), ".tmp_proj")) // ignore projection tmp merge dir { diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 2c957132918..df4dea978a1 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace DB { @@ -1752,23 +1753,34 @@ ReplicatedMergeTreeMergePredicate ReplicatedMergeTreeQueue::getMergePredicate(zk } -MutationCommands ReplicatedMergeTreeQueue::getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const +std::map ReplicatedMergeTreeQueue::getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const { - std::lock_guard lock(state_mutex); + std::unique_lock lock(state_mutex); auto in_partition = mutations_by_partition.find(part->info.partition_id); if (in_partition == mutations_by_partition.end()) - return MutationCommands{}; + return {}; - Int64 part_mutation_version = part->info.getMutationVersion(); - MutationCommands result; + Int64 part_data_version = part->info.getDataVersion(); + std::map result; /// Here we return mutation commands for part which has bigger mutation version than part mutation version. /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. - for (auto [mutation_version, mutation_status] : in_partition->second) + + for (auto [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { - if (mutation_version > part_mutation_version && mutation_status->entry->alter_version != -1) - result.insert(result.end(), mutation_status->entry->commands.begin(), mutation_status->entry->commands.end()); + if (mutation_status->entry->alter_version != -1) + { + if (mutation_version > part_data_version) + { + result[mutation_version] = mutation_status->entry->commands; + } + else + { + result[mutation_version] = mutation_status->entry->commands; + break; + } + } } return result; diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h index 2c566668877..150fb49b333 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h @@ -396,7 +396,7 @@ public: /// Return mutation commands for part which could be not applied to /// it according to part mutation version. Used when we apply alter commands on fly, /// without actual data modification on disk. - MutationCommands getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; + std::map getAlterMutationCommandsForPart(const MergeTreeData::DataPartPtr & part) const; /// Mark finished mutations as done. If the function needs to be called again at some later time /// (because some mutations are probably done but we are not sure yet), returns true. diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index e36bb31551d..b0a45f9cfba 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2080,17 +2080,17 @@ void StorageMergeTree::attachRestoredParts(MutableDataPartsVector && parts) } -MutationCommands StorageMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const +std::map StorageMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { std::lock_guard lock(currently_processing_in_background_mutex); Int64 part_mutation_version = part->info.getMutationVersion(); - MutationCommands result; + std::map result; for (const auto & current_mutation_by_version : current_mutations_by_version) { if (static_cast(current_mutation_by_version.first) > part_mutation_version) - result.insert(result.end(), current_mutation_by_version.second.commands.begin(), current_mutation_by_version.second.commands.end()); + result[current_mutation_by_version.first] = current_mutation_by_version.second.commands; } return result; } diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index 957d7804b96..9385542dcac 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -265,7 +265,7 @@ private: protected: - MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; + std::map getAlterMutationCommandsForPart(const DataPartPtr & part) const override; }; } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 58adc4be594..ef2fd46b705 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -7892,7 +7892,7 @@ bool StorageReplicatedMergeTree::canUseAdaptiveGranularity() const } -MutationCommands StorageReplicatedMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const +std::map StorageReplicatedMergeTree::getAlterMutationCommandsForPart(const DataPartPtr & part) const { return queue.getAlterMutationCommandsForPart(part); } diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index 3dc5ab75891..a4ac60052bc 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -830,7 +830,7 @@ private: void waitMutationToFinishOnReplicas( const Strings & replicas, const String & mutation_id) const; - MutationCommands getAlterMutationCommandsForPart(const DataPartPtr & part) const override; + std::map getAlterMutationCommandsForPart(const DataPartPtr & part) const override; void startBackgroundMovesIfNeeded() override; diff --git a/tests/queries/0_stateless/01278_alter_rename_combination.reference b/tests/queries/0_stateless/01278_alter_rename_combination.reference index cc912e9b265..e70c2d2e6f8 100644 --- a/tests/queries/0_stateless/01278_alter_rename_combination.reference +++ b/tests/queries/0_stateless/01278_alter_rename_combination.reference @@ -1,7 +1,7 @@ -CREATE TABLE default.rename_table\n(\n `key` Int32,\n `old_value1` Int32,\n `value1` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table\n(\n `key` Int32,\n `old_value1` Int32,\n `value1` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 key old_value1 value1 1 2 3 -CREATE TABLE default.rename_table\n(\n `k` Int32,\n `v1` Int32,\n `v2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS index_granularity = 8192 +CREATE TABLE default.rename_table\n(\n `k` Int32,\n `v1` Int32,\n `v2` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS min_bytes_for_wide_part = 0, index_granularity = 8192 k v1 v2 1 2 3 4 5 6 diff --git a/tests/queries/0_stateless/01278_alter_rename_combination.sql b/tests/queries/0_stateless/01278_alter_rename_combination.sql index fa73362622c..51322f5d86f 100644 --- a/tests/queries/0_stateless/01278_alter_rename_combination.sql +++ b/tests/queries/0_stateless/01278_alter_rename_combination.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS rename_table; -CREATE TABLE rename_table (key Int32, value1 Int32, value2 Int32) ENGINE = MergeTree ORDER BY tuple(); +CREATE TABLE rename_table (key Int32, value1 Int32, value2 Int32) ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part=0; INSERT INTO rename_table VALUES (1, 2, 3); From f91c10d09d56de8d23faf326c5c300de1ee20c2a Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 20:49:07 +0100 Subject: [PATCH 025/445] Initial support for version --- src/Storages/MergeTree/DataPartsExchange.cpp | 3 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 26 ++++++++++---- src/Storages/MergeTree/IMergeTreeDataPart.h | 7 +++- src/Storages/MergeTree/MergeTask.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 3 +- .../MergeTree/MergeTreeDataPartInMemory.cpp | 4 +-- .../MergeTree/MergeTreeDataWriter.cpp | 4 +-- .../MergeTree/MergeTreeWriteAheadLog.cpp | 2 +- .../MergeTree/MergedBlockOutputStream.cpp | 10 +++++- .../MergedColumnOnlyOutputStream.cpp | 2 +- src/Storages/MergeTree/MutateTask.cpp | 2 +- .../ReplicatedMergeTreeAttachThread.cpp | 4 ++- src/Storages/StorageInMemoryMetadata.cpp | 7 ++++ src/Storages/StorageInMemoryMetadata.h | 7 ++++ src/Storages/StorageReplicatedMergeTree.cpp | 35 +++++++++++-------- src/Storages/StorageReplicatedMergeTree.h | 9 +++-- 16 files changed, 89 insertions(+), 38 deletions(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index cbbdb911974..1864ce37ddf 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -692,7 +692,8 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToMemory( auto block = block_in.read(); throttler->add(block.bytes()); - new_data_part->setColumns(block.getNamesAndTypesList(), {}); + //// TODO Read them from file + new_data_part->setColumns(block.getNamesAndTypesList(), {}, metadata_snapshot->getMetadataVersion()); if (!is_projection) { diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 5de13020a1d..3424ef5bf0a 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -416,10 +416,11 @@ std::pair IMergeTreeDataPart::getMinMaxTime() const } -void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos) +void IMergeTreeDataPart::setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos, int32_t metadata_version_) { columns = new_columns; serialization_infos = new_infos; + metadata_version = metadata_version_; column_name_to_position.clear(); column_name_to_position.reserve(new_columns.size()); @@ -798,6 +799,9 @@ NameSet IMergeTreeDataPart::getFileNamesWithoutChecksums() const if (getDataPartStorage().exists(TXN_VERSION_METADATA_FILE_NAME)) result.emplace(TXN_VERSION_METADATA_FILE_NAME); + if (getDataPartStorage().exists(METADATA_VERSION_FILE_NAME)) + result.emplace(METADATA_VERSION_FILE_NAME); + return result; } @@ -1288,8 +1292,7 @@ void IMergeTreeDataPart::loadColumns(bool require) metadata_snapshot = metadata_snapshot->projections.get(name).metadata; NamesAndTypesList loaded_columns; - bool exists = metadata_manager->exists("columns.txt"); - if (!exists) + if (!metadata_manager->exists("columns.txt")) { /// We can get list of columns only from columns.txt in compact parts. if (require || part_type == Type::Compact) @@ -1322,14 +1325,25 @@ void IMergeTreeDataPart::loadColumns(bool require) }; SerializationInfoByName infos(loaded_columns, settings); - exists = metadata_manager->exists(SERIALIZATION_FILE_NAME); - if (exists) + if (metadata_manager->exists(SERIALIZATION_FILE_NAME)) { auto in = metadata_manager->read(SERIALIZATION_FILE_NAME); infos.readJSON(*in); } - setColumns(loaded_columns, infos); + int32_t loaded_metadata_version; + if (metadata_manager->exists(METADATA_VERSION_FILE_NAME)) + { + auto in = metadata_manager->read(METADATA_VERSION_FILE_NAME); + readIntText(loaded_metadata_version, *in); + } + else + { + loaded_metadata_version = metadata_snapshot->getMetadataVersion(); + } + + ///TODO read metadata here + setColumns(loaded_columns, infos, loaded_metadata_version); } /// Project part / part with project parts / compact part doesn't support LWD. diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 9d0252bd625..df83d138322 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -139,7 +139,8 @@ public: String getTypeName() const { return getType().toString(); } - void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos); + void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos, int32_t metadata_version_); + int32_t getMetadataVersion() const { return metadata_version; } const NamesAndTypesList & getColumns() const { return columns; } const ColumnsDescription & getColumnsDescription() const { return columns_description; } @@ -310,6 +311,8 @@ public: mutable VersionMetadata version; + int32_t metadata_version; + /// For data in RAM ('index') UInt64 getIndexSizeInBytes() const; UInt64 getIndexSizeInAllocatedBytes() const; @@ -383,6 +386,8 @@ public: static inline constexpr auto TXN_VERSION_METADATA_FILE_NAME = "txn_version.txt"; + static inline constexpr auto METADATA_VERSION_FILE_NAME = "metadata_version.txt"; + /// One of part files which is used to check how many references (I'd like /// to say hardlinks, but it will confuse even more) we have for the part /// for zero copy replication. Sadly it's very complex. diff --git a/src/Storages/MergeTree/MergeTask.cpp b/src/Storages/MergeTree/MergeTask.cpp index 5874c257ad0..97bb9099c25 100644 --- a/src/Storages/MergeTree/MergeTask.cpp +++ b/src/Storages/MergeTree/MergeTask.cpp @@ -205,7 +205,7 @@ bool MergeTask::ExecuteAndFinalizeHorizontalPart::prepare() infos.add(part->getSerializationInfos()); } - global_ctx->new_data_part->setColumns(global_ctx->storage_columns, infos); + global_ctx->new_data_part->setColumns(global_ctx->storage_columns, infos, global_ctx->metadata_snapshot->getMetadataVersion()); const auto & local_part_min_ttl = global_ctx->new_data_part->ttl_infos.part_min_ttl; if (local_part_min_ttl && local_part_min_ttl <= global_ctx->time_of_merge) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 27b9881bcf3..b35d58688d5 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7548,6 +7548,7 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa rename_map[command.rename_to] = rename_source; } + else rename_map[command.rename_to] = command.column_name; } @@ -7935,7 +7936,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeData::createEmptyPart( if (settings->assign_part_uuids) new_data_part->uuid = UUIDHelpers::generateV4(); - new_data_part->setColumns(columns, {}); + new_data_part->setColumns(columns, {}, metadata_snapshot->getMetadataVersion()); new_data_part->rows_count = block.rows(); new_data_part->partition = partition; diff --git a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp index 20049976acf..5b1054d0a0e 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp @@ -73,7 +73,7 @@ MutableDataPartStoragePtr MergeTreeDataPartInMemory::flushToDisk(const String & new_data_part_storage->beginTransaction(); new_data_part->uuid = uuid; - new_data_part->setColumns(columns, {}); + new_data_part->setColumns(columns, {}, metadata_snapshot->getMetadataVersion()); new_data_part->partition.value = partition.value; new_data_part->minmax_idx = minmax_idx; @@ -104,7 +104,7 @@ MutableDataPartStoragePtr MergeTreeDataPartInMemory::flushToDisk(const String & .build(); new_projection_part->is_temp = false; // clean up will be done on parent part - new_projection_part->setColumns(projection->getColumns(), {}); + new_projection_part->setColumns(projection->getColumns(), {}, metadata_snapshot->getMetadataVersion()); auto new_projection_part_storage = new_projection_part->getDataPartStoragePtr(); if (new_projection_part_storage->exists()) diff --git a/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/src/Storages/MergeTree/MergeTreeDataWriter.cpp index 93b0abeca35..ba344dc70aa 100644 --- a/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -465,7 +465,7 @@ MergeTreeDataWriter::TemporaryPart MergeTreeDataWriter::writeTempPartImpl( SerializationInfoByName infos(columns, settings); infos.add(block); - new_data_part->setColumns(columns, infos); + new_data_part->setColumns(columns, infos, metadata_snapshot->getMetadataVersion()); new_data_part->rows_count = block.rows(); new_data_part->partition = std::move(partition); new_data_part->minmax_idx = std::move(minmax_idx); @@ -587,7 +587,7 @@ MergeTreeDataWriter::TemporaryPart MergeTreeDataWriter::writeProjectionPartImpl( SerializationInfoByName infos(columns, settings); infos.add(block); - new_data_part->setColumns(columns, infos); + new_data_part->setColumns(columns, infos, metadata_snapshot->getMetadataVersion()); if (new_data_part->isStoredOnDisk()) { diff --git a/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp b/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp index fabf2acdad3..87d9bb0f168 100644 --- a/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp +++ b/src/Storages/MergeTree/MergeTreeWriteAheadLog.cpp @@ -229,7 +229,7 @@ MergeTreeData::MutableDataPartsVector MergeTreeWriteAheadLog::restore( part->minmax_idx->update(block, storage.getMinMaxColumnsNames(metadata_snapshot->getPartitionKey())); part->partition.create(metadata_snapshot, block, 0, context); - part->setColumns(block.getNamesAndTypesList(), {}); + part->setColumns(block.getNamesAndTypesList(), {}, metadata_snapshot->getMetadataVersion()); if (metadata_snapshot->hasSortingKey()) metadata_snapshot->getSortingKey().expression->execute(block); diff --git a/src/Storages/MergeTree/MergedBlockOutputStream.cpp b/src/Storages/MergeTree/MergedBlockOutputStream.cpp index ced43ae25b0..53d4a32fc0e 100644 --- a/src/Storages/MergeTree/MergedBlockOutputStream.cpp +++ b/src/Storages/MergeTree/MergedBlockOutputStream.cpp @@ -175,7 +175,7 @@ MergedBlockOutputStream::Finalizer MergedBlockOutputStream::finalizePartAsync( serialization_infos.replaceData(new_serialization_infos); files_to_remove_after_sync = removeEmptyColumnsFromPart(new_part, part_columns, serialization_infos, checksums); - new_part->setColumns(part_columns, serialization_infos); + new_part->setColumns(part_columns, serialization_infos, metadata_snapshot->getMetadataVersion()); } auto finalizer = std::make_unique(*writer, new_part, files_to_remove_after_sync, sync); @@ -289,6 +289,14 @@ MergedBlockOutputStream::WrittenFiles MergedBlockOutputStream::finalizePartOnDis written_files.emplace_back(std::move(out)); } + { + /// Write a file with a description of columns. + auto out = new_part->getDataPartStorage().writeFile(IMergeTreeDataPart::METADATA_VERSION_FILE_NAME, 4096, write_settings); + DB::writeIntText(new_part->getMetadataVersion(), *out); + out->preFinalize(); + written_files.emplace_back(std::move(out)); + } + if (default_codec != nullptr) { auto out = new_part->getDataPartStorage().writeFile(IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME, 4096, write_settings); diff --git a/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp b/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp index 03829f1daf9..c70c5187b8b 100644 --- a/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp +++ b/src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp @@ -85,7 +85,7 @@ MergedColumnOnlyOutputStream::fillChecksums( all_checksums.files.erase(removed_file); } - new_part->setColumns(columns, serialization_infos); + new_part->setColumns(columns, serialization_infos, metadata_snapshot->getMetadataVersion()); return checksums; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 85dc9f7f0d6..4c1ca86f5cb 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -1700,7 +1700,7 @@ bool MutateTask::prepare() ctx->source_part, ctx->updated_header, ctx->storage_columns, ctx->source_part->getSerializationInfos(), ctx->commands_for_part); - ctx->new_data_part->setColumns(new_columns, new_infos); + ctx->new_data_part->setColumns(new_columns, new_infos, ctx->metadata_snapshot->getMetadataVersion()); ctx->new_data_part->partition.assign(ctx->source_part->partition); /// Don't change granularity type while mutating subset of columns diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 557123ddae2..271edb0399f 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -149,7 +149,9 @@ void ReplicatedMergeTreeAttachThread::runImpl() const bool replica_metadata_version_exists = zookeeper->tryGet(replica_path + "/metadata_version", replica_metadata_version); if (replica_metadata_version_exists) { - storage.metadata_version = parse(replica_metadata_version); + StorageInMemoryMetadata metadata_with_new_version; + metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); + storage.setInMemoryMetadata(metadata_with_new_version); } else { diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index f6550c6cd5d..5250c6f5330 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -41,6 +41,7 @@ StorageInMemoryMetadata::StorageInMemoryMetadata(const StorageInMemoryMetadata & , settings_changes(other.settings_changes ? other.settings_changes->clone() : nullptr) , select(other.select) , comment(other.comment) + , metadata_version(other.metadata_version) { } @@ -69,6 +70,7 @@ StorageInMemoryMetadata & StorageInMemoryMetadata::operator=(const StorageInMemo settings_changes.reset(); select = other.select; comment = other.comment; + metadata_version = other.metadata_version; return *this; } @@ -122,6 +124,11 @@ void StorageInMemoryMetadata::setSelectQuery(const SelectQueryDescription & sele select = select_; } +void StorageInMemoryMetadata::setMetadataVersion(int32_t metadata_version_) +{ + metadata_version = metadata_version_; +} + const ColumnsDescription & StorageInMemoryMetadata::getColumns() const { return columns; diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index eadce581334..2dd6c32e3c6 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -50,6 +50,8 @@ struct StorageInMemoryMetadata String comment; + int32_t metadata_version; + StorageInMemoryMetadata() = default; StorageInMemoryMetadata(const StorageInMemoryMetadata & other); @@ -90,6 +92,8 @@ struct StorageInMemoryMetadata /// Set SELECT query for (Materialized)View void setSelectQuery(const SelectQueryDescription & select_); + void setMetadataVersion(int32_t metadata_version_); + /// Returns combined set of columns const ColumnsDescription & getColumns() const; @@ -218,6 +222,9 @@ struct StorageInMemoryMetadata const SelectQueryDescription & getSelectQuery() const; bool hasSelectQuery() const; + int32_t getMetadataVersion() const { return metadata_version; } + bool hasMetadataVersion() const { return metadata_version != -1; } + /// Check that all the requested names are in the table and have the correct types. void check(const NamesAndTypesList & columns) const; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index ef2fd46b705..3083fbd2c46 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -452,7 +452,10 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( Coordination::Stat metadata_stat; current_zookeeper->get(zookeeper_path + "/metadata", &metadata_stat); - metadata_version = metadata_stat.version; + + StorageInMemoryMetadata storage_metadata; + storage_metadata.setMetadataVersion(metadata_stat.version); + setInMemoryMetadata(storage_metadata); } catch (Coordination::Exception & e) { @@ -772,7 +775,7 @@ bool StorageReplicatedMergeTree::createTableIfNotExists(const StorageMetadataPtr zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/columns", metadata_snapshot->getColumns().toString(), zkutil::CreateMode::Persistent)); - ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", std::to_string(metadata_version), + ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", toString(metadata_snapshot->getMetadataVersion()), zkutil::CreateMode::Persistent)); /// The following 3 nodes were added in version 1.1.xxx, so we create them here, not in createNewZooKeeperNodes() @@ -845,7 +848,7 @@ void StorageReplicatedMergeTree::createReplica(const StorageMetadataPtr & metada zkutil::CreateMode::Persistent)); ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/columns", metadata_snapshot->getColumns().toString(), zkutil::CreateMode::Persistent)); - ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", std::to_string(metadata_version), + ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/metadata_version", toString(metadata_snapshot->getMetadataVersion()), zkutil::CreateMode::Persistent)); /// The following 3 nodes were added in version 1.1.xxx, so we create them here, not in createNewZooKeeperNodes() @@ -1140,16 +1143,19 @@ void StorageReplicatedMergeTree::checkTableStructure(const String & zookeeper_pr } void StorageReplicatedMergeTree::setTableStructure(const StorageID & table_id, const ContextPtr & local_context, - ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff) + ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff, int32_t new_metadata_version) { StorageInMemoryMetadata old_metadata = getInMemoryMetadata(); + StorageInMemoryMetadata new_metadata = metadata_diff.getNewMetadata(new_columns, local_context, old_metadata); + new_metadata.setMetadataVersion(new_metadata_version); /// Even if the primary/sorting/partition keys didn't change we must reinitialize it /// because primary/partition key column types might have changed. checkTTLExpressions(new_metadata, old_metadata); setProperties(new_metadata, old_metadata); + DatabaseCatalog::instance().getDatabase(table_id.database_name)->alterTable(local_context, table_id, new_metadata); } @@ -2760,8 +2766,9 @@ void StorageReplicatedMergeTree::cloneMetadataIfNeeded(const String & source_rep return; } + auto metadata_snapshot = getInMemoryMetadataPtr(); Int32 source_metadata_version = parse(source_metadata_version_str); - if (metadata_version == source_metadata_version) + if (metadata_snapshot->getMetadataVersion() == source_metadata_version) return; /// Our metadata it not up to date with source replica metadata. @@ -2779,7 +2786,7 @@ void StorageReplicatedMergeTree::cloneMetadataIfNeeded(const String & source_rep /// if all such entries were cleaned up from the log and source_queue. LOG_WARNING(log, "Metadata version ({}) on replica is not up to date with metadata ({}) on source replica {}", - metadata_version, source_metadata_version, source_replica); + metadata_snapshot->getMetadataVersion(), source_metadata_version, source_replica); String source_metadata; String source_columns; @@ -4926,14 +4933,15 @@ bool StorageReplicatedMergeTree::optimize( bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMergeTree::LogEntry & entry) { - if (entry.alter_version < metadata_version) + auto current_metadata = getInMemoryMetadataPtr(); + if (entry.alter_version < current_metadata->getMetadataVersion()) { /// TODO Can we replace it with LOGICAL_ERROR? /// As for now, it may rarely happen due to reordering of ALTER_METADATA entries in the queue of /// non-initial replica and also may happen after stale replica recovery. LOG_WARNING(log, "Attempt to update metadata of version {} " "to older version {} when processing log entry {}: {}", - metadata_version, entry.alter_version, entry.znode_name, entry.toString()); + current_metadata->getMetadataVersion(), entry.alter_version, entry.znode_name, entry.toString()); return true; } @@ -4981,10 +4989,9 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer LOG_INFO(log, "Metadata changed in ZooKeeper. Applying changes locally."); auto metadata_diff = ReplicatedMergeTreeTableMetadata(*this, getInMemoryMetadataPtr()).checkAndFindDiff(metadata_from_entry, getInMemoryMetadataPtr()->getColumns(), getContext()); - setTableStructure(table_id, alter_context, std::move(columns_from_entry), metadata_diff); - metadata_version = entry.alter_version; + setTableStructure(table_id, alter_context, std::move(columns_from_entry), metadata_diff, entry.alter_version); - LOG_INFO(log, "Applied changes to the metadata of the table. Current metadata version: {}", metadata_version); + LOG_INFO(log, "Applied changes to the metadata of the table. Current metadata version: {}", current_metadata->getMetadataVersion()); } { @@ -4996,7 +5003,7 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer /// This transaction may not happen, but it's OK, because on the next retry we will eventually create/update this node /// TODO Maybe do in in one transaction for Replicated database? - zookeeper->createOrUpdate(fs::path(replica_path) / "metadata_version", std::to_string(metadata_version), zkutil::CreateMode::Persistent); + zookeeper->createOrUpdate(fs::path(replica_path) / "metadata_version", std::to_string(current_metadata->getMetadataVersion()), zkutil::CreateMode::Persistent); return true; } @@ -5120,7 +5127,7 @@ void StorageReplicatedMergeTree::alter( size_t mutation_path_idx = std::numeric_limits::max(); String new_metadata_str = future_metadata_in_zk.toString(); - ops.emplace_back(zkutil::makeSetRequest(fs::path(zookeeper_path) / "metadata", new_metadata_str, metadata_version)); + ops.emplace_back(zkutil::makeSetRequest(fs::path(zookeeper_path) / "metadata", new_metadata_str, current_metadata->getMetadataVersion())); String new_columns_str = future_metadata.columns.toString(); ops.emplace_back(zkutil::makeSetRequest(fs::path(zookeeper_path) / "columns", new_columns_str, -1)); @@ -5136,7 +5143,7 @@ void StorageReplicatedMergeTree::alter( /// We can be sure, that in case of successful commit in zookeeper our /// version will increments by 1. Because we update with version check. - int new_metadata_version = metadata_version + 1; + int new_metadata_version = current_metadata->getMetadataVersion() + 1; alter_entry->type = LogEntry::ALTER_METADATA; alter_entry->source_replica = replica_name; diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index a4ac60052bc..e0143b9aac6 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -214,8 +214,6 @@ public: /// It's used if not set in engine's arguments while creating a replicated table. static String getDefaultReplicaName(const ContextPtr & context_); - int getMetadataVersion() const { return metadata_version; } - /// Modify a CREATE TABLE query to make a variant which must be written to a backup. void adjustCreateQueryForBackup(ASTPtr & create_query) const override; @@ -425,7 +423,6 @@ private: std::atomic shutdown_called {false}; std::atomic flush_called {false}; - int metadata_version = 0; /// Threads. /// A task that keeps track of the updates in the logs of all replicas and loads them into the queue. @@ -502,8 +499,10 @@ private: /// A part of ALTER: apply metadata changes only (data parts are altered separately). /// Must be called under IStorage::lockForAlter() lock. - void setTableStructure(const StorageID & table_id, const ContextPtr & local_context, - ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff); + void setTableStructure( + const StorageID & table_id, const ContextPtr & local_context, + ColumnsDescription new_columns, const ReplicatedMergeTreeTableMetadata::Diff & metadata_diff, + int32_t new_metadata_version); /** Check that the set of parts corresponds to that in ZK (/replicas/me/parts/). * If any parts described in ZK are not locally, throw an exception. From 9416401406b4978677d3d77a41cb80e07213f66c Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 20:52:10 +0100 Subject: [PATCH 026/445] Fix --- src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index df4dea978a1..d6e3f53c757 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1760,7 +1760,7 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo if (in_partition == mutations_by_partition.end()) return {}; - Int64 part_data_version = part->info.getDataVersion(); + Int64 part_metadata_version = part->getMetadataVersion(); std::map result; /// Here we return mutation commands for part which has bigger mutation version than part mutation version. /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions @@ -1771,13 +1771,12 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo { if (mutation_status->entry->alter_version != -1) { - if (mutation_version > part_data_version) + if (mutation_status->entry->alter_version > part_metadata_version) { result[mutation_version] = mutation_status->entry->commands; } else { - result[mutation_version] = mutation_status->entry->commands; break; } } From 51b6154d68a6fe7da0ab06f955f8fba8415c09a4 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 20:56:09 +0100 Subject: [PATCH 027/445] Fix stupid bug --- src/Storages/StorageReplicatedMergeTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 3083fbd2c46..9559c61e1a5 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -453,7 +453,7 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( Coordination::Stat metadata_stat; current_zookeeper->get(zookeeper_path + "/metadata", &metadata_stat); - StorageInMemoryMetadata storage_metadata; + StorageInMemoryMetadata storage_metadata(*metadata_snapshot); storage_metadata.setMetadataVersion(metadata_stat.version); setInMemoryMetadata(storage_metadata); } From fd4ae0990ba3bbb00c3353c911d0c8df4ea7abe8 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 3 Feb 2023 21:07:34 +0100 Subject: [PATCH 028/445] Tiny fix --- src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp | 1 + src/Storages/MergeTree/IMergeTreeDataPart.cpp | 8 ++++++++ src/Storages/MergeTree/IMergeTreeDataPart.h | 2 ++ src/Storages/MergeTree/MergeTreeData.cpp | 6 ------ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp b/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp index 175df9b6e28..7a9f927ae0f 100644 --- a/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp +++ b/src/Storages/MergeTree/DataPartStorageOnDiskBase.cpp @@ -683,6 +683,7 @@ void DataPartStorageOnDiskBase::clearDirectory( request.emplace_back(fs::path(dir) / "default_compression_codec.txt", true); request.emplace_back(fs::path(dir) / "delete-on-destroy.txt", true); request.emplace_back(fs::path(dir) / "txn_version.txt", true); + request.emplace_back(fs::path(dir) / "metadata_version.txt", true); disk->removeSharedFiles(request, !can_remove_shared_data, names_not_to_remove); disk->removeDirectory(dir); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 3424ef5bf0a..5fb529ddf64 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -661,6 +661,7 @@ void IMergeTreeDataPart::appendFilesOfColumnsChecksumsIndexes(Strings & files, b appendFilesOfPartitionAndMinMaxIndex(files); appendFilesOfTTLInfos(files); appendFilesOfDefaultCompressionCodec(files); + appendFilesOfMetadataVersion(files); } if (!parent_part && include_projection) @@ -980,6 +981,11 @@ void IMergeTreeDataPart::appendFilesOfDefaultCompressionCodec(Strings & files) files.push_back(DEFAULT_COMPRESSION_CODEC_FILE_NAME); } +void IMergeTreeDataPart::appendFilesOfMetadataVersion(Strings & files) +{ + files.push_back(METADATA_VERSION_FILE_NAME); +} + CompressionCodecPtr IMergeTreeDataPart::detectDefaultCompressionCodec() const { /// In memory parts doesn't have any compression @@ -1346,6 +1352,8 @@ void IMergeTreeDataPart::loadColumns(bool require) setColumns(loaded_columns, infos, loaded_metadata_version); } + + /// Project part / part with project parts / compact part doesn't support LWD. bool IMergeTreeDataPart::supportLightweightDeleteMutate() const { diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index df83d138322..d706045c3fe 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -589,6 +589,8 @@ private: static void appendFilesOfDefaultCompressionCodec(Strings & files); + static void appendFilesOfMetadataVersion(Strings & files); + /// Found column without specific compression and return codec /// for this column with default parameters. CompressionCodecPtr detectDefaultCompressionCodec() const; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index b35d58688d5..e569513914e 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7536,11 +7536,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) { - if (!part_columns.has(command.column_name)) - continue; - - part_columns.rename(command.column_name, command.rename_to); - if (auto it = rename_map.find(command.column_name); it != rename_map.end()) { auto rename_source = it->second; @@ -7548,7 +7543,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa rename_map[command.rename_to] = rename_source; } - else rename_map[command.rename_to] = command.column_name; } From e51e385017178260af81e40e933aae9238f241b5 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 14:16:17 +0100 Subject: [PATCH 029/445] fix style --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 5fb529ddf64..8f5f353b777 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1353,7 +1353,6 @@ void IMergeTreeDataPart::loadColumns(bool require) } - /// Project part / part with project parts / compact part doesn't support LWD. bool IMergeTreeDataPart::supportLightweightDeleteMutate() const { From a02e6f3d5b0f83203b8063ea7ed51d5a24d51b05 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 14:57:21 +0100 Subject: [PATCH 030/445] fix bug ordinary merge tree --- src/Storages/StorageMergeTree.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 37c2ca8b746..a6f26654a80 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -2111,13 +2111,16 @@ std::map StorageMergeTree::getAlterMutationCommandsFo { std::lock_guard lock(currently_processing_in_background_mutex); - Int64 part_mutation_version = part->info.getMutationVersion(); + Int64 part_data_version = part->info.getDataVersion(); std::map result; - for (const auto & current_mutation_by_version : current_mutations_by_version) + if (!current_mutations_by_version.empty()) { - if (static_cast(current_mutation_by_version.first) > part_mutation_version) - result[current_mutation_by_version.first] = current_mutation_by_version.second.commands; + const auto & [latest_mutation_id, latest_commands] = *current_mutations_by_version.rbegin(); + if (part_data_version < static_cast(latest_mutation_id)) + { + result[latest_mutation_id] = latest_commands.commands; + } } return result; } From bd4a3ce2d52d0656fa7e502281023a997c952318 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 15:05:13 +0100 Subject: [PATCH 031/445] Update src/Storages/MergeTree/DataPartsExchange.cpp --- src/Storages/MergeTree/DataPartsExchange.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 1864ce37ddf..86bf86c916e 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -692,7 +692,6 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToMemory( auto block = block_in.read(); throttler->add(block.bytes()); - //// TODO Read them from file new_data_part->setColumns(block.getNamesAndTypesList(), {}, metadata_snapshot->getMetadataVersion()); if (!is_projection) From 30a7198411b42f07287ca99b32984ace43de4a40 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 15:06:28 +0100 Subject: [PATCH 032/445] Update src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp --- src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index fb05cc1bddd..6be4cadc03e 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1769,7 +1769,7 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. - for (auto [mutation_version, mutation_status] : in_partition->second | std::views::reverse) + for (const auto & [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { if (mutation_status->entry->alter_version != -1) { From 2a0484ca41316a11d6706bdb435262980a996405 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 18:04:42 +0100 Subject: [PATCH 033/445] Add commnents, fix bugs --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 6 ++ src/Storages/MergeTree/IMergeTreeDataPart.h | 10 ++ src/Storages/MergeTree/MergeTreeData.cpp | 18 ++-- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 8 +- .../02555_davengers_rename_chain.reference | 26 ++++++ .../02555_davengers_rename_chain.sql | 91 +++++++++++++++++++ 6 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 tests/queries/0_stateless/02555_davengers_rename_chain.reference create mode 100644 tests/queries/0_stateless/02555_davengers_rename_chain.sql diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 8f5f353b777..cfd298952fa 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -976,6 +976,12 @@ void IMergeTreeDataPart::removeVersionMetadata() getDataPartStorage().removeFileIfExists("txn_version.txt"); } + +void IMergeTreeDataPart::removeMetadataVersion() +{ + getDataPartStorage().removeFileIfExists(METADATA_VERSION_FILE_NAME); +} + void IMergeTreeDataPart::appendFilesOfDefaultCompressionCodec(Strings & files) { files.push_back(DEFAULT_COMPRESSION_CODEC_FILE_NAME); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index d706045c3fe..e31c83a0278 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -139,7 +139,10 @@ public: String getTypeName() const { return getType().toString(); } + /// We could have separate method like setMetadata, but it's much more convenient to set it up with columns void setColumns(const NamesAndTypesList & new_columns, const SerializationInfoByName & new_infos, int32_t metadata_version_); + + /// Version of metadata for part (columns, pk and so on) int32_t getMetadataVersion() const { return metadata_version; } const NamesAndTypesList & getColumns() const { return columns; } @@ -311,6 +314,7 @@ public: mutable VersionMetadata version; + /// Version of part metadata (columns, pk and so on). Managed properly only for replicated merge tree. int32_t metadata_version; /// For data in RAM ('index') @@ -384,8 +388,10 @@ public: /// (number of rows, number of rows with default values, etc). static inline constexpr auto SERIALIZATION_FILE_NAME = "serialization.json"; + /// Version used for transactions. static inline constexpr auto TXN_VERSION_METADATA_FILE_NAME = "txn_version.txt"; + static inline constexpr auto METADATA_VERSION_FILE_NAME = "metadata_version.txt"; /// One of part files which is used to check how many references (I'd like @@ -450,7 +456,11 @@ public: void writeDeleteOnDestroyMarker(); void removeDeleteOnDestroyMarker(); + /// It may look like a stupid joke. but these two methods are absolutely unrelated. + /// This one is about removing file with metadata about part version (for transactions) void removeVersionMetadata(); + /// This one is about removing file with version of part's metadata (columns, pk and so on) + void removeMetadataVersion(); mutable std::atomic removal_state = DataPartRemovalState::NOT_ATTEMPTED; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index be53a126c18..d96a903e6bd 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4374,6 +4374,11 @@ MergeTreeData::DataPartPtr MergeTreeData::getPartIfExistsUnlocked(const MergeTre static void loadPartAndFixMetadataImpl(MergeTreeData::MutableDataPartPtr part) { + /// Remove metadata version file and take it from table. + /// Currently we cannot attach parts with different schema, so + /// we can assume that it's equal to table's current schema. + part->removeMetadataVersion(); + part->loadColumnsChecksumsIndexes(false, true); part->modification_time = part->getDataPartStorage().getLastModified().epochTime(); part->removeDeleteOnDestroyMarker(); @@ -7565,9 +7570,9 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa { std::map commands_map = getAlterMutationCommandsForPart(part); - auto part_columns = part->getColumnsDescription(); AlterConversions result{}; auto & rename_map = result.rename_map; + /// Squash "log of renames" into single map for (const auto & [version, commands] : commands_map) { for (const auto & command : commands) @@ -7577,15 +7582,7 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) { - if (auto it = rename_map.find(command.column_name); it != rename_map.end()) - { - auto rename_source = it->second; - rename_map.erase(it); - - rename_map[command.rename_to] = rename_source; - } - else - rename_map[command.rename_to] = command.column_name; + rename_map[command.rename_to] = command.column_name; } } } @@ -7596,6 +7593,7 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa it = rename_map.erase(it); else ++it; + } return result; diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 6be4cadc03e..341007e79e5 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1764,21 +1764,21 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo Int64 part_metadata_version = part->getMetadataVersion(); std::map result; - /// Here we return mutation commands for part which has bigger mutation version than part mutation version. + /// Here we return mutation commands for part which has bigger alter version than part metadata version. /// Please note, we don't use getDataVersion(). It's because these alter commands are used for in-fly conversions - /// of part's metadata. It mean that even if we have mutation with version X and part with data version X+10, but - /// without mutation version part can still have wrong metadata and we have to apply this change on-fly if needed. - + /// of part's metadata. for (const auto & [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { if (mutation_status->entry->alter_version != -1) { + /// we take commands with bigger metadata version if (mutation_status->entry->alter_version > part_metadata_version) { result[mutation_version] = mutation_status->entry->commands; } else { + /// entries are ordered, we processing them in reverse order so we can break break; } } diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.reference b/tests/queries/0_stateless/02555_davengers_rename_chain.reference new file mode 100644 index 00000000000..a9fc4b395e2 --- /dev/null +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.reference @@ -0,0 +1,26 @@ +{"a1":"1","b1":"2","c":"3"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +{"a1":"7","b1":"8","c":"9"} +~~~~~~~ +{"b":"1","a":"2","c":"3"} +{"b":"4","a":"5","c":"6"} +{"b":"7","a":"8","c":"9"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +~~~~~~~ +{"a1":"1","b1":"2","c":"3"} +{"a1":"4","b1":"5","c":"6"} +{"a1":"7","b1":"8","c":"9"} +~~~~~~~ +{"b":"1","a":"2","c":"3"} +{"b":"4","a":"5","c":"6"} +{"b":"7","a":"8","c":"9"} +~~~~~~~ diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sql b/tests/queries/0_stateless/02555_davengers_rename_chain.sql new file mode 100644 index 00000000000..eae345d0472 --- /dev/null +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sql @@ -0,0 +1,91 @@ +DROP TABLE IF EXISTS wrong_metadata; + +CREATE TABLE wrong_metadata( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO wrong_metadata VALUES (1, 2, 3); + +SYSTEM STOP MERGES wrong_metadata; + +ALTER TABLE wrong_metadata RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; + +SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +INSERT INTO wrong_metadata VALUES (4, 5, 6); + +SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +ALTER TABLE wrong_metadata RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; +SELECT sleep(1) FORMAT Null; + +INSERT INTO wrong_metadata VALUES (7, 8, 9); + +SELECT * FROM wrong_metadata ORDER by a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +SYSTEM START MERGES wrong_metadata; + +SYSTEM SYNC REPLICA wrong_metadata; + +SELECT * FROM wrong_metadata order by a FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +DROP TABLE IF EXISTS wrong_metadata; + +DROP TABLE IF EXISTS wrong_metadata_compact; + +CREATE TABLE wrong_metadata_compact( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_compact', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 10000000; + +INSERT INTO wrong_metadata_compact VALUES (1, 2, 3); + +SYSTEM STOP MERGES wrong_metadata_compact; + +ALTER TABLE wrong_metadata_compact RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; + +SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +INSERT INTO wrong_metadata_compact VALUES (4, 5, 6); + +SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +ALTER TABLE wrong_metadata_compact RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; + +SELECT sleep(1) FORMAT Null; +SELECT sleep(1) FORMAT Null; + +INSERT INTO wrong_metadata_compact VALUES (7, 8, 9); + +SELECT * FROM wrong_metadata_compact ORDER by a1 FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +SYSTEM START MERGES wrong_metadata_compact; + +SYSTEM SYNC REPLICA wrong_metadata_compact; + +SELECT * FROM wrong_metadata_compact order by a FORMAT JSONEachRow; +SELECT '~~~~~~~'; + +DROP TABLE IF EXISTS wrong_metadata_compact; From aac5da77e88f63a26153e76408fe305549b7c243 Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 21:32:40 +0100 Subject: [PATCH 034/445] fix stupid bug --- src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 271edb0399f..2082a7c000a 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -149,7 +149,7 @@ void ReplicatedMergeTreeAttachThread::runImpl() const bool replica_metadata_version_exists = zookeeper->tryGet(replica_path + "/metadata_version", replica_metadata_version); if (replica_metadata_version_exists) { - StorageInMemoryMetadata metadata_with_new_version; + StorageInMemoryMetadata metadata_with_new_version(*metadata_snapshot); metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); storage.setInMemoryMetadata(metadata_with_new_version); } From dd3a98e88dc3055ab17e08000e4fd4c2aeb91b2e Mon Sep 17 00:00:00 2001 From: alesapin Date: Mon, 6 Feb 2023 22:30:31 +0100 Subject: [PATCH 035/445] Fixes --- src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp | 1 + src/Storages/StorageInMemoryMetadata.h | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 1 + tests/queries/0_stateless/02361_fsync_profile_events.sh | 4 ++-- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 2082a7c000a..941d0850f67 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -150,6 +150,7 @@ void ReplicatedMergeTreeAttachThread::runImpl() if (replica_metadata_version_exists) { StorageInMemoryMetadata metadata_with_new_version(*metadata_snapshot); + metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); storage.setInMemoryMetadata(metadata_with_new_version); } diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index 2dd6c32e3c6..dfb5e9ddb54 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -50,7 +50,7 @@ struct StorageInMemoryMetadata String comment; - int32_t metadata_version; + int32_t metadata_version = 0; StorageInMemoryMetadata() = default; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 55d857c4c28..8e6e809a54a 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -5018,6 +5018,7 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer auto metadata_diff = ReplicatedMergeTreeTableMetadata(*this, getInMemoryMetadataPtr()).checkAndFindDiff(metadata_from_entry, getInMemoryMetadataPtr()->getColumns(), getContext()); setTableStructure(table_id, alter_context, std::move(columns_from_entry), metadata_diff, entry.alter_version); + current_metadata = getInMemoryMetadataPtr(); LOG_INFO(log, "Applied changes to the metadata of the table. Current metadata version: {}", current_metadata->getMetadataVersion()); } diff --git a/tests/queries/0_stateless/02361_fsync_profile_events.sh b/tests/queries/0_stateless/02361_fsync_profile_events.sh index 44a1bd58d36..5b603133f6c 100755 --- a/tests/queries/0_stateless/02361_fsync_profile_events.sh +++ b/tests/queries/0_stateless/02361_fsync_profile_events.sh @@ -44,8 +44,8 @@ for i in {1..100}; do ")" # Non retriable errors - if [[ $FileSync -ne 7 ]]; then - echo "FileSync: $FileSync != 11" >&2 + if [[ $FileSync -ne 8 ]]; then + echo "FileSync: $FileSync != 8" >&2 exit 2 fi # Check that all files was synced From 1fd9baa3f3b2c67196317723031d04687b3db7c9 Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 7 Feb 2023 12:49:51 +0100 Subject: [PATCH 036/445] Fix fetch: --- src/Storages/MergeTree/DataPartsExchange.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index 86bf86c916e..a8fbecbf523 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -768,7 +768,8 @@ void Fetcher::downloadBaseOrProjectionPartToDisk( if (file_name != "checksums.txt" && file_name != "columns.txt" && - file_name != IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME) + file_name != IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME && + file_name != IMergeTreeDataPart::METADATA_VERSION_FILE_NAME) checksums.addFile(file_name, file_size, expected_hash); } From 36f41da6e001734574f10be747e8f1762801d48d Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 7 Feb 2023 18:53:47 +0100 Subject: [PATCH 037/445] One more bugfix --- src/Storages/MergeTree/AlterConversions.cpp | 21 ++++ src/Storages/MergeTree/AlterConversions.h | 11 ++- src/Storages/MergeTree/MergeTreeData.cpp | 4 +- src/Storages/MergeTree/MutateTask.cpp | 95 ++++++++++++++++--- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 8 +- 5 files changed, 118 insertions(+), 21 deletions(-) diff --git a/src/Storages/MergeTree/AlterConversions.cpp b/src/Storages/MergeTree/AlterConversions.cpp index e2eea2e68f6..7a298b0f6ca 100644 --- a/src/Storages/MergeTree/AlterConversions.cpp +++ b/src/Storages/MergeTree/AlterConversions.cpp @@ -31,4 +31,25 @@ std::string AlterConversions::getColumnNewName(const std::string & old_name) con throw Exception(ErrorCodes::LOGICAL_ERROR, "Column {} was not renamed", old_name); } + +bool AlterConversions::isColumnRenamed(const std::string & new_name) const +{ + for (const auto & [name_to, name_from] : rename_map) + { + if (name_to == new_name) + return true; + } + return false; +} +/// Get column old name before rename (lookup by key in rename_map) +std::string AlterConversions::getColumnOldName(const std::string & new_name) const +{ + for (const auto & [name_to, name_from] : rename_map) + { + if (name_to == new_name) + return name_from; + } + throw Exception(ErrorCodes::LOGICAL_ERROR, "Column {} was not renamed", new_name); +} + } diff --git a/src/Storages/MergeTree/AlterConversions.h b/src/Storages/MergeTree/AlterConversions.h index 47f964e85fe..ada385d6100 100644 --- a/src/Storages/MergeTree/AlterConversions.h +++ b/src/Storages/MergeTree/AlterConversions.h @@ -14,17 +14,22 @@ namespace DB /// part->getColumns() and storage->getColumns(). struct AlterConversions { + struct RenamePair + { + std::string rename_to; + std::string rename_from; + }; /// Rename map new_name -> old_name - std::unordered_map rename_map; + std::vector rename_map; /// Column was renamed (lookup by value in rename_map) bool columnHasNewName(const std::string & old_name) const; /// Get new name for column (lookup by value in rename_map) std::string getColumnNewName(const std::string & old_name) const; /// Is this name is new name of column (lookup by key in rename_map) - bool isColumnRenamed(const std::string & new_name) const { return rename_map.count(new_name) > 0; } + bool isColumnRenamed(const std::string & new_name) const; /// Get column old name before rename (lookup by key in rename_map) - std::string getColumnOldName(const std::string & new_name) const { return rename_map.at(new_name); } + std::string getColumnOldName(const std::string & new_name) const; }; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index d96a903e6bd..05c75067f3f 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7582,14 +7582,14 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa /// and columns in storage. if (command.type == MutationCommand::Type::RENAME_COLUMN) { - rename_map[command.rename_to] = command.column_name; + rename_map.emplace_back(AlterConversions::RenamePair{command.rename_to, command.column_name}); } } } for (auto it = rename_map.begin(); it != rename_map.end();) { - if (it->first == it->second) + if (it->rename_to == it->rename_from) it = rename_map.erase(it); else ++it; diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 4c1ca86f5cb..16ddcaae89e 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -158,11 +158,7 @@ static void splitMutationCommands( auto alter_conversions = part->storage.getAlterConversionsForPart(part); for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { - if (part_columns.has(rename_from)) - { - for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); - part_columns.rename(rename_from, rename_to); - } + for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); } } } @@ -174,8 +170,13 @@ getColumnsForNewDataPart( const Block & updated_header, NamesAndTypesList storage_columns, const SerializationInfoByName & serialization_infos, + const MutationCommands & commands_for_interpreter, const MutationCommands & commands_for_removes) { + MutationCommands all_commands; + all_commands.insert(all_commands.end(), commands_for_interpreter.begin(), commands_for_interpreter.end()); + all_commands.insert(all_commands.end(), commands_for_removes.begin(), commands_for_removes.end()); + NameSet removed_columns; NameToNameMap renamed_columns_to_from; NameToNameMap renamed_columns_from_to; @@ -191,8 +192,37 @@ getColumnsForNewDataPart( storage_columns.emplace_back(column); } - /// All commands are validated in AlterCommand so we don't care about order - for (const auto & command : commands_for_removes) + NameToNameMap squashed_renames; + for (const auto & command : all_commands) + { + std::string result_name = command.rename_to; + + bool squashed = false; + for (const auto & [name_from, name_to] : squashed_renames) + { + if (name_to == command.column_name) + { + squashed = true; + squashed_renames[name_from] = result_name; + break; + } + } + if (!squashed) + squashed_renames[command.column_name] = result_name; + } + + MutationCommands squashed_commands; + for (const auto & command : all_commands) + { + if (squashed_renames.contains(command.column_name)) + { + squashed_commands.push_back(command); + squashed_commands.back().rename_to = squashed_renames[command.column_name]; + } + } + + + for (const auto & command : squashed_commands) { if (command.type == MutationCommand::UPDATE) { @@ -285,10 +315,10 @@ getColumnsForNewDataPart( /// should it's previous version should be dropped or removed if (renamed_columns_to_from.contains(it->name) && !was_renamed && !was_removed) throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Incorrect mutation commands, trying to rename column {} to {}, " - "but part {} already has column {}", - renamed_columns_to_from[it->name], it->name, source_part->name, it->name); + ErrorCodes::LOGICAL_ERROR, + "Incorrect mutation commands, trying to rename column {} to {}, " + "but part {} already has column {}", + renamed_columns_to_from[it->name], it->name, source_part->name, it->name); /// Column was renamed and no other column renamed to it's name /// or column is dropped. @@ -582,8 +612,46 @@ static NameToNameVector collectFilesForRenames( rename_vector.emplace_back(file_rename_from, file_rename_to); }; - /// Remove old data + NameToNameMap squashed_renames; for (const auto & command : commands_for_removes) + { + + std::string result_name; + if (command.type == MutationCommand::Type::DROP_INDEX + || command.type == MutationCommand::Type::DROP_PROJECTION + || command.type == MutationCommand::Type::DROP_COLUMN + || command.type == MutationCommand::Type::READ_COLUMN) + result_name = ""; + + if (command.type == MutationCommand::RENAME_COLUMN) + result_name = command.rename_to; + + bool squashed = false; + for (const auto & [name_from, name_to] : squashed_renames) + { + if (name_to == command.column_name) + { + squashed = true; + squashed_renames[name_from] = result_name; + break; + } + } + if (!squashed) + squashed_renames[command.column_name] = result_name; + } + + MutationCommands squashed_commands; + for (const auto & command : commands_for_removes) + { + if (squashed_renames.contains(command.column_name)) + { + squashed_commands.push_back(command); + squashed_commands.back().rename_to = squashed_renames[command.column_name]; + } + } + + /// Remove old data + for (const auto & command : squashed_commands) { if (command.type == MutationCommand::Type::DROP_INDEX) { @@ -624,7 +692,6 @@ static NameToNameVector collectFilesForRenames( String escaped_name_from = escapeForFileName(command.column_name); String escaped_name_to = escapeForFileName(command.rename_to); - ISerialization::StreamCallback callback = [&](const ISerialization::SubstreamPath & substream_path) { String stream_from = ISerialization::getFileNameForStream(command.column_name, substream_path); @@ -1698,7 +1765,7 @@ bool MutateTask::prepare() auto [new_columns, new_infos] = MutationHelpers::getColumnsForNewDataPart( ctx->source_part, ctx->updated_header, ctx->storage_columns, - ctx->source_part->getSerializationInfos(), ctx->commands_for_part); + ctx->source_part->getSerializationInfos(), ctx->for_interpreter, ctx->for_file_renames); ctx->new_data_part->setColumns(new_columns, new_infos, ctx->metadata_snapshot->getMetadataVersion()); ctx->new_data_part->partition.assign(ctx->source_part->partition); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index 341007e79e5..f491cdc8cd1 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1769,10 +1769,14 @@ std::map ReplicatedMergeTreeQueue::getAlterMutationCo /// of part's metadata. for (const auto & [mutation_version, mutation_status] : in_partition->second | std::views::reverse) { - if (mutation_status->entry->alter_version != -1) + int32_t alter_version = mutation_status->entry->alter_version; + if (alter_version != -1) { + if (!alter_sequence.canExecuteDataAlter(alter_version, lock)) + continue; + /// we take commands with bigger metadata version - if (mutation_status->entry->alter_version > part_metadata_version) + if (alter_version > part_metadata_version) { result[mutation_version] = mutation_status->entry->commands; } From bbf044f4773547d40401838a7e8112f674ae96cc Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 7 Feb 2023 22:30:38 +0100 Subject: [PATCH 038/445] Fxi --- src/Storages/MergeTree/MutateTask.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 16ddcaae89e..5738a48783d 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -114,6 +114,12 @@ static void splitMutationCommands( .column_name = rename_to, }); + for_file_renames.push_back( + { + .type = MutationCommand::Type::RENAME_COLUMN, + .column_name = rename_from, + .rename_to = rename_to + }); part_columns.rename(rename_from, rename_to); } } @@ -615,16 +621,7 @@ static NameToNameVector collectFilesForRenames( NameToNameMap squashed_renames; for (const auto & command : commands_for_removes) { - - std::string result_name; - if (command.type == MutationCommand::Type::DROP_INDEX - || command.type == MutationCommand::Type::DROP_PROJECTION - || command.type == MutationCommand::Type::DROP_COLUMN - || command.type == MutationCommand::Type::READ_COLUMN) - result_name = ""; - - if (command.type == MutationCommand::RENAME_COLUMN) - result_name = command.rename_to; + std::string result_name = command.rename_to; bool squashed = false; for (const auto & [name_from, name_to] : squashed_renames) From a28d10a81013c71d26c8c5d5d87791bc3533617b Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 14:50:30 +0100 Subject: [PATCH 039/445] Fxi --- src/Storages/MergeTree/DataPartsExchange.cpp | 11 +++-- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 6 ++- src/Storages/MergeTree/MutateTask.cpp | 9 ++++- tests/integration/test_merge_tree_s3/test.py | 7 ++-- ...system_cache_on_write_operations.reference | 40 +++++++++---------- 5 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/Storages/MergeTree/DataPartsExchange.cpp b/src/Storages/MergeTree/DataPartsExchange.cpp index a8fbecbf523..35c627c3210 100644 --- a/src/Storages/MergeTree/DataPartsExchange.cpp +++ b/src/Storages/MergeTree/DataPartsExchange.cpp @@ -63,8 +63,9 @@ constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_DEFAULT_COMPRESSION = 4; constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_UUID = 5; constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_ZERO_COPY = 6; constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PROJECTION = 7; +constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION = 8; // Reserved for ALTER PRIMARY KEY -// constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PRIMARY_KEY = 8; +// constexpr auto REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PRIMARY_KEY = 9; std::string getEndpointId(const std::string & node_id) { @@ -120,7 +121,7 @@ void Service::processQuery(const HTMLForm & params, ReadBuffer & /*body*/, Write MergeTreePartInfo::fromPartName(part_name, data.format_version); /// We pretend to work as older server version, to be sure that client will correctly process our version - response.addCookie({"server_protocol_version", toString(std::min(client_protocol_version, REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PROJECTION))}); + response.addCookie({"server_protocol_version", toString(std::min(client_protocol_version, REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION))}); LOG_TRACE(log, "Sending part {}", part_name); @@ -280,6 +281,10 @@ MergeTreeData::DataPart::Checksums Service::sendPartFromDisk( && name == IMergeTreeDataPart::DEFAULT_COMPRESSION_CODEC_FILE_NAME) continue; + if (client_protocol_version < REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION + && name == IMergeTreeDataPart::METADATA_VERSION_FILE_NAME) + continue; + files_to_replicate.insert(name); } @@ -407,7 +412,7 @@ MergeTreeData::MutableDataPartPtr Fetcher::fetchSelectedPart( { {"endpoint", getEndpointId(replica_path)}, {"part", part_name}, - {"client_protocol_version", toString(REPLICATION_PROTOCOL_VERSION_WITH_PARTS_PROJECTION)}, + {"client_protocol_version", toString(REPLICATION_PROTOCOL_VERSION_WITH_METADATA_VERSION)}, {"compress", "false"} }); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index cfd298952fa..94aa2a72949 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1352,9 +1352,13 @@ void IMergeTreeDataPart::loadColumns(bool require) else { loaded_metadata_version = metadata_snapshot->getMetadataVersion(); + + writeMetadata(METADATA_VERSION_FILE_NAME, {}, [loaded_metadata_version](auto & buffer) + { + writeIntText(loaded_metadata_version, buffer); + }); } - ///TODO read metadata here setColumns(loaded_columns, infos, loaded_metadata_version); } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 5738a48783d..340709bff2c 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -735,6 +735,7 @@ void finalizeMutatedPart( ExecuteTTLType execute_ttl_type, const CompressionCodecPtr & codec, ContextPtr context, + StorageMetadataPtr metadata_snapshot, bool sync) { std::vector> written_files; @@ -783,6 +784,12 @@ void finalizeMutatedPart( written_files.push_back(std::move(out_comp)); } + { + auto out_metadata = new_data_part->getDataPartStorage().writeFile(IMergeTreeDataPart::METADATA_VERSION_FILE_NAME, 4096, context->getWriteSettings()); + DB::writeText(metadata_snapshot->getMetadataVersion(), *out_metadata); + written_files.push_back(std::move(out_metadata)); + } + { /// Write a file with a description of columns. auto out_columns = new_data_part->getDataPartStorage().writeFile("columns.txt", 4096, context->getWriteSettings()); @@ -1555,7 +1562,7 @@ private: } } - MutationHelpers::finalizeMutatedPart(ctx->source_part, ctx->new_data_part, ctx->execute_ttl_type, ctx->compression_codec, ctx->context, ctx->need_sync); + MutationHelpers::finalizeMutatedPart(ctx->source_part, ctx->new_data_part, ctx->execute_ttl_type, ctx->compression_codec, ctx->context, ctx->metadata_snapshot, ctx->need_sync); } diff --git a/tests/integration/test_merge_tree_s3/test.py b/tests/integration/test_merge_tree_s3/test.py index f0f81100320..c8c97f4d40f 100644 --- a/tests/integration/test_merge_tree_s3/test.py +++ b/tests/integration/test_merge_tree_s3/test.py @@ -52,8 +52,10 @@ def cluster(): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +FILES_OVERHEAD_PER_PART_COMPACT = 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION def create_table(node, table_name, **additional_settings): @@ -232,7 +234,6 @@ def test_insert_same_partition_and_merge(cluster, merge_vertical, node_name): def test_alter_table_columns(cluster, node_name): node = cluster.instances[node_name] create_table(node, "s3_test") - minio = cluster.minio_client node.query( "INSERT INTO s3_test VALUES {}".format(generate_values("2020-01-03", 4096)) diff --git a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference index bbca9bbbfee..7fa38f6ff44 100644 --- a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference +++ b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference @@ -7,25 +7,25 @@ file_segment_range_begin: 0 file_segment_range_end: 745 size: 746 state: DOWNLOADED -7 -7 +8 +8 0 2 2 -7 +8 Row 1: ────── file_segment_range_begin: 0 file_segment_range_end: 1659 size: 1660 state: DOWNLOADED -7 -7 -7 -7 -21 -31 -38 +8 +8 +8 +8 +24 +27 +27 5010500 18816 Using storage policy: local_cache @@ -37,24 +37,24 @@ file_segment_range_begin: 0 file_segment_range_end: 745 size: 746 state: DOWNLOADED -7 -7 +8 +8 0 2 2 -7 +8 Row 1: ────── file_segment_range_begin: 0 file_segment_range_end: 1659 size: 1660 state: DOWNLOADED -7 -7 -7 -7 -21 -31 -38 +8 +8 +8 +8 +24 +27 +27 5010500 18816 From f81f0b351fc7882aae2ddf4e156c7fec235d9ad2 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 8 Feb 2023 13:58:55 +0000 Subject: [PATCH 040/445] Automatic style fix --- tests/integration/test_merge_tree_s3/test.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_merge_tree_s3/test.py b/tests/integration/test_merge_tree_s3/test.py index c8c97f4d40f..696c016f760 100644 --- a/tests/integration/test_merge_tree_s3/test.py +++ b/tests/integration/test_merge_tree_s3/test.py @@ -54,8 +54,16 @@ FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 FILES_OVERHEAD_METADATA_VERSION = 1 -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION -FILES_OVERHEAD_PER_PART_COMPACT = 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) def create_table(node, table_name, **additional_settings): From 5f33aaf59b12d06d2f1dac3412ce8b58723426b0 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 16:31:37 +0100 Subject: [PATCH 041/445] Fix three leftovers --- tests/integration/test_merge_tree_hdfs/test.py | 14 ++++++++++++-- .../test_replicated_merge_tree_s3/test.py | 14 ++++++++++++-- .../test.py | 14 ++++++++++++-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_merge_tree_hdfs/test.py b/tests/integration/test_merge_tree_hdfs/test.py index 3950077e619..782237539fa 100644 --- a/tests/integration/test_merge_tree_hdfs/test.py +++ b/tests/integration/test_merge_tree_hdfs/test.py @@ -43,8 +43,18 @@ def create_table(cluster, table_name, additional_settings=None): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) @pytest.fixture(scope="module") diff --git a/tests/integration/test_replicated_merge_tree_s3/test.py b/tests/integration/test_replicated_merge_tree_s3/test.py index 0d978bb6967..b90e28dfdb2 100644 --- a/tests/integration/test_replicated_merge_tree_s3/test.py +++ b/tests/integration/test_replicated_merge_tree_s3/test.py @@ -44,8 +44,18 @@ def cluster(): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) def random_string(length): diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 60a1b9b9746..1b80b80987d 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -45,8 +45,18 @@ def cluster(): FILES_OVERHEAD = 1 FILES_OVERHEAD_PER_COLUMN = 2 # Data and mark files -FILES_OVERHEAD_PER_PART_WIDE = FILES_OVERHEAD_PER_COLUMN * 3 + 2 + 6 + 1 -FILES_OVERHEAD_PER_PART_COMPACT = 10 + 1 +FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC = 1 +FILES_OVERHEAD_METADATA_VERSION = 1 +FILES_OVERHEAD_PER_PART_WIDE = ( + FILES_OVERHEAD_PER_COLUMN * 3 + + 2 + + 6 + + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + + FILES_OVERHEAD_METADATA_VERSION +) +FILES_OVERHEAD_PER_PART_COMPACT = ( + 10 + FILES_OVERHEAD_DEFAULT_COMPRESSION_CODEC + FILES_OVERHEAD_METADATA_VERSION +) def random_string(length): From 242d6d0cb8b928da0d93adc4541d864b33f9fd83 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 17:25:55 +0100 Subject: [PATCH 042/445] better test --- .../02555_davengers_rename_chain.sh | 143 ++++++++++++++++++ .../02555_davengers_rename_chain.sql | 91 ----------- 2 files changed, 143 insertions(+), 91 deletions(-) create mode 100755 tests/queries/0_stateless/02555_davengers_rename_chain.sh delete mode 100644 tests/queries/0_stateless/02555_davengers_rename_chain.sql diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sh b/tests/queries/0_stateless/02555_davengers_rename_chain.sh new file mode 100755 index 00000000000..71201537170 --- /dev/null +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Tags: replica +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata" + +$CLICKHOUSE_CLIENT -n --query="CREATE TABLE wrong_metadata( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata VALUES (1, 2, 3)" + + +$CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES wrong_metadata" + + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") + if [[ $result == *"a1 UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow" + +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata VALUES (4, 5, 6)" + + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") + if [[ $result == *"b UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata VALUES (7, 8, 9)" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata ORDER by a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="SYSTEM START MERGES wrong_metadata" + +$CLICKHOUSE_CLIENT --query="SYSTEM SYNC REPLICA wrong_metadata" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata order by a FORMAT JSONEachRow" + +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata" + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata_compact" + +$CLICKHOUSE_CLIENT -n --query="CREATE TABLE wrong_metadata_compact( + a UInt64, + b UInt64, + c UInt64 +) +ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_compact', '1') +ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 10000000" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata_compact VALUES (1, 2, 3)" + +$CLICKHOUSE_CLIENT --query="SYSTEM STOP MERGES wrong_metadata_compact" + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata_compact RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") + if [[ $result == *"b1 UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata_compact VALUES (4, 5, 6)" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="ALTER TABLE wrong_metadata_compact RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0" + +counter=0 retries=60 +I=0 +while [[ $counter -lt $retries ]]; do + I=$((I + 1)) + result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") + if [[ $result == *"b UInt64"* ]]; then + break; + fi + sleep 0.1 + ((++counter)) +done + +$CLICKHOUSE_CLIENT --query="INSERT INTO wrong_metadata_compact VALUES (7, 8, 9)" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact ORDER by a1 FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="SYSTEM START MERGES wrong_metadata_compact" + +$CLICKHOUSE_CLIENT --query="SYSTEM SYNC REPLICA wrong_metadata_compact" + +$CLICKHOUSE_CLIENT --query="SELECT * FROM wrong_metadata_compact order by a FORMAT JSONEachRow" +$CLICKHOUSE_CLIENT --query="SELECT '~~~~~~~'" + +$CLICKHOUSE_CLIENT --query="DROP TABLE IF EXISTS wrong_metadata_compact" diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sql b/tests/queries/0_stateless/02555_davengers_rename_chain.sql deleted file mode 100644 index eae345d0472..00000000000 --- a/tests/queries/0_stateless/02555_davengers_rename_chain.sql +++ /dev/null @@ -1,91 +0,0 @@ -DROP TABLE IF EXISTS wrong_metadata; - -CREATE TABLE wrong_metadata( - a UInt64, - b UInt64, - c UInt64 -) -ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata', '1') -ORDER BY tuple() -SETTINGS min_bytes_for_wide_part = 0; - -INSERT INTO wrong_metadata VALUES (1, 2, 3); - -SYSTEM STOP MERGES wrong_metadata; - -ALTER TABLE wrong_metadata RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; - -SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -INSERT INTO wrong_metadata VALUES (4, 5, 6); - -SELECT * FROM wrong_metadata ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -ALTER TABLE wrong_metadata RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; -SELECT sleep(1) FORMAT Null; - -INSERT INTO wrong_metadata VALUES (7, 8, 9); - -SELECT * FROM wrong_metadata ORDER by a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -SYSTEM START MERGES wrong_metadata; - -SYSTEM SYNC REPLICA wrong_metadata; - -SELECT * FROM wrong_metadata order by a FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -DROP TABLE IF EXISTS wrong_metadata; - -DROP TABLE IF EXISTS wrong_metadata_compact; - -CREATE TABLE wrong_metadata_compact( - a UInt64, - b UInt64, - c UInt64 -) -ENGINE ReplicatedMergeTree('/test/{database}/tables/wrong_metadata_compact', '1') -ORDER BY tuple() -SETTINGS min_bytes_for_wide_part = 10000000; - -INSERT INTO wrong_metadata_compact VALUES (1, 2, 3); - -SYSTEM STOP MERGES wrong_metadata_compact; - -ALTER TABLE wrong_metadata_compact RENAME COLUMN a TO a1, RENAME COLUMN b to b1 SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; - -SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -INSERT INTO wrong_metadata_compact VALUES (4, 5, 6); - -SELECT * FROM wrong_metadata_compact ORDER BY a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -ALTER TABLE wrong_metadata_compact RENAME COLUMN a1 TO b, RENAME COLUMN b1 to a SETTINGS replication_alter_partitions_sync = 0; - -SELECT sleep(1) FORMAT Null; -SELECT sleep(1) FORMAT Null; - -INSERT INTO wrong_metadata_compact VALUES (7, 8, 9); - -SELECT * FROM wrong_metadata_compact ORDER by a1 FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -SYSTEM START MERGES wrong_metadata_compact; - -SYSTEM SYNC REPLICA wrong_metadata_compact; - -SELECT * FROM wrong_metadata_compact order by a FORMAT JSONEachRow; -SELECT '~~~~~~~'; - -DROP TABLE IF EXISTS wrong_metadata_compact; From 00686ae03b377482a2ae1ee335d4e61693a22880 Mon Sep 17 00:00:00 2001 From: alesapin Date: Wed, 8 Feb 2023 22:34:09 +0100 Subject: [PATCH 043/445] Fix tests again --- tests/integration/test_partition/test.py | 2 ++ tests/integration/test_s3_zero_copy_ttl/test.py | 4 ++-- .../02241_filesystem_cache_on_write_operations.reference | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_partition/test.py b/tests/integration/test_partition/test.py index ae4393fc6f6..b59cc21f39f 100644 --- a/tests/integration/test_partition/test.py +++ b/tests/integration/test_partition/test.py @@ -105,6 +105,8 @@ def partition_complex_assert_checksums(): "c4ca4238a0b923820dcc509a6f75849b\tshadow/1/data/test/partition_complex/19700102_2_2_0/count.txt\n" "c4ca4238a0b923820dcc509a6f75849b\tshadow/1/data/test/partition_complex/19700201_1_1_0/count.txt\n" "cfcb770c3ecd0990dcceb1bde129e6c6\tshadow/1/data/test/partition_complex/19700102_2_2_0/p.bin\n" + "cfcd208495d565ef66e7dff9f98764da\tshadow/1/data/test/partition_complex/19700102_2_2_0/metadata_version.txt\n" + "cfcd208495d565ef66e7dff9f98764da\tshadow/1/data/test/partition_complex/19700201_1_1_0/metadata_version.txt\n" "e2af3bef1fd129aea73a890ede1e7a30\tshadow/1/data/test/partition_complex/19700201_1_1_0/k.bin\n" "f2312862cc01adf34a93151377be2ddf\tshadow/1/data/test/partition_complex/19700201_1_1_0/minmax_p.idx\n" ) diff --git a/tests/integration/test_s3_zero_copy_ttl/test.py b/tests/integration/test_s3_zero_copy_ttl/test.py index 9a782aacef6..7dcf3734653 100644 --- a/tests/integration/test_s3_zero_copy_ttl/test.py +++ b/tests/integration/test_s3_zero_copy_ttl/test.py @@ -86,9 +86,9 @@ def test_ttl_move_and_s3(started_cluster): print(f"Total objects: {counter}") - if counter == 300: + if counter == 330: break print(f"Attempts remaining: {attempt}") - assert counter == 300 + assert counter == 330 diff --git a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference index 7fa38f6ff44..f3fac9b32d3 100644 --- a/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference +++ b/tests/queries/0_stateless/02241_filesystem_cache_on_write_operations.reference @@ -24,8 +24,8 @@ state: DOWNLOADED 8 8 24 -27 -27 +35 +43 5010500 18816 Using storage policy: local_cache @@ -54,7 +54,7 @@ state: DOWNLOADED 8 8 24 -27 -27 +35 +43 5010500 18816 From 0a823854137271ae19c3b498f7b1cfa2923bb220 Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 9 Feb 2023 12:23:18 +0100 Subject: [PATCH 044/445] Blind fix for trash test --- tests/integration/test_merge_tree_s3_failover/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_merge_tree_s3_failover/test.py b/tests/integration/test_merge_tree_s3_failover/test.py index d4c691fdb55..9194a1b68f6 100644 --- a/tests/integration/test_merge_tree_s3_failover/test.py +++ b/tests/integration/test_merge_tree_s3_failover/test.py @@ -89,7 +89,7 @@ def drop_table(cluster): # S3 request will be failed for an appropriate part file write. -FILES_PER_PART_BASE = 5 # partition.dat, default_compression_codec.txt, count.txt, columns.txt, checksums.txt +FILES_PER_PART_BASE = 6 # partition.dat, metadata_version.txt, default_compression_codec.txt, count.txt, columns.txt, checksums.txt FILES_PER_PART_WIDE = ( FILES_PER_PART_BASE + 1 + 1 + 3 * 2 ) # Primary index, MinMax, Mark and data file for column(s) From ba761e04d72285800aeb7faeb8d073a5745b46c1 Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 9 Feb 2023 12:31:01 +0000 Subject: [PATCH 045/445] Handle unknown exceptions --- src/Processors/Executors/StreamingFormatExecutor.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Processors/Executors/StreamingFormatExecutor.cpp b/src/Processors/Executors/StreamingFormatExecutor.cpp index cfe88344198..2223721439e 100644 --- a/src/Processors/Executors/StreamingFormatExecutor.cpp +++ b/src/Processors/Executors/StreamingFormatExecutor.cpp @@ -8,6 +8,7 @@ namespace DB namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int UNKNOWN_EXCEPTION; } StreamingFormatExecutor::StreamingFormatExecutor( @@ -99,6 +100,12 @@ size_t StreamingFormatExecutor::execute() auto exception = Exception(Exception::CreateFromSTDTag{}, e); return on_error(result_columns, exception); } + catch (...) + { + format->resetParser(); + auto exception = Exception(ErrorCodes::UNKNOWN_EXCEPTION, "Unknowk exception while executing StreamingFormatExecutor with format {}", format->getName()); + return on_error(result_columns, exception); + } } } From 8821074f8393aa63325d6da901a1b0c312d21f99 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:08:24 +0100 Subject: [PATCH 046/445] Fix style --- tests/integration/test_storage_kafka/test.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_storage_kafka/test.py b/tests/integration/test_storage_kafka/test.py index 9e79b0bc55f..8be6022a430 100644 --- a/tests/integration/test_storage_kafka/test.py +++ b/tests/integration/test_storage_kafka/test.py @@ -4470,7 +4470,7 @@ def test_bad_messages_parsing(kafka_cluster): print(format_name) - kafka_create_topic(admin_client, f'{format_name}_err') + kafka_create_topic(admin_client, f"{format_name}_err") instance.query( f""" @@ -4491,7 +4491,7 @@ def test_bad_messages_parsing(kafka_cluster): ) messages = ["qwertyuiop", "asdfghjkl", "zxcvbnm"] - kafka_produce(kafka_cluster, f'{format_name}_err', messages) + kafka_produce(kafka_cluster, f"{format_name}_err", messages) attempt = 0 rows = 0 @@ -4503,9 +4503,9 @@ def test_bad_messages_parsing(kafka_cluster): assert rows == len(messages) - kafka_delete_topic(admin_client, f'{format_name}_err') + kafka_delete_topic(admin_client, f"{format_name}_err") - protobuf_schema=""" + protobuf_schema = """ syntax = "proto3"; message Message { @@ -4538,7 +4538,7 @@ message Message { print(format_name) - kafka_create_topic(admin_client, f'{format_name}_err') + kafka_create_topic(admin_client, f"{format_name}_err") messages = ["qwertyuiop", "poiuytrewq", "zxcvbnm"] kafka_produce(kafka_cluster, f'{format_name}_err', messages) @@ -4553,9 +4553,9 @@ message Message { assert rows == len(messages) - kafka_delete_topic(admin_client, f'{format_name}_err') + kafka_delete_topic(admin_client, f"{format_name}_err") - capn_proto_schema=""" + capn_proto_schema = """ @0xd9dd7b35452d1c4f; struct Message @@ -4587,7 +4587,7 @@ struct Message print("CapnProto") - kafka_create_topic(admin_client, 'CapnProto_err') + kafka_create_topic(admin_client, "CapnProto_err") messages = ["qwertyuiop", "asdfghjkl", "zxcvbnm"] kafka_produce(kafka_cluster, 'CapnProto_err', messages) @@ -4602,7 +4602,7 @@ struct Message assert rows == len(messages) - kafka_delete_topic(admin_client, 'CapnProto_err') + kafka_delete_topic(admin_client, "CapnProto_err") if __name__ == "__main__": From 59c52c503dfced06b6fadae16aae4d43222692c3 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:09:46 +0100 Subject: [PATCH 047/445] Fix style --- tests/integration/helpers/cluster.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 28af442a3d5..a344bf99619 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -4401,7 +4401,13 @@ class ClickHouseInstance: def create_format_schema(self, file_name, content): self.exec_in_container( - ["bash", "-c", "echo '{}' > {}".format(content, "/var/lib/clickhouse/format_schemas/" + file_name)] + [ + "bash", + "-c", + "echo '{}' > {}".format( + content, "/var/lib/clickhouse/format_schemas/" + file_name + ), + ] ) class ClickHouseKiller(object): From 82521bb9734d4ecfbfdf5e4842826ea9228f01a0 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Thu, 9 Feb 2023 13:33:49 +0000 Subject: [PATCH 048/445] Automatic style fix --- tests/ci/stress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/stress.py b/tests/ci/stress.py index e21d34da5c0..2c77731a7b6 100755 --- a/tests/ci/stress.py +++ b/tests/ci/stress.py @@ -298,7 +298,7 @@ if __name__ == "__main__": ] ) hung_check_log = os.path.join(args.output_folder, "hung_check.log") - tee = Popen(['/usr/bin/tee', hung_check_log], stdin=PIPE) + tee = Popen(["/usr/bin/tee", hung_check_log], stdin=PIPE) res = call(cmd, shell=True, stdout=tee.stdin, stderr=STDOUT) tee.stdin.close() if res != 0 and have_long_running_queries: From 947923321c9b3ecaa90c15117ba1fab6d424fffd Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:55:21 +0100 Subject: [PATCH 049/445] Fix style --- tests/integration/helpers/cluster.py | 1 + tests/integration/test_storage_kafka/test.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index a344bf99619..75632b3c0ea 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -4410,6 +4410,7 @@ class ClickHouseInstance: ] ) + class ClickHouseKiller(object): def __init__(self, clickhouse_node): self.clickhouse_node = clickhouse_node diff --git a/tests/integration/test_storage_kafka/test.py b/tests/integration/test_storage_kafka/test.py index 8be6022a430..51871eae3d8 100644 --- a/tests/integration/test_storage_kafka/test.py +++ b/tests/integration/test_storage_kafka/test.py @@ -4541,7 +4541,7 @@ message Message { kafka_create_topic(admin_client, f"{format_name}_err") messages = ["qwertyuiop", "poiuytrewq", "zxcvbnm"] - kafka_produce(kafka_cluster, f'{format_name}_err', messages) + kafka_produce(kafka_cluster, f"{format_name}_err", messages) attempt = 0 rows = 0 @@ -4590,7 +4590,7 @@ struct Message kafka_create_topic(admin_client, "CapnProto_err") messages = ["qwertyuiop", "asdfghjkl", "zxcvbnm"] - kafka_produce(kafka_cluster, 'CapnProto_err', messages) + kafka_produce(kafka_cluster, "CapnProto_err", messages) attempt = 0 rows = 0 From fd52979991439905a871ff22cefb1fd2775f6ebd Mon Sep 17 00:00:00 2001 From: alesapin Date: Thu, 9 Feb 2023 14:56:13 +0100 Subject: [PATCH 050/445] Fix snapshot for ordinary merge tree --- src/Storages/StorageMergeTree.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 5aa4a2bc98d..78b29c943dc 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1262,7 +1262,10 @@ bool StorageMergeTree::scheduleDataProcessingJob(BackgroundJobsAssignee & assign } if (mutate_entry) { - auto task = std::make_shared(*this, metadata_snapshot, mutate_entry, shared_lock, common_assignee_trigger); + /// We take new metadata snapshot here. It's because mutation commands can be executed only with metadata snapshot + /// which is equal or more fresh than commands themselves. In extremely rare case it can happen that we will have alter + /// in between we took snapshot above and selected commands. That is why we take new snapshot here. + auto task = std::make_shared(*this, getInMemoryMetadataPtr(), mutate_entry, shared_lock, common_assignee_trigger); assignee.scheduleMergeMutateTask(task); return true; } From 5c62afd15e8054a66162b4be359248c867709f97 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 9 Feb 2023 15:01:32 +0100 Subject: [PATCH 051/445] Fix style --- tests/ci/stress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci/stress.py b/tests/ci/stress.py index 2c77731a7b6..e62a37fb165 100755 --- a/tests/ci/stress.py +++ b/tests/ci/stress.py @@ -302,7 +302,7 @@ if __name__ == "__main__": res = call(cmd, shell=True, stdout=tee.stdin, stderr=STDOUT) tee.stdin.close() if res != 0 and have_long_running_queries: - logging.info("Hung check failed with exit code {}".format(res)) + logging.info("Hung check failed with exit code %d", res) else: hung_check_status = "No queries hung\tOK\t\\N\t\n" with open( From 93dfbe6617b3b5d47ade4c2127026c1e5b2108c2 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 9 Feb 2023 15:05:06 +0100 Subject: [PATCH 052/445] Fix mypy style --- tests/ci/stress.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ci/stress.py b/tests/ci/stress.py index e62a37fb165..979806a87fd 100755 --- a/tests/ci/stress.py +++ b/tests/ci/stress.py @@ -300,7 +300,8 @@ if __name__ == "__main__": hung_check_log = os.path.join(args.output_folder, "hung_check.log") tee = Popen(["/usr/bin/tee", hung_check_log], stdin=PIPE) res = call(cmd, shell=True, stdout=tee.stdin, stderr=STDOUT) - tee.stdin.close() + if tee.stdin is not None: + tee.stdin.close() if res != 0 and have_long_running_queries: logging.info("Hung check failed with exit code %d", res) else: From 4067453602b6a2c93550ad11d4e1ddefa887b622 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Fri, 10 Feb 2023 11:57:02 +0100 Subject: [PATCH 053/445] Update run.sh --- docker/test/upgrade/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index d5b29485afe..bbf24e96685 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -467,7 +467,7 @@ else for table in query_log trace_log do clickhouse-local --path /var/lib/clickhouse/ --only-system-tables -q "select * from system.$table format TSVWithNamesAndTypes" \ - | zstd --threads=0 > /test_output/$table.backward.tsv.zst ||: + | zstd --threads=0 > /test_output/$table.tsv.zst ||: done fi From 808a939ad2ca2730e186eb48dbd6d130663de3ec Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 10 Feb 2023 14:25:19 +0100 Subject: [PATCH 054/445] Small rehacktoing --- src/Storages/MergeTree/MergeTreeData.cpp | 10 -- src/Storages/MergeTree/MutateTask.cpp | 115 ++++++++---------- .../ReplicatedMergeTreeAttachThread.cpp | 5 +- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 20 +-- src/Storages/MutationCommands.cpp | 16 +++ src/Storages/MutationCommands.h | 5 + src/Storages/StorageInMemoryMetadata.cpp | 7 ++ src/Storages/StorageInMemoryMetadata.h | 9 +- src/Storages/StorageMergeTree.cpp | 15 +-- src/Storages/StorageReplicatedMergeTree.cpp | 5 +- .../02555_davengers_rename_chain.sh | 16 +-- 11 files changed, 106 insertions(+), 117 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index f8f8ab74b31..4461a1470b4 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -7574,7 +7574,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa AlterConversions result{}; auto & rename_map = result.rename_map; - /// Squash "log of renames" into single map for (const auto & [version, commands] : commands_map) { for (const auto & command : commands) @@ -7589,15 +7588,6 @@ AlterConversions MergeTreeData::getAlterConversionsForPart(const MergeTreeDataPa } } - for (auto it = rename_map.begin(); it != rename_map.end();) - { - if (it->rename_to == it->rename_from) - it = rename_map.erase(it); - else - ++it; - - } - return result; } diff --git a/src/Storages/MergeTree/MutateTask.cpp b/src/Storages/MergeTree/MutateTask.cpp index 340709bff2c..01f7b47a170 100644 --- a/src/Storages/MergeTree/MutateTask.cpp +++ b/src/Storages/MergeTree/MutateTask.cpp @@ -52,7 +52,7 @@ static bool checkOperationIsNotCanceled(ActionBlocker & merges_blocker, MergeLis * First part should be executed by mutations interpreter. * Other is just simple drop/renames, so they can be executed without interpreter. */ -static void splitMutationCommands( +static void splitAndModifyMutationCommands( MergeTreeData::DataPartPtr part, const MutationCommands & commands, MutationCommands & for_interpreter, @@ -101,29 +101,37 @@ static void splitMutationCommands( } auto alter_conversions = part->storage.getAlterConversionsForPart(part); - /// If it's compact part, then we don't need to actually remove files - /// from disk we just don't read dropped columns + /// We don't add renames from commands, instead we take them from rename_map. + /// It's important because required renames depend not only on part's data version (i.e. mutation version) + /// but also on part's metadata version. Why we have such logic only for renames? Because all other types of alter + /// can be deduced based on difference between part's schema and table schema. for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { if (part_columns.has(rename_from)) { + /// Actual rename for_interpreter.push_back( { .type = MutationCommand::Type::READ_COLUMN, .column_name = rename_to, }); + /// Not needed for compact parts (not executed), added here only to produce correct + /// set of columns for new part and their serializations for_file_renames.push_back( { .type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to }); + part_columns.rename(rename_from, rename_to); } } + /// If it's compact part, then we don't need to actually remove files + /// from disk we just don't read dropped columns for (const auto & column : part_columns) { if (!mutated_columns.contains(column.name)) @@ -162,6 +170,11 @@ static void splitMutationCommands( } auto alter_conversions = part->storage.getAlterConversionsForPart(part); + /// We don't add renames from commands, instead we take them from rename_map. + /// It's important because required renames depend not only on part's data version (i.e. mutation version) + /// but also on part's metadata version. Why we have such logic only for renames? Because all other types of alter + /// can be deduced based on difference between part's schema and table schema. + for (const auto & [rename_to, rename_from] : alter_conversions.rename_map) { for_file_renames.push_back({.type = MutationCommand::Type::RENAME_COLUMN, .column_name = rename_from, .rename_to = rename_to}); @@ -169,6 +182,41 @@ static void splitMutationCommands( } } +/// It's legal to squash renames because commands with rename are always "barrier" +/// and executed separately from other types of commands. +static MutationCommands squashRenamesInCommands(const MutationCommands & commands) +{ + NameToNameMap squashed_renames; + for (const auto & command : commands) + { + std::string result_name = command.rename_to; + + bool squashed = false; + for (const auto & [name_from, name_to] : squashed_renames) + { + if (name_to == command.column_name) + { + squashed = true; + squashed_renames[name_from] = result_name; + break; + } + } + if (!squashed) + squashed_renames[command.column_name] = result_name; + } + + MutationCommands squashed_commands; + for (const auto & command : commands) + { + if (squashed_renames.contains(command.column_name)) + { + squashed_commands.push_back(command); + squashed_commands.back().rename_to = squashed_renames[command.column_name]; + } + } + return squashed_commands; +} + /// Get the columns list of the resulting part in the same order as storage_columns. static std::pair getColumnsForNewDataPart( @@ -198,35 +246,7 @@ getColumnsForNewDataPart( storage_columns.emplace_back(column); } - NameToNameMap squashed_renames; - for (const auto & command : all_commands) - { - std::string result_name = command.rename_to; - - bool squashed = false; - for (const auto & [name_from, name_to] : squashed_renames) - { - if (name_to == command.column_name) - { - squashed = true; - squashed_renames[name_from] = result_name; - break; - } - } - if (!squashed) - squashed_renames[command.column_name] = result_name; - } - - MutationCommands squashed_commands; - for (const auto & command : all_commands) - { - if (squashed_renames.contains(command.column_name)) - { - squashed_commands.push_back(command); - squashed_commands.back().rename_to = squashed_renames[command.column_name]; - } - } - + MutationCommands squashed_commands = squashRenamesInCommands(all_commands); for (const auto & command : squashed_commands) { @@ -618,34 +638,7 @@ static NameToNameVector collectFilesForRenames( rename_vector.emplace_back(file_rename_from, file_rename_to); }; - NameToNameMap squashed_renames; - for (const auto & command : commands_for_removes) - { - std::string result_name = command.rename_to; - - bool squashed = false; - for (const auto & [name_from, name_to] : squashed_renames) - { - if (name_to == command.column_name) - { - squashed = true; - squashed_renames[name_from] = result_name; - break; - } - } - if (!squashed) - squashed_renames[command.column_name] = result_name; - } - - MutationCommands squashed_commands; - for (const auto & command : commands_for_removes) - { - if (squashed_renames.contains(command.column_name)) - { - squashed_commands.push_back(command); - squashed_commands.back().rename_to = squashed_renames[command.column_name]; - } - } + MutationCommands squashed_commands = squashRenamesInCommands(commands_for_removes); /// Remove old data for (const auto & command : squashed_commands) @@ -1724,7 +1717,7 @@ bool MutateTask::prepare() context_for_reading->setSetting("allow_asynchronous_read_from_io_pool_for_merge_tree", false); context_for_reading->setSetting("max_streams_for_merge_tree_reading", Field(0)); - MutationHelpers::splitMutationCommands(ctx->source_part, ctx->commands_for_part, ctx->for_interpreter, ctx->for_file_renames); + MutationHelpers::splitAndModifyMutationCommands(ctx->source_part, ctx->commands_for_part, ctx->for_interpreter, ctx->for_file_renames); ctx->stage_progress = std::make_unique(1.0); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp index 941d0850f67..f1bdc9f43af 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeAttachThread.cpp @@ -149,10 +149,7 @@ void ReplicatedMergeTreeAttachThread::runImpl() const bool replica_metadata_version_exists = zookeeper->tryGet(replica_path + "/metadata_version", replica_metadata_version); if (replica_metadata_version_exists) { - StorageInMemoryMetadata metadata_with_new_version(*metadata_snapshot); - - metadata_with_new_version.setMetadataVersion(parse(replica_metadata_version)); - storage.setInMemoryMetadata(metadata_with_new_version); + storage.setInMemoryMetadata(metadata_snapshot->withMetadataVersion(parse(replica_metadata_version))); } else { diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index f491cdc8cd1..e3e558db77b 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1831,28 +1831,16 @@ MutationCommands ReplicatedMergeTreeQueue::getMutationCommands( MutationCommands commands; for (auto it = begin; it != end; ++it) { - bool rename_command = false; - if (it->second->entry->isAlterMutation()) - { - const auto & single_mutation_commands = it->second->entry->commands; - for (const auto & command : single_mutation_commands) - { - if (command.type == MutationCommand::Type::RENAME_COLUMN) - { - rename_command = true; - break; - } - } - } + const auto & commands_from_entry = it->second->entry->commands; - if (rename_command) + if (commands_from_entry.containBarrierCommand()) { if (commands.empty()) - commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + commands.insert(commands.end(), commands_from_entry.begin(), commands_from_entry.end()); break; } else - commands.insert(commands.end(), it->second->entry->commands.begin(), it->second->entry->commands.end()); + commands.insert(commands.end(), commands_from_entry.begin(), commands_from_entry.end()); } return commands; diff --git a/src/Storages/MutationCommands.cpp b/src/Storages/MutationCommands.cpp index 0c9e9223929..aa77988348d 100644 --- a/src/Storages/MutationCommands.cpp +++ b/src/Storages/MutationCommands.cpp @@ -23,6 +23,12 @@ namespace ErrorCodes extern const int MULTIPLE_ASSIGNMENTS_TO_COLUMN; } + +bool MutationCommand::isBarrierCommand() const +{ + return type == RENAME_COLUMN; +} + std::optional MutationCommand::parse(ASTAlterCommand * command, bool parse_alter_commands) { if (command->type == ASTAlterCommand::DELETE) @@ -212,4 +218,14 @@ bool MutationCommands::hasNonEmptyMutationCommands() const return false; } +bool MutationCommands::containBarrierCommand() const +{ + for (const auto & command : *this) + { + if (command.isBarrierCommand()) + return true; + } + return false; +} + } diff --git a/src/Storages/MutationCommands.h b/src/Storages/MutationCommands.h index aca91c16e85..079b456fa3b 100644 --- a/src/Storages/MutationCommands.h +++ b/src/Storages/MutationCommands.h @@ -67,6 +67,9 @@ struct MutationCommand /// If parse_alter_commands, than consider more Alter commands as mutation commands static std::optional parse(ASTAlterCommand * command, bool parse_alter_commands = false); + + /// This command shouldn't stick with other commands + bool isBarrierCommand() const; }; /// Multiple mutation commands, possible from different ALTER queries @@ -79,6 +82,8 @@ public: void readText(ReadBuffer & in); std::string toString() const; bool hasNonEmptyMutationCommands() const; + + bool containBarrierCommand() const; }; using MutationCommandsConstPtr = std::shared_ptr; diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index 5250c6f5330..45abd4bebef 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -129,6 +129,13 @@ void StorageInMemoryMetadata::setMetadataVersion(int32_t metadata_version_) metadata_version = metadata_version_; } +StorageInMemoryMetadata StorageInMemoryMetadata::withMetadataVersion(int32_t metadata_version_) const +{ + StorageInMemoryMetadata copy(*this); + copy.setMetadataVersion(metadata_version_); + return copy; +} + const ColumnsDescription & StorageInMemoryMetadata::getColumns() const { return columns; diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index dfb5e9ddb54..25618c5b03f 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -50,6 +50,8 @@ struct StorageInMemoryMetadata String comment; + /// Version of metadata. Managed properly by ReplicatedMergeTree only + /// (zero-initialization is important) int32_t metadata_version = 0; StorageInMemoryMetadata() = default; @@ -60,7 +62,7 @@ struct StorageInMemoryMetadata StorageInMemoryMetadata(StorageInMemoryMetadata && other) = default; StorageInMemoryMetadata & operator=(StorageInMemoryMetadata && other) = default; - /// NOTE: Thread unsafe part. You should modify same StorageInMemoryMetadata + /// NOTE: Thread unsafe part. You should not modify same StorageInMemoryMetadata /// structure from different threads. It should be used as MultiVersion /// object. See example in IStorage. @@ -92,7 +94,10 @@ struct StorageInMemoryMetadata /// Set SELECT query for (Materialized)View void setSelectQuery(const SelectQueryDescription & select_); + /// Set version of metadata. void setMetadataVersion(int32_t metadata_version_); + /// Get copy of current metadata with metadata_version_ + StorageInMemoryMetadata withMetadataVersion(int32_t metadata_version_) const; /// Returns combined set of columns const ColumnsDescription & getColumns() const; @@ -222,8 +227,8 @@ struct StorageInMemoryMetadata const SelectQueryDescription & getSelectQuery() const; bool hasSelectQuery() const; + /// Get version of metadata int32_t getMetadataVersion() const { return metadata_version; } - bool hasMetadataVersion() const { return metadata_version != -1; } /// Check that all the requested names are in the table and have the correct types. void check(const NamesAndTypesList & columns) const; diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 78b29c943dc..3ee56a2e62d 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1141,22 +1141,13 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( if (current_ast_elements + commands_size >= max_ast_elements) break; - bool rename_command = false; const auto & single_mutation_commands = it->second.commands; - for (const auto & command : single_mutation_commands) - { - if (command.type == MutationCommand::Type::RENAME_COLUMN) - { - rename_command = true; - break; - } - } - if (rename_command) + if (single_mutation_commands.containBarrierCommand()) { if (commands->empty()) { - commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + commands->insert(commands->end(), single_mutation_commands.begin(), single_mutation_commands.end()); last_mutation_to_apply = it; } break; @@ -1164,7 +1155,7 @@ MergeMutateSelectedEntryPtr StorageMergeTree::selectPartsToMutate( else { current_ast_elements += commands_size; - commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + commands->insert(commands->end(), single_mutation_commands.begin(), single_mutation_commands.end()); last_mutation_to_apply = it; } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 8e6e809a54a..806e535b711 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -455,10 +455,7 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( Coordination::Stat metadata_stat; current_zookeeper->get(zookeeper_path + "/metadata", &metadata_stat); - - StorageInMemoryMetadata storage_metadata(*metadata_snapshot); - storage_metadata.setMetadataVersion(metadata_stat.version); - setInMemoryMetadata(storage_metadata); + setInMemoryMetadata(metadata_snapshot->withMetadataVersion(metadata_stat.version)); } catch (Coordination::Exception & e) { diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sh b/tests/queries/0_stateless/02555_davengers_rename_chain.sh index 71201537170..f4af5e091ad 100755 --- a/tests/queries/0_stateless/02555_davengers_rename_chain.sh +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sh @@ -27,8 +27,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") - if [[ $result == *"a1 UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"a TO a1"* ]]; then break; fi sleep 0.1 @@ -53,8 +53,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata") - if [[ $result == *"b UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"b1 TO a"* ]]; then break; fi sleep 0.1 @@ -98,8 +98,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") - if [[ $result == *"b1 UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata_compact' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"a TO a1"* ]]; then break; fi sleep 0.1 @@ -120,8 +120,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "show create table wrong_metadata_compact") - if [[ $result == *"b UInt64"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata_compact' AND database='${CLICKHOUSE_DATABASE}'") + if [[ $result == *"b1 TO a"* ]]; then break; fi sleep 0.1 From 99dc7fbde8ec125b85176c481396a2ca916854d2 Mon Sep 17 00:00:00 2001 From: alesapin Date: Fri, 10 Feb 2023 14:27:59 +0100 Subject: [PATCH 055/445] Update src/Storages/MutationCommands.h --- src/Storages/MutationCommands.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Storages/MutationCommands.h b/src/Storages/MutationCommands.h index 079b456fa3b..5ef0cfda1be 100644 --- a/src/Storages/MutationCommands.h +++ b/src/Storages/MutationCommands.h @@ -83,6 +83,9 @@ public: std::string toString() const; bool hasNonEmptyMutationCommands() const; + /// These set of commands contain barrier command and shouldn't + /// stick with other commands. Commands from one set have already been validated + /// to be executed without issues on the creation state. bool containBarrierCommand() const; }; From d1efd024809cea35b2bb099e32208ad88603c37a Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 10 Feb 2023 16:40:14 +0000 Subject: [PATCH 056/445] Extend setting input_format_null_as_default for more formats --- src/Columns/ColumnLowCardinality.cpp | 28 ++++++++ src/Columns/ColumnLowCardinality.h | 2 + src/Columns/ColumnNullable.cpp | 22 ++++++ src/Columns/ColumnNullable.h | 2 + src/Core/Settings.h | 3 +- src/DataTypes/IDataType.h | 5 ++ src/Formats/FormatFactory.cpp | 1 - src/Formats/FormatSettings.h | 1 - src/Formats/NativeReader.cpp | 31 +++++++-- src/Formats/NativeReader.h | 10 ++- src/Formats/insertNullAsDefaultIfNeeded.cpp | 37 ++++++++++ src/Formats/insertNullAsDefaultIfNeeded.h | 10 +++ .../Formats/Impl/ArrowBlockInputFormat.cpp | 9 +-- .../Formats/Impl/ArrowBlockInputFormat.h | 1 - .../Formats/Impl/ArrowColumnToCHColumn.cpp | 39 ++++------- .../Formats/Impl/ArrowColumnToCHColumn.h | 9 ++- .../Formats/Impl/AvroRowInputFormat.cpp | 69 +++++++++++++++---- .../Formats/Impl/AvroRowInputFormat.h | 7 +- .../Formats/Impl/MsgPackRowInputFormat.cpp | 44 +++++++----- .../Formats/Impl/MsgPackRowInputFormat.h | 10 ++- src/Processors/Formats/Impl/NativeFormat.cpp | 12 +++- .../Formats/Impl/ORCBlockInputFormat.cpp | 8 +-- .../Formats/Impl/ORCBlockInputFormat.h | 1 - .../Formats/Impl/ParquetBlockInputFormat.cpp | 13 ++-- .../Formats/Impl/ParquetBlockInputFormat.h | 1 - ...561_null_as_default_more_formats.reference | 36 ++++++++++ .../02561_null_as_default_more_formats.sh | 21 ++++++ ...2_native_null_on_missing_columns.reference | 4 ++ .../02562_native_null_on_missing_columns.sh | 16 +++++ 29 files changed, 352 insertions(+), 100 deletions(-) create mode 100644 src/Formats/insertNullAsDefaultIfNeeded.cpp create mode 100644 src/Formats/insertNullAsDefaultIfNeeded.h create mode 100644 tests/queries/0_stateless/02561_null_as_default_more_formats.reference create mode 100755 tests/queries/0_stateless/02561_null_as_default_more_formats.sh create mode 100644 tests/queries/0_stateless/02562_native_null_on_missing_columns.reference create mode 100755 tests/queries/0_stateless/02562_native_null_on_missing_columns.sh diff --git a/src/Columns/ColumnLowCardinality.cpp b/src/Columns/ColumnLowCardinality.cpp index ecdaf240e5e..109bf201836 100644 --- a/src/Columns/ColumnLowCardinality.cpp +++ b/src/Columns/ColumnLowCardinality.cpp @@ -830,4 +830,32 @@ void ColumnLowCardinality::Dictionary::compact(ColumnPtr & positions) shared = false; } +ColumnPtr ColumnLowCardinality::cloneWithDefaultOnNull() const +{ + if (!nestedIsNullable()) + return getPtr(); + + auto res = cloneEmpty(); + auto & lc_res = assert_cast(*res); + lc_res.nestedRemoveNullable(); + size_t end = size(); + size_t start = 0; + while (start < end) + { + size_t next_null_index = start; + while (next_null_index < end && !isNullAt(next_null_index)) + ++next_null_index; + + if (next_null_index != start) + lc_res.insertRangeFrom(*this, start, next_null_index - start); + + if (next_null_index < end) + lc_res.insertDefault(); + + start = next_null_index + 1; + } + + return res; +} + } diff --git a/src/Columns/ColumnLowCardinality.h b/src/Columns/ColumnLowCardinality.h index e895bc6b54e..3d42f82a867 100644 --- a/src/Columns/ColumnLowCardinality.h +++ b/src/Columns/ColumnLowCardinality.h @@ -220,6 +220,8 @@ public: void nestedToNullable() { dictionary.getColumnUnique().nestedToNullable(); } void nestedRemoveNullable() { dictionary.getColumnUnique().nestedRemoveNullable(); } + ColumnPtr cloneWithDefaultOnNull() const; + const IColumnUnique & getDictionary() const { return dictionary.getColumnUnique(); } IColumnUnique & getDictionary() { return dictionary.getColumnUnique(); } const ColumnPtr & getDictionaryPtr() const { return dictionary.getColumnUniquePtr(); } diff --git a/src/Columns/ColumnNullable.cpp b/src/Columns/ColumnNullable.cpp index 9398c66bef0..99d377f10eb 100644 --- a/src/Columns/ColumnNullable.cpp +++ b/src/Columns/ColumnNullable.cpp @@ -781,6 +781,28 @@ ColumnPtr ColumnNullable::createWithOffsets(const IColumn::Offsets & offsets, co return ColumnNullable::create(new_values, new_null_map); } +ColumnPtr ColumnNullable::getNestedColumnWithDefaultOnNull() const +{ + auto res = nested_column->cloneEmpty(); + const auto & null_map_data = getNullMapData(); + size_t start = 0; + while (start < nested_column->size()) + { + size_t next_null_index = start; + while (next_null_index < null_map->size() && !null_map_data[next_null_index]) + ++next_null_index; + + if (next_null_index != start) + res->insertRangeFrom(*nested_column, start, next_null_index - start); + + if (next_null_index < null_map->size()) + res->insertDefault(); + + start = next_null_index + 1; + } + return res; +} + ColumnPtr makeNullable(const ColumnPtr & column) { if (isColumnNullable(*column)) diff --git a/src/Columns/ColumnNullable.h b/src/Columns/ColumnNullable.h index 85bf095a9d1..1ec037092b5 100644 --- a/src/Columns/ColumnNullable.h +++ b/src/Columns/ColumnNullable.h @@ -188,6 +188,8 @@ public: NullMap & getNullMapData() { return getNullMapColumn().getData(); } const NullMap & getNullMapData() const { return getNullMapColumn().getData(); } + ColumnPtr getNestedColumnWithDefaultOnNull() const; + /// Apply the null byte map of a specified nullable column onto the /// null byte map of the current column by performing an element-wise OR /// between both byte maps. This method is used to determine the null byte diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 6e085fd27ac..481929d915f 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -751,7 +751,7 @@ class IColumn; M(Bool, input_format_csv_empty_as_default, true, "Treat empty fields in CSV input as default values.", 0) \ M(Bool, input_format_tsv_empty_as_default, false, "Treat empty fields in TSV input as default values.", 0) \ M(Bool, input_format_tsv_enum_as_number, false, "Treat inserted enum values in TSV formats as enum indices.", 0) \ - M(Bool, input_format_null_as_default, true, "For text input formats initialize null fields with default values if data type of this field is not nullable", 0) \ + M(Bool, input_format_null_as_default, true, "For most input formats initialize null fields with default values if data type of this field is not nullable", 0) \ M(Bool, input_format_arrow_import_nested, false, "Allow to insert array of structs into Nested table in Arrow input format.", 0) \ M(Bool, input_format_arrow_case_insensitive_column_matching, false, "Ignore case when matching Arrow columns with CH columns.", 0) \ M(Bool, input_format_orc_import_nested, false, "Allow to insert array of structs into Nested table in ORC input format.", 0) \ @@ -811,6 +811,7 @@ class IColumn; M(Bool, input_format_values_deduce_templates_of_expressions, true, "For Values format: if the field could not be parsed by streaming parser, run SQL parser, deduce template of the SQL expression, try to parse all rows using template and then interpret expression for all rows.", 0) \ M(Bool, input_format_values_accurate_types_of_literals, true, "For Values format: when parsing and interpreting expressions using template, check actual type of literal to avoid possible overflow and precision issues.", 0) \ M(Bool, input_format_avro_allow_missing_fields, false, "For Avro/AvroConfluent format: when field is not found in schema use default value instead of error", 0) \ + /** This setting is obsolete and do nothing, left for compatibility reasons. */ \ M(Bool, input_format_avro_null_as_default, false, "For Avro/AvroConfluent format: insert default in case of null and non Nullable column", 0) \ M(UInt64, format_binary_max_string_size, 1_GiB, "The maximum allowed size for String in RowBinary format. It prevents allocating large amount of memory in case of corrupted data. 0 means there is no limit", 0) \ M(URI, format_avro_schema_registry_url, "", "For AvroConfluent format: Confluent Schema Registry URL.", 0) \ diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index bafe03dbc3a..f2230b70cab 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -548,6 +548,11 @@ inline bool isAggregateFunction(const DataTypePtr & data_type) return which.isAggregateFunction(); } +inline bool isNullableOrLowCardinalityNullable(const DataTypePtr & data_type) +{ + return data_type->isNullable() || data_type->isLowCardinalityNullable(); +} + template constexpr bool IsDataTypeDecimal = false; template constexpr bool IsDataTypeNumber = false; template constexpr bool IsDataTypeDateOrDateTime = false; diff --git a/src/Formats/FormatFactory.cpp b/src/Formats/FormatFactory.cpp index 3fcecd23f5b..8d8ffebe270 100644 --- a/src/Formats/FormatFactory.cpp +++ b/src/Formats/FormatFactory.cpp @@ -56,7 +56,6 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings) format_settings.avro.schema_registry_url = settings.format_avro_schema_registry_url.toString(); format_settings.avro.string_column_pattern = settings.output_format_avro_string_column_pattern.toString(); format_settings.avro.output_rows_in_file = settings.output_format_avro_rows_in_file; - format_settings.avro.null_as_default = settings.input_format_avro_null_as_default; format_settings.csv.allow_double_quotes = settings.format_csv_allow_double_quotes; format_settings.csv.allow_single_quotes = settings.format_csv_allow_single_quotes; format_settings.csv.crlf_end_of_line = settings.output_format_csv_crlf_end_of_line; diff --git a/src/Formats/FormatSettings.h b/src/Formats/FormatSettings.h index 92e499abb10..2bf8e136c63 100644 --- a/src/Formats/FormatSettings.h +++ b/src/Formats/FormatSettings.h @@ -104,7 +104,6 @@ struct FormatSettings bool allow_missing_fields = false; String string_column_pattern; UInt64 output_rows_in_file = 1; - bool null_as_default = false; } avro; String bool_true_representation = "true"; diff --git a/src/Formats/NativeReader.cpp b/src/Formats/NativeReader.cpp index 58baee5931b..9f8d4ba1930 100644 --- a/src/Formats/NativeReader.cpp +++ b/src/Formats/NativeReader.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -32,8 +33,19 @@ NativeReader::NativeReader(ReadBuffer & istr_, UInt64 server_revision_) { } -NativeReader::NativeReader(ReadBuffer & istr_, const Block & header_, UInt64 server_revision_, bool skip_unknown_columns_) - : istr(istr_), header(header_), server_revision(server_revision_), skip_unknown_columns(skip_unknown_columns_) +NativeReader::NativeReader( + ReadBuffer & istr_, + const Block & header_, + UInt64 server_revision_, + bool skip_unknown_columns_, + bool null_as_default_, + BlockMissingValues * block_missing_values_) + : istr(istr_) + , header(header_) + , server_revision(server_revision_) + , skip_unknown_columns(skip_unknown_columns_) + , null_as_default(null_as_default_) + , block_missing_values(block_missing_values_) { } @@ -187,8 +199,12 @@ Block NativeReader::read() { if (header.has(column.name)) { - /// Support insert from old clients without low cardinality type. auto & header_column = header.getByName(column.name); + + if (null_as_default) + insertNullAsDefaultIfNeeded(column, header_column, header.getPositionByName(column.name), block_missing_values); + + /// Support insert from old clients without low cardinality type. if (!header_column.type->equals(*column.type)) { column.column = recursiveTypeConversion(column.column, column.type, header.safeGetByPosition(i).type); @@ -225,12 +241,19 @@ Block NativeReader::read() /// Allow to skip columns. Fill them with default values. Block tmp_res; - for (auto & col : header) + for (size_t column_i = 0; column_i != header.columns(); ++column_i) { + auto & col = header.getByPosition(column_i); if (res.has(col.name)) + { tmp_res.insert(res.getByName(col.name)); + } else + { tmp_res.insert({col.type->createColumn()->cloneResized(rows), col.type, col.name}); + if (block_missing_values) + block_missing_values->setBits(column_i, rows); + } } tmp_res.info = res.info; diff --git a/src/Formats/NativeReader.h b/src/Formats/NativeReader.h index 3ae53d45faf..64d3e4d6df0 100644 --- a/src/Formats/NativeReader.h +++ b/src/Formats/NativeReader.h @@ -24,7 +24,13 @@ public: /// For cases when data structure (header) is known in advance. /// NOTE We may use header for data validation and/or type conversions. It is not implemented. - NativeReader(ReadBuffer & istr_, const Block & header_, UInt64 server_revision_, bool skip_unknown_columns_ = false); + NativeReader( + ReadBuffer & istr_, + const Block & header_, + UInt64 server_revision_, + bool skip_unknown_columns_ = false, + bool null_as_default_ = false, + BlockMissingValues * block_missing_values_ = nullptr); /// For cases when we have an index. It allows to skip columns. Only columns specified in the index will be read. NativeReader(ReadBuffer & istr_, UInt64 server_revision_, @@ -44,6 +50,8 @@ private: Block header; UInt64 server_revision; bool skip_unknown_columns; + bool null_as_default; + BlockMissingValues * block_missing_values; bool use_index = false; IndexForNativeFormat::Blocks::const_iterator index_block_it; diff --git a/src/Formats/insertNullAsDefaultIfNeeded.cpp b/src/Formats/insertNullAsDefaultIfNeeded.cpp new file mode 100644 index 00000000000..767892718c5 --- /dev/null +++ b/src/Formats/insertNullAsDefaultIfNeeded.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +namespace DB +{ + +void insertNullAsDefaultIfNeeded(ColumnWithTypeAndName & input_column, const ColumnWithTypeAndName & header_column, size_t column_i, BlockMissingValues * block_missing_values) +{ + if (!isNullableOrLowCardinalityNullable(input_column.type) || isNullableOrLowCardinalityNullable(header_column.type)) + return; + + if (block_missing_values) + { + for (size_t i = 0; i < input_column.column->size(); ++i) + { + if (input_column.column->isNullAt(i)) + block_missing_values->setBit(column_i, i); + } + } + + if (input_column.type->isNullable()) + { + input_column.column = assert_cast(input_column.column.get())->getNestedColumnWithDefaultOnNull(); + input_column.type = removeNullable(input_column.type); + } + else + { + input_column.column = assert_cast(input_column.column.get())->cloneWithDefaultOnNull(); + const auto * lc_type = assert_cast(input_column.type.get()); + input_column.type = std::make_shared(removeNullable(lc_type->getDictionaryType())); + } +} + +} diff --git a/src/Formats/insertNullAsDefaultIfNeeded.h b/src/Formats/insertNullAsDefaultIfNeeded.h new file mode 100644 index 00000000000..3e4dcd1e74a --- /dev/null +++ b/src/Formats/insertNullAsDefaultIfNeeded.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace DB +{ + +void insertNullAsDefaultIfNeeded(ColumnWithTypeAndName & input_column, const ColumnWithTypeAndName & header_column, size_t column_i, BlockMissingValues * block_missing_values); + +} diff --git a/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp b/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp index ed963d8a500..cd8facb83eb 100644 --- a/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ArrowBlockInputFormat.cpp @@ -71,13 +71,10 @@ Chunk ArrowBlockInputFormat::generate() ++record_batch_current; - arrow_column_to_ch_column->arrowTableToCHChunk(res, *table_result, (*table_result)->num_rows()); - /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. /// Otherwise fill the missing columns with zero values of its type. - if (format_settings.defaults_for_omitted_fields) - for (const auto & column_idx : missing_columns) - block_missing_values.setBits(column_idx, res.getNumRows()); + BlockMissingValues * block_missing_values_ptr = format_settings.defaults_for_omitted_fields ? &block_missing_values : nullptr; + arrow_column_to_ch_column->arrowTableToCHChunk(res, *table_result, (*table_result)->num_rows(), block_missing_values_ptr); return res; } @@ -143,8 +140,8 @@ void ArrowBlockInputFormat::prepareReader() "Arrow", format_settings.arrow.import_nested, format_settings.arrow.allow_missing_columns, + format_settings.null_as_default, format_settings.arrow.case_insensitive_column_matching); - missing_columns = arrow_column_to_ch_column->getMissingColumns(*schema); if (stream) record_batch_total = -1; diff --git a/src/Processors/Formats/Impl/ArrowBlockInputFormat.h b/src/Processors/Formats/Impl/ArrowBlockInputFormat.h index 02648d28048..3db76777891 100644 --- a/src/Processors/Formats/Impl/ArrowBlockInputFormat.h +++ b/src/Processors/Formats/Impl/ArrowBlockInputFormat.h @@ -47,7 +47,6 @@ private: int record_batch_total = 0; int record_batch_current = 0; - std::vector missing_columns; BlockMissingValues block_missing_values; const FormatSettings format_settings; diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp index 68c40527097..80172ca9c05 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -384,9 +385,10 @@ static ColumnWithTypeAndName readColumnWithIndexesDataImpl(std::shared_ptr(buffer->data()); /// Check that indexes are correct (protection against corrupted files) + /// Note that on null values index can be arbitrary value. for (int64_t i = 0; i != chunk->length(); ++i) { - if (data[i] < 0 || data[i] >= dict_size) + if (!chunk->IsNull(i) && (data[i] < 0 || data[i] >= dict_size)) throw Exception(ErrorCodes::INCORRECT_DATA, "Index {} in Dictionary column is out of bounds, dictionary size is {}", Int64(data[i]), UInt64(dict_size)); @@ -805,16 +807,18 @@ ArrowColumnToCHColumn::ArrowColumnToCHColumn( const std::string & format_name_, bool import_nested_, bool allow_missing_columns_, + bool null_as_default_, bool case_insensitive_matching_) : header(header_) , format_name(format_name_) , import_nested(import_nested_) , allow_missing_columns(allow_missing_columns_) + , null_as_default(null_as_default_) , case_insensitive_matching(case_insensitive_matching_) { } -void ArrowColumnToCHColumn::arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows) +void ArrowColumnToCHColumn::arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows, BlockMissingValues * block_missing_values) { NameToColumnPtr name_to_column_ptr; for (auto column_name : table->ColumnNames()) @@ -828,10 +832,10 @@ void ArrowColumnToCHColumn::arrowTableToCHChunk(Chunk & res, std::shared_ptrcloneResized(num_rows); columns_list.push_back(std::move(column.column)); + if (block_missing_values) + block_missing_values->setBits(column_i, num_rows); continue; } } @@ -906,6 +912,9 @@ void ArrowColumnToCHColumn::arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & arrow_column, header_column.name, format_name, false, dictionary_infos, true, false, skipped, header_column.type); } + if (null_as_default) + insertNullAsDefaultIfNeeded(column, header_column, column_i, block_missing_values); + try { column.column = castColumn(column, header_column.type); @@ -927,28 +936,6 @@ void ArrowColumnToCHColumn::arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & res.setColumns(columns_list, num_rows); } -std::vector ArrowColumnToCHColumn::getMissingColumns(const arrow::Schema & schema) const -{ - std::vector missing_columns; - auto block_from_arrow = arrowSchemaToCHHeader(schema, format_name, false, &header, case_insensitive_matching); - NestedColumnExtractHelper nested_columns_extractor(block_from_arrow, case_insensitive_matching); - - for (size_t i = 0, columns = header.columns(); i < columns; ++i) - { - const auto & header_column = header.getByPosition(i); - if (!block_from_arrow.has(header_column.name, case_insensitive_matching)) - { - if (!import_nested || !nested_columns_extractor.extractColumn(header_column.name)) - { - if (!allow_missing_columns) - throw Exception{ErrorCodes::THERE_IS_NO_COLUMN, "Column '{}' is not presented in input data.", header_column.name}; - missing_columns.push_back(i); - } - } - } - return missing_columns; -} - } #endif diff --git a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h index dd9f44eb94e..64ff99c70ac 100644 --- a/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h +++ b/src/Processors/Formats/Impl/ArrowColumnToCHColumn.h @@ -26,14 +26,12 @@ public: const std::string & format_name_, bool import_nested_, bool allow_missing_columns_, + bool null_as_default_, bool case_insensitive_matching_ = false); - void arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows); + void arrowTableToCHChunk(Chunk & res, std::shared_ptr & table, size_t num_rows, BlockMissingValues * block_missing_values = nullptr); - void arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & name_to_column_ptr, size_t num_rows); - - /// Get missing columns that exists in header but not in arrow::Schema - std::vector getMissingColumns(const arrow::Schema & schema) const; + void arrowColumnsToCHChunk(Chunk & res, NameToColumnPtr & name_to_column_ptr, size_t num_rows, BlockMissingValues * block_missing_values = nullptr); /// Transform arrow schema to ClickHouse header. If hint_header is provided, /// we will skip columns in schema that are not in hint_header. @@ -58,6 +56,7 @@ private: bool import_nested; /// If false, throw exception if some columns in header not exists in arrow table. bool allow_missing_columns; + bool null_as_default; bool case_insensitive_matching; /// Map {column name : dictionary column}. diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp index 9a475efa195..e77f4132100 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp @@ -176,8 +176,9 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { auto & lc_column = assert_cast(column); auto tmp_column = lc_column.getDictionary().getNestedColumn()->cloneEmpty(); - dict_deserialize(*tmp_column, decoder); + auto res = dict_deserialize(*tmp_column, decoder); lc_column.insertFromFullColumn(*tmp_column, 0); + return res; }; } @@ -198,6 +199,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node UUID uuid; parseUUID(reinterpret_cast(tmp.data()), std::reverse_iterator(reinterpret_cast(&uuid) + 16)); assert_cast(column).insertValue(uuid); + return true; }; } if (target.isString() || target.isFixedString()) @@ -206,6 +208,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { decoder.decodeString(tmp); column.insertData(tmp.c_str(), tmp.length()); + return true; }; } break; @@ -215,6 +218,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeInt()); + return true; }; } break; @@ -224,6 +228,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeLong()); + return true; }; } break; @@ -233,6 +238,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeFloat()); + return true; }; } break; @@ -242,6 +248,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeDouble()); + return true; }; } break; @@ -251,6 +258,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [target](IColumn & column, avro::Decoder & decoder) { insertNumber(column, target, decoder.decodeBool()); + return true; }; } break; @@ -275,6 +283,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node } } offsets.push_back(offsets.back() + total); + return true; }; } break; @@ -301,24 +310,33 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { col.insertDefault(); } + return true; }; } - - /// If the Union is ['Null', Nested-Type], since the Nested-Type can not be inside - /// Nullable, so we will get Nested-Type, instead of Nullable type. - if (null_as_default || !target.isNullable()) + else if (null_as_default) { auto nested_deserialize = this->createDeserializeFn(root_node->leafAt(non_null_union_index), target_type); return [non_null_union_index, nested_deserialize](IColumn & column, avro::Decoder & decoder) { int union_index = static_cast(decoder.decodeUnionIndex()); if (union_index == non_null_union_index) + { nested_deserialize(column, decoder); - else - column.insertDefault(); + return true; + } + column.insertDefault(); + return false; }; } - + else + { + throw Exception( + ErrorCodes::BAD_ARGUMENTS, + "Cannot insert Avro Union(Null, {}) into non-nullable type {}. To use default value on NULL, enable setting " + "input_format_null_as_default", + avro::toString(root_node->leafAt(non_null_union_index)->type()), + target_type->getName()); + } } break; } @@ -331,6 +349,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node return [](IColumn &, avro::Decoder & decoder) { decoder.decodeNull(); + return true; }; } else @@ -340,10 +359,26 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node ColumnNullable & col = assert_cast(column); decoder.decodeNull(); col.insertDefault(); + return true; }; } } - break; + else if (null_as_default) + { + return [](IColumn & column, avro::Decoder & decoder) + { + decoder.decodeNull(); + column.insertDefault(); + return false; + }; + } + else + { + throw Exception( + ErrorCodes::BAD_ARGUMENTS, + "Cannot insert Avro Null into non-nullable type {}. To use default value on NULL, enable setting " + "input_format_null_as_default", target_type->getName()); + } case avro::AVRO_ENUM: if (target.isString()) { @@ -358,6 +393,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node size_t enum_index = decoder.decodeEnum(); const auto & enum_symbol = symbols[enum_index]; column.insertData(enum_symbol.c_str(), enum_symbol.length()); + return true; }; } if (target.isEnum()) @@ -372,6 +408,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { size_t enum_index = decoder.decodeEnum(); column.insert(symbol_mapping[enum_index]); + return true; }; } break; @@ -384,6 +421,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node { decoder.decodeFixed(tmp_fixed.size(), tmp_fixed); column.insertData(reinterpret_cast(tmp_fixed.data()), tmp_fixed.size()); + return true; }; } break; @@ -415,6 +453,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node auto nested_columns = column_tuple.getColumns(); for (const auto & [nested_deserializer, pos] : nested_deserializers) nested_deserializer(*nested_columns[pos], decoder); + return true; }; } break; @@ -449,6 +488,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node } } offsets.push_back(offsets.back() + total); + return true; }; } break; @@ -465,6 +505,7 @@ AvroDeserializer::DeserializeFn AvroDeserializer::createDeserializeFn(avro::Node ColumnNullable & col = assert_cast(column); nested_deserialize(col.getNestedColumn(), decoder); col.getNullMapData().push_back(0); + return true; }; } @@ -593,7 +634,6 @@ void AvroDeserializer::Action::deserializeNested(MutableColumns & columns, avro: ColumnArray & column_array = assert_cast(*columns[index]); arrays_offsets.push_back(&column_array.getOffsets()); nested_columns.push_back(&column_array.getData()); - ext.read_columns[index] = true; } size_t total = 0; @@ -603,7 +643,7 @@ void AvroDeserializer::Action::deserializeNested(MutableColumns & columns, avro: for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j != nested_deserializers.size(); ++j) - nested_deserializers[j](*nested_columns[j], decoder); + ext.read_columns[nested_column_indexes[j]] = nested_deserializers[j](*nested_columns[j], decoder); } } @@ -742,7 +782,8 @@ void AvroDeserializer::deserializeRow(MutableColumns & columns, avro::Decoder & row_action.execute(columns, decoder, ext); for (size_t i = 0; i < ext.read_columns.size(); ++i) { - if (!ext.read_columns[i]) + /// Insert default in missing columns. + if (!column_found[i]) { columns[i]->insertDefault(); } @@ -759,7 +800,7 @@ void AvroRowInputFormat::readPrefix() { file_reader_ptr = std::make_unique(std::make_unique(*in)); deserializer_ptr = std::make_unique( - output.getHeader(), file_reader_ptr->dataSchema(), format_settings.avro.allow_missing_fields, format_settings.avro.null_as_default); + output.getHeader(), file_reader_ptr->dataSchema(), format_settings.avro.allow_missing_fields, format_settings.null_as_default); file_reader_ptr->init(); } @@ -950,7 +991,7 @@ const AvroDeserializer & AvroConfluentRowInputFormat::getOrCreateDeserializer(Sc { auto schema = schema_registry->getSchema(schema_id); AvroDeserializer deserializer( - output.getHeader(), schema, format_settings.avro.allow_missing_fields, format_settings.avro.null_as_default); + output.getHeader(), schema, format_settings.avro.allow_missing_fields, format_settings.null_as_default); it = deserializer_cache.emplace(schema_id, deserializer).first; } return it->second; diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.h b/src/Processors/Formats/Impl/AvroRowInputFormat.h index 96370b8c4c7..dcd51398032 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.h +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.h @@ -35,8 +35,8 @@ public: void deserializeRow(MutableColumns & columns, avro::Decoder & decoder, RowReadExtension & ext) const; private: - using DeserializeFn = std::function; - using DeserializeNestedFn = std::function; + using DeserializeFn = std::function; + using DeserializeNestedFn = std::function; using SkipFn = std::function; DeserializeFn createDeserializeFn(avro::NodePtr root_node, DataTypePtr target_type); @@ -86,8 +86,7 @@ private: case Noop: break; case Deserialize: - deserialize_fn(*columns[target_column_idx], decoder); - ext.read_columns[target_column_idx] = true; + ext.read_columns[target_column_idx] = deserialize_fn(*columns[target_column_idx], decoder); break; case Skip: skip_fn(decoder); diff --git a/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp b/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp index f337eedbb05..488f4ff9a73 100644 --- a/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/MsgPackRowInputFormat.cpp @@ -45,11 +45,11 @@ namespace ErrorCodes extern const int UNEXPECTED_END_OF_FILE; } -MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_) - : MsgPackRowInputFormat(header_, std::make_unique(in_), params_) {} +MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_, const FormatSettings & settings) + : MsgPackRowInputFormat(header_, std::make_unique(in_), params_, settings) {} -MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_) - : IRowInputFormat(header_, *buf_, std::move(params_)), buf(std::move(buf_)), parser(visitor), data_types(header_.getDataTypes()) {} +MsgPackRowInputFormat::MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_, const FormatSettings & settings) + : IRowInputFormat(header_, *buf_, std::move(params_)), buf(std::move(buf_)), visitor(settings.null_as_default), parser(visitor), data_types(header_.getDataTypes()) {} void MsgPackRowInputFormat::resetParser() { @@ -58,13 +58,13 @@ void MsgPackRowInputFormat::resetParser() visitor.reset(); } -void MsgPackVisitor::set_info(IColumn & column, DataTypePtr type) // NOLINT +void MsgPackVisitor::set_info(IColumn & column, DataTypePtr type, UInt8 & read) // NOLINT { while (!info_stack.empty()) { info_stack.pop(); } - info_stack.push(Info{column, type}); + info_stack.push(Info{column, type, &read}); } void MsgPackVisitor::reset() @@ -228,11 +228,11 @@ static void insertFloat64(IColumn & column, DataTypePtr type, Float64 value) // assert_cast(column).insertValue(value); } -static void insertNull(IColumn & column, DataTypePtr type) +static void insertNull(IColumn & column, DataTypePtr type, UInt8 * read, bool null_as_default) { auto insert_func = [&](IColumn & column_, DataTypePtr type_) { - insertNull(column_, type_); + insertNull(column_, type_, read, null_as_default); }; /// LowCardinality(Nullable(...)) @@ -240,7 +240,16 @@ static void insertNull(IColumn & column, DataTypePtr type) return; if (!type->isNullable()) - throw Exception(ErrorCodes::ILLEGAL_COLUMN, "Cannot insert MessagePack null into non-nullable column with type {}.", type->getName()); + { + if (!null_as_default) + throw Exception( + ErrorCodes::ILLEGAL_COLUMN, "Cannot insert MessagePack null into non-nullable column with type {}.", type->getName()); + column.insertDefault(); + /// In case of default on null column can have defined DEFAULT expression that should be used. + if (read) + *read = false; + return; + } assert_cast(column).insertDefault(); } @@ -316,7 +325,7 @@ bool MsgPackVisitor::start_array(size_t size) // NOLINT ColumnArray::Offsets & offsets = column_array.getOffsets(); IColumn & nested_column = column_array.getData(); offsets.push_back(offsets.back() + size); - info_stack.push(Info{nested_column, nested_type}); + info_stack.push(Info{nested_column, nested_type, nullptr}); return true; } @@ -340,7 +349,7 @@ bool MsgPackVisitor::start_map_key() // NOLINT { auto key_column = assert_cast(info_stack.top().column).getNestedData().getColumns()[0]; auto key_type = assert_cast(*info_stack.top().type).getKeyType(); - info_stack.push(Info{*key_column, key_type}); + info_stack.push(Info{*key_column, key_type, nullptr}); return true; } @@ -354,7 +363,7 @@ bool MsgPackVisitor::start_map_value() // NOLINT { auto value_column = assert_cast(info_stack.top().column).getNestedData().getColumns()[1]; auto value_type = assert_cast(*info_stack.top().type).getValueType(); - info_stack.push(Info{*value_column, value_type}); + info_stack.push(Info{*value_column, value_type, nullptr}); return true; } @@ -366,7 +375,7 @@ bool MsgPackVisitor::end_map_value() // NOLINT bool MsgPackVisitor::visit_nil() { - insertNull(info_stack.top().column, info_stack.top().type); + insertNull(info_stack.top().column, info_stack.top().type, info_stack.top().read, null_as_default); return true; } @@ -407,13 +416,14 @@ bool MsgPackRowInputFormat::readObject() return true; } -bool MsgPackRowInputFormat::readRow(MutableColumns & columns, RowReadExtension &) +bool MsgPackRowInputFormat::readRow(MutableColumns & columns, RowReadExtension & ext) { size_t column_index = 0; bool has_more_data = true; + ext.read_columns.resize(columns.size(), true); for (; column_index != columns.size(); ++column_index) { - visitor.set_info(*columns[column_index], data_types[column_index]); + visitor.set_info(*columns[column_index], data_types[column_index], ext.read_columns[column_index]); has_more_data = readObject(); if (!has_more_data) break; @@ -547,9 +557,9 @@ void registerInputFormatMsgPack(FormatFactory & factory) ReadBuffer & buf, const Block & sample, const RowInputFormatParams & params, - const FormatSettings &) + const FormatSettings & settings) { - return std::make_shared(sample, buf, params); + return std::make_shared(sample, buf, params, settings); }); factory.registerFileExtension("messagepack", "MsgPack"); } diff --git a/src/Processors/Formats/Impl/MsgPackRowInputFormat.h b/src/Processors/Formats/Impl/MsgPackRowInputFormat.h index 64bb8b569e0..5eaa3719d0c 100644 --- a/src/Processors/Formats/Impl/MsgPackRowInputFormat.h +++ b/src/Processors/Formats/Impl/MsgPackRowInputFormat.h @@ -19,10 +19,13 @@ class ReadBuffer; class MsgPackVisitor : public msgpack::null_visitor { public: + MsgPackVisitor(bool null_as_default_) : null_as_default(null_as_default_) {} + struct Info { IColumn & column; DataTypePtr type; + UInt8 * read; }; /// These functions are called when parser meets corresponding object in parsed data @@ -47,25 +50,26 @@ public: [[noreturn]] void parse_error(size_t parsed_offset, size_t error_offset); /// Update info_stack - void set_info(IColumn & column, DataTypePtr type); + void set_info(IColumn & column, DataTypePtr type, UInt8 & read); void reset(); private: /// Stack is needed to process arrays and maps std::stack info_stack; + bool null_as_default; }; class MsgPackRowInputFormat : public IRowInputFormat { public: - MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_); + MsgPackRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_, const FormatSettings & settings); String getName() const override { return "MagPackRowInputFormat"; } void resetParser() override; void setReadBuffer(ReadBuffer & in_) override; private: - MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_); + MsgPackRowInputFormat(const Block & header_, std::unique_ptr buf_, Params params_, const FormatSettings & settings); bool readRow(MutableColumns & columns, RowReadExtension & ext) override; diff --git a/src/Processors/Formats/Impl/NativeFormat.cpp b/src/Processors/Formats/Impl/NativeFormat.cpp index 959b86ec051..3c1a2bd5965 100644 --- a/src/Processors/Formats/Impl/NativeFormat.cpp +++ b/src/Processors/Formats/Impl/NativeFormat.cpp @@ -17,7 +17,13 @@ class NativeInputFormat final : public IInputFormat public: NativeInputFormat(ReadBuffer & buf, const Block & header_, const FormatSettings & settings) : IInputFormat(header_, buf) - , reader(std::make_unique(buf, header_, 0, settings.skip_unknown_fields)) + , reader(std::make_unique( + buf, + header_, + 0, + settings.skip_unknown_fields, + settings.null_as_default, + settings.defaults_for_omitted_fields ? &block_missing_values : nullptr)) , header(header_) {} String getName() const override { return "Native"; } @@ -30,6 +36,7 @@ public: Chunk generate() override { + block_missing_values.clear(); auto block = reader->read(); if (!block) return {}; @@ -47,9 +54,12 @@ public: IInputFormat::setReadBuffer(in_); } + const BlockMissingValues & getMissingValues() const override { return block_missing_values; } + private: std::unique_ptr reader; Block header; + BlockMissingValues block_missing_values; }; class NativeOutputFormat final : public IOutputFormat diff --git a/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp b/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp index 2e45d817506..03f056e22b3 100644 --- a/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ORCBlockInputFormat.cpp @@ -67,12 +67,10 @@ Chunk ORCBlockInputFormat::generate() ++stripe_current; Chunk res; - arrow_column_to_ch_column->arrowTableToCHChunk(res, table, num_rows); /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. /// Otherwise fill the missing columns with zero values of its type. - if (format_settings.defaults_for_omitted_fields) - for (const auto & column_idx : missing_columns) - block_missing_values.setBits(column_idx, res.getNumRows()); + BlockMissingValues * block_missing_values_ptr = format_settings.defaults_for_omitted_fields ? &block_missing_values : nullptr; + arrow_column_to_ch_column->arrowTableToCHChunk(res, table, num_rows, block_missing_values_ptr); return res; } @@ -128,8 +126,8 @@ void ORCBlockInputFormat::prepareReader() "ORC", format_settings.orc.import_nested, format_settings.orc.allow_missing_columns, + format_settings.null_as_default, format_settings.orc.case_insensitive_column_matching); - missing_columns = arrow_column_to_ch_column->getMissingColumns(*schema); ArrowFieldIndexUtil field_util( format_settings.orc.case_insensitive_column_matching, diff --git a/src/Processors/Formats/Impl/ORCBlockInputFormat.h b/src/Processors/Formats/Impl/ORCBlockInputFormat.h index bc2abe41cc1..3d8bc781278 100644 --- a/src/Processors/Formats/Impl/ORCBlockInputFormat.h +++ b/src/Processors/Formats/Impl/ORCBlockInputFormat.h @@ -49,7 +49,6 @@ private: // indices of columns to read from ORC file std::vector include_indices; - std::vector missing_columns; BlockMissingValues block_missing_values; const FormatSettings format_settings; diff --git a/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp b/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp index d2ec3c02eed..fca097d8ea7 100644 --- a/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ParquetBlockInputFormat.cpp @@ -71,7 +71,10 @@ Chunk ParquetBlockInputFormat::generate() if (*batch) { auto tmp_table = arrow::Table::FromRecordBatches({*batch}); - arrow_column_to_ch_column->arrowTableToCHChunk(res, *tmp_table, (*tmp_table)->num_rows()); + /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. + /// Otherwise fill the missing columns with zero values of its type. + BlockMissingValues * block_missing_values_ptr = format_settings.defaults_for_omitted_fields ? &block_missing_values : nullptr; + arrow_column_to_ch_column->arrowTableToCHChunk(res, *tmp_table, (*tmp_table)->num_rows(), block_missing_values_ptr); } else { @@ -80,12 +83,6 @@ Chunk ParquetBlockInputFormat::generate() return {}; } - /// If defaults_for_omitted_fields is true, calculate the default values from default expression for omitted fields. - /// Otherwise fill the missing columns with zero values of its type. - if (format_settings.defaults_for_omitted_fields) - for (const auto & column_idx : missing_columns) - block_missing_values.setBits(column_idx, res.getNumRows()); - return res; } @@ -133,8 +130,8 @@ void ParquetBlockInputFormat::prepareReader() "Parquet", format_settings.parquet.import_nested, format_settings.parquet.allow_missing_columns, + format_settings.null_as_default, format_settings.parquet.case_insensitive_column_matching); - missing_columns = arrow_column_to_ch_column->getMissingColumns(*schema); ArrowFieldIndexUtil field_util( format_settings.parquet.case_insensitive_column_matching, diff --git a/src/Processors/Formats/Impl/ParquetBlockInputFormat.h b/src/Processors/Formats/Impl/ParquetBlockInputFormat.h index 37878a94dd9..afc46939c79 100644 --- a/src/Processors/Formats/Impl/ParquetBlockInputFormat.h +++ b/src/Processors/Formats/Impl/ParquetBlockInputFormat.h @@ -42,7 +42,6 @@ private: // indices of columns to read from Parquet file std::vector column_indices; std::unique_ptr arrow_column_to_ch_column; - std::vector missing_columns; BlockMissingValues block_missing_values; const FormatSettings format_settings; const std::unordered_set & skip_row_groups; diff --git a/tests/queries/0_stateless/02561_null_as_default_more_formats.reference b/tests/queries/0_stateless/02561_null_as_default_more_formats.reference new file mode 100644 index 00000000000..f5d4f41efe8 --- /dev/null +++ b/tests/queries/0_stateless/02561_null_as_default_more_formats.reference @@ -0,0 +1,36 @@ +Parquet +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +Arrow +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +ORC +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +Avro +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +MsgPack +1 +0 0 0 +0 0 +0 0 0 +42 0 42 +Native +1 +0 0 0 +0 0 +0 0 0 +42 0 42 diff --git a/tests/queries/0_stateless/02561_null_as_default_more_formats.sh b/tests/queries/0_stateless/02561_null_as_default_more_formats.sh new file mode 100755 index 00000000000..eacd8e964a6 --- /dev/null +++ b/tests/queries/0_stateless/02561_null_as_default_more_formats.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Tags: no-fasttest + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "drop table if exists test" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64 default 42, y UInt64, z LowCardinality(String) default '42') engine=Memory"; +for format in Parquet Arrow ORC Avro MsgPack Native +do + echo $format + $CLICKHOUSE_CLIENT -q "select number % 2 ? NULL : number as x, x as y, CAST(number % 2 ? NULL : toString(number), 'LowCardinality(Nullable(String))') as z from numbers(2) format $format" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_null_as_default=0 format $format" 2>&1 | grep "Exception" -c + $CLICKHOUSE_CLIENT -q "select number % 2 ? NULL : number as x, x as y, CAST(number % 2 ? NULL : toString(number), 'LowCardinality(Nullable(String))') as z from numbers(2) format $format settings output_format_arrow_low_cardinality_as_dictionary=1" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_null_as_default=1, input_format_defaults_for_omitted_fields=0 format $format" + $CLICKHOUSE_CLIENT -q "select * from test" + $CLICKHOUSE_CLIENT -q "truncate table test" + $CLICKHOUSE_CLIENT -q "select number % 2 ? NULL : number as x, x as y, CAST(number % 2 ? NULL : toString(number), 'LowCardinality(Nullable(String))') as z from numbers(2) format $format settings output_format_arrow_low_cardinality_as_dictionary=1" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_null_as_default=1, input_format_defaults_for_omitted_fields=1 format $format" + $CLICKHOUSE_CLIENT -q "select * from test" + $CLICKHOUSE_CLIENT -q "truncate table test" +done + diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference b/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference new file mode 100644 index 00000000000..e072efc3352 --- /dev/null +++ b/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference @@ -0,0 +1,4 @@ +0 0 +1 0 +0 42 +1 42 diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh b/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh new file mode 100755 index 00000000000..c3d174d77e8 --- /dev/null +++ b/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "drop table if exists test" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=Memory" + +$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=0 format Native" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "truncate table test" + +$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=1 format Native" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "truncate table test" From 04cf144edc9abba1f9e61ff4f4a949140f8f51e7 Mon Sep 17 00:00:00 2001 From: avogar Date: Fri, 10 Feb 2023 17:20:51 +0000 Subject: [PATCH 057/445] Fix TSKV, update docs --- .../operations/settings/settings-formats.md | 11 +++++--- .../Formats/Impl/TSKVRowInputFormat.cpp | 5 +++- .../02562_native_null_on_missing_columns.sh | 16 ------------ ...tskv_default_for_omitted_fields.reference} | 4 +-- ..._native_tskv_default_for_omitted_fields.sh | 25 +++++++++++++++++++ 5 files changed, 38 insertions(+), 23 deletions(-) delete mode 100755 tests/queries/0_stateless/02562_native_null_on_missing_columns.sh rename tests/queries/0_stateless/{02562_native_null_on_missing_columns.reference => 02562_native_tskv_default_for_omitted_fields.reference} (50%) create mode 100755 tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh diff --git a/docs/en/operations/settings/settings-formats.md b/docs/en/operations/settings/settings-formats.md index fd727704710..dda97696de5 100644 --- a/docs/en/operations/settings/settings-formats.md +++ b/docs/en/operations/settings/settings-formats.md @@ -15,11 +15,12 @@ When writing data, ClickHouse throws an exception if input data contain columns Supported formats: -- [JSONEachRow](../../interfaces/formats.md/#jsoneachrow) +- [JSONEachRow](../../interfaces/formats.md/#jsoneachrow) (and other JSON formats) +- [BSONEachRow](../../interfaces/formats.md/#bsoneachrow) (and other JSON formats) - [TSKV](../../interfaces/formats.md/#tskv) - All formats with suffixes WithNames/WithNamesAndTypes -- [JSONColumns](../../interfaces/formats.md/#jsoncolumns) - [MySQLDump](../../interfaces/formats.md/#mysqldump) +- [Native](../../interfaces/formats.md/#native) Possible values: @@ -78,7 +79,7 @@ Default value: 1. ## input_format_defaults_for_omitted_fields {#input_format_defaults_for_omitted_fields} -When performing `INSERT` queries, replace omitted input column values with default values of the respective columns. This option only applies to [JSONEachRow](../../interfaces/formats.md/#jsoneachrow), [CSV](../../interfaces/formats.md/#csv), [TabSeparated](../../interfaces/formats.md/#tabseparated) formats and formats with `WithNames`/`WithNamesAndTypes` suffixes. +When performing `INSERT` queries, replace omitted input column values with default values of the respective columns. This option applies to [JSONEachRow](../../interfaces/formats.md/#jsoneachrow) (and other JSON formats), [CSV](../../interfaces/formats.md/#csv), [TabSeparated](../../interfaces/formats.md/#tabseparated), [TSKV](../../interfaces/formats.md/#tskv), [Parquet](../../interfaces/formats.md/#parquet), [Arrow](../../interfaces/formats.md/#arrow), [Avro](../../interfaces/formats.md/#avro), [ORC](../../interfaces/formats.md/#orc), [Native](../../interfaces/formats.md/#native) formats and formats with `WithNames`/`WithNamesAndTypes` suffixes. :::note When this option is enabled, extended table metadata are sent from server to client. It consumes additional computing resources on the server and can reduce performance. @@ -96,7 +97,9 @@ Default value: 1. Enables or disables the initialization of [NULL](../../sql-reference/syntax.md/#null-literal) fields with [default values](../../sql-reference/statements/create/table.md/#create-default-values), if data type of these fields is not [nullable](../../sql-reference/data-types/nullable.md/#data_type-nullable). If column type is not nullable and this setting is disabled, then inserting `NULL` causes an exception. If column type is nullable, then `NULL` values are inserted as is, regardless of this setting. -This setting is applicable to [INSERT ... VALUES](../../sql-reference/statements/insert-into.md) queries for text input formats. +This setting is applicable for most input formats. + +For complex default expressions `input_format_defaults_for_omitted_fields` must be enabled too. Possible values: diff --git a/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp b/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp index bf6d0ab88d2..23a8589bd0a 100644 --- a/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/TSKVRowInputFormat.cpp @@ -193,7 +193,10 @@ bool TSKVRowInputFormat::readRow(MutableColumns & columns, RowReadExtension & ex header.getByPosition(i).type->insertDefaultInto(*columns[i]); /// return info about defaults set - ext.read_columns = read_columns; + if (format_settings.defaults_for_omitted_fields) + ext.read_columns = read_columns; + else + ext.read_columns.assign(num_columns, true); return true; } diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh b/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh deleted file mode 100755 index c3d174d77e8..00000000000 --- a/tests/queries/0_stateless/02562_native_null_on_missing_columns.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -# shellcheck source=../shell_config.sh -. "$CUR_DIR"/../shell_config.sh - -$CLICKHOUSE_CLIENT -q "drop table if exists test" -$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=Memory" - -$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=0 format Native" -$CLICKHOUSE_CLIENT -q "select * from test" -$CLICKHOUSE_CLIENT -q "truncate table test" - -$CLICKHOUSE_CLIENT -q "select number as x from numbers(2) format Native" | $CLICKHOUSE_CLIENT -q "insert into test settings input_format_defaults_for_omitted_fields=1 format Native" -$CLICKHOUSE_CLIENT -q "select * from test" -$CLICKHOUSE_CLIENT -q "truncate table test" diff --git a/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.reference similarity index 50% rename from tests/queries/0_stateless/02562_native_null_on_missing_columns.reference rename to tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.reference index e072efc3352..17197fa3563 100644 --- a/tests/queries/0_stateless/02562_native_null_on_missing_columns.reference +++ b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.reference @@ -1,4 +1,4 @@ -0 0 1 0 -0 42 +1 42 +1 0 1 42 diff --git a/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh new file mode 100755 index 00000000000..a08c948705d --- /dev/null +++ b/tests/queries/0_stateless/02562_native_tskv_default_for_omitted_fields.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "drop table if exists test" +$CLICKHOUSE_CLIENT -q "insert into function file(02562_data.native) select 1::UInt64 as x settings engine_file_truncate_on_insert=1" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(Native, '02562_data.native') settings input_format_defaults_for_omitted_fields=0" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(Native, '02562_data.native') settings input_format_defaults_for_omitted_fields=1" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + +$CLICKHOUSE_CLIENT -q "insert into function file(02562_data.tskv) select 1::UInt64 as x settings engine_file_truncate_on_insert=1" +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(TSKV, '02562_data.tskv') settings input_format_defaults_for_omitted_fields=0" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + +$CLICKHOUSE_CLIENT -q "create table test (x UInt64, y UInt64 default 42) engine=File(TSKV, '02562_data.tskv') settings input_format_defaults_for_omitted_fields=1" +$CLICKHOUSE_CLIENT -q "select * from test" +$CLICKHOUSE_CLIENT -q "drop table test" + From 43e660ebd2cdb18fadda2a1385c014135654c4d9 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:28:19 +0100 Subject: [PATCH 058/445] Update run.sh --- docker/test/upgrade/run.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/test/upgrade/run.sh b/docker/test/upgrade/run.sh index bbf24e96685..e2392dd4438 100644 --- a/docker/test/upgrade/run.sh +++ b/docker/test/upgrade/run.sh @@ -416,6 +416,7 @@ else -e "Session expired" \ -e "TOO_MANY_PARTS" \ -e "Authentication failed" \ + -e "Container already exists" \ /var/log/clickhouse-server/clickhouse-server.upgrade.log | zgrep -Fa "" > /test_output/upgrade_error_messages.txt \ && echo -e "Error message in clickhouse-server.log (see upgrade_error_messages.txt)$FAIL$(head_escaped /test_output/bc_check_error_messages.txt)" \ >> /test_output/test_results.tsv \ From c68ef743b0a073bce75ddd649f1b85cc7411cf95 Mon Sep 17 00:00:00 2001 From: alesapin Date: Sat, 11 Feb 2023 12:55:05 +0100 Subject: [PATCH 059/445] Fix flaky test --- tests/queries/0_stateless/02555_davengers_rename_chain.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/queries/0_stateless/02555_davengers_rename_chain.sh b/tests/queries/0_stateless/02555_davengers_rename_chain.sh index f4af5e091ad..b23f8085fd7 100755 --- a/tests/queries/0_stateless/02555_davengers_rename_chain.sh +++ b/tests/queries/0_stateless/02555_davengers_rename_chain.sh @@ -27,8 +27,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata' AND database='${CLICKHOUSE_DATABASE}'") - if [[ $result == *"a TO a1"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SHOW CREATE TABLE wrong_metadata") + if [[ $result == *"\`a1\` UInt64"* ]]; then break; fi sleep 0.1 @@ -98,8 +98,8 @@ counter=0 retries=60 I=0 while [[ $counter -lt $retries ]]; do I=$((I + 1)) - result=$($CLICKHOUSE_CLIENT --query "SELECT * FROM system.mutations WHERE table = 'wrong_metadata_compact' AND database='${CLICKHOUSE_DATABASE}'") - if [[ $result == *"a TO a1"* ]]; then + result=$($CLICKHOUSE_CLIENT --query "SHOW CREATE TABLE wrong_metadata_compact") + if [[ $result == *"\`a1\` UInt64"* ]]; then break; fi sleep 0.1 From d5f413304dfe2b9807c6bbef530f803c14e86bdf Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 13 Feb 2023 13:28:09 +0100 Subject: [PATCH 060/445] Store null map size into a variable --- src/Columns/ColumnNullable.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Columns/ColumnNullable.cpp b/src/Columns/ColumnNullable.cpp index 99d377f10eb..139323f9770 100644 --- a/src/Columns/ColumnNullable.cpp +++ b/src/Columns/ColumnNullable.cpp @@ -786,16 +786,17 @@ ColumnPtr ColumnNullable::getNestedColumnWithDefaultOnNull() const auto res = nested_column->cloneEmpty(); const auto & null_map_data = getNullMapData(); size_t start = 0; + size_t end = null_map->size(); while (start < nested_column->size()) { size_t next_null_index = start; - while (next_null_index < null_map->size() && !null_map_data[next_null_index]) + while (next_null_index < end && !null_map_data[next_null_index]) ++next_null_index; if (next_null_index != start) res->insertRangeFrom(*nested_column, start, next_null_index - start); - if (next_null_index < null_map->size()) + if (next_null_index < end) res->insertDefault(); start = next_null_index + 1; From d67e7e47f5025744e4fdc291952303e646634684 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 13 Feb 2023 13:28:46 +0100 Subject: [PATCH 061/445] Update src/Core/Settings.h --- 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 481929d915f..7fec350f208 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -751,7 +751,7 @@ class IColumn; M(Bool, input_format_csv_empty_as_default, true, "Treat empty fields in CSV input as default values.", 0) \ M(Bool, input_format_tsv_empty_as_default, false, "Treat empty fields in TSV input as default values.", 0) \ M(Bool, input_format_tsv_enum_as_number, false, "Treat inserted enum values in TSV formats as enum indices.", 0) \ - M(Bool, input_format_null_as_default, true, "For most input formats initialize null fields with default values if data type of this field is not nullable", 0) \ + M(Bool, input_format_null_as_default, true, "Initialize null fields with default values if the data type of this field is not nullable and it is supported by the input format", 0) \ M(Bool, input_format_arrow_import_nested, false, "Allow to insert array of structs into Nested table in Arrow input format.", 0) \ M(Bool, input_format_arrow_case_insensitive_column_matching, false, "Ignore case when matching Arrow columns with CH columns.", 0) \ M(Bool, input_format_orc_import_nested, false, "Allow to insert array of structs into Nested table in ORC input format.", 0) \ From e9e6d735ef12a3276b03c1aa4ca2fc9c087c1fea Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 13 Feb 2023 16:44:54 +0000 Subject: [PATCH 062/445] add query_id header in all queries --- src/Interpreters/executeQuery.cpp | 14 +++++++++++--- src/Interpreters/executeQuery.h | 10 +++++++++- src/Server/HTTPHandler.cpp | 18 +++++++++++++----- src/Server/MySQLHandler.cpp | 4 ++-- .../02564_query_id_header.reference | 5 +++++ .../0_stateless/02564_query_id_header.sh | 13 +++++++++++++ 6 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 tests/queries/0_stateless/02564_query_id_header.reference create mode 100755 tests/queries/0_stateless/02564_query_id_header.sh diff --git a/src/Interpreters/executeQuery.cpp b/src/Interpreters/executeQuery.cpp index 06d92116adc..6fd3528ec65 100644 --- a/src/Interpreters/executeQuery.cpp +++ b/src/Interpreters/executeQuery.cpp @@ -1249,6 +1249,12 @@ void executeQuery( std::tie(ast, streams) = executeQueryImpl(begin, end, context, false, QueryProcessingStage::Complete, &istr); auto & pipeline = streams.pipeline; + QueryResultDetails result_details + { + .query_id = context->getClientInfo().current_query_id, + .timezone = DateLUT::instance().getTimeZone(), + }; + std::unique_ptr compressed_buffer; try { @@ -1307,9 +1313,8 @@ void executeQuery( out->onProgress(progress); }); - if (set_result_details) - set_result_details( - context->getClientInfo().current_query_id, out->getContentType(), format_name, DateLUT::instance().getTimeZone()); + result_details.content_type = out->getContentType(); + result_details.format = format_name; pipeline.complete(std::move(out)); } @@ -1318,6 +1323,9 @@ void executeQuery( pipeline.setProgressCallback(context->getProgressCallback()); } + if (set_result_details) + set_result_details(result_details); + if (pipeline.initialized()) { CompletedPipelineExecutor executor(pipeline); diff --git a/src/Interpreters/executeQuery.h b/src/Interpreters/executeQuery.h index 9c561d8b88c..93152cc1de6 100644 --- a/src/Interpreters/executeQuery.h +++ b/src/Interpreters/executeQuery.h @@ -11,7 +11,15 @@ namespace DB class ReadBuffer; class WriteBuffer; -using SetResultDetailsFunc = std::function; +struct QueryResultDetails +{ + String query_id; + std::optional content_type; + std::optional format; + std::optional timezone; +}; + +using SetResultDetailsFunc = std::function; /// Parse and execute a query. void executeQuery( diff --git a/src/Server/HTTPHandler.cpp b/src/Server/HTTPHandler.cpp index 29bfa8065ba..1e249b77079 100644 --- a/src/Server/HTTPHandler.cpp +++ b/src/Server/HTTPHandler.cpp @@ -819,12 +819,20 @@ void HTTPHandler::processQuery( customizeContext(request, context); executeQuery(*in, *used_output.out_maybe_delayed_and_compressed, /* allow_into_outfile = */ false, context, - [&response, this] (const String & current_query_id, const String & content_type, const String & format, const String & timezone) + [&response, this] (const QueryResultDetails & details) { - response.setContentType(content_type_override.value_or(content_type)); - response.add("X-ClickHouse-Query-Id", current_query_id); - response.add("X-ClickHouse-Format", format); - response.add("X-ClickHouse-Timezone", timezone); + response.add("X-ClickHouse-Query-Id", details.query_id); + + if (content_type_override) + response.setContentType(*content_type_override); + else if (details.content_type) + response.setContentType(*details.content_type); + + if (details.format) + response.add("X-ClickHouse-Format", *details.format); + + if (details.timezone) + response.add("X-ClickHouse-Timezone", *details.timezone); } ); diff --git a/src/Server/MySQLHandler.cpp b/src/Server/MySQLHandler.cpp index 3715d658730..e3467d664e7 100644 --- a/src/Server/MySQLHandler.cpp +++ b/src/Server/MySQLHandler.cpp @@ -352,9 +352,9 @@ void MySQLHandler::comQuery(ReadBuffer & payload) format_settings.mysql_wire.max_packet_size = max_packet_size; format_settings.mysql_wire.sequence_id = &sequence_id; - auto set_result_details = [&with_output](const String &, const String &, const String &format, const String &) + auto set_result_details = [&with_output](const QueryResultDetails & details) { - if (format != "MySQLWire") + if (details.format && *details.format != "MySQLWire") throw Exception(ErrorCodes::UNSUPPORTED_METHOD, "MySQL protocol does not support custom output formats"); with_output = true; }; diff --git a/tests/queries/0_stateless/02564_query_id_header.reference b/tests/queries/0_stateless/02564_query_id_header.reference new file mode 100644 index 00000000000..655f9fa9eaa --- /dev/null +++ b/tests/queries/0_stateless/02564_query_id_header.reference @@ -0,0 +1,5 @@ +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id +X-ClickHouse-Query-Id diff --git a/tests/queries/0_stateless/02564_query_id_header.sh b/tests/queries/0_stateless/02564_query_id_header.sh new file mode 100755 index 00000000000..440182cd243 --- /dev/null +++ b/tests/queries/0_stateless/02564_query_id_header.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 + +${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS t_query_id_header" + +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "INSERT INTO t_query_id_header VALUES (1)" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "EXISTS TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "SELECT * FROM t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" +${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "DROP TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" From a4d9688775d8d39b8b462b4711d649776d4dcfaa Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 13 Feb 2023 19:35:07 +0100 Subject: [PATCH 063/445] fix 'Directory not empty after drop' with zero copy replication --- src/Storages/MergeTree/MergeTreeData.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index c3c4cd3082d..3c260484d95 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1852,11 +1852,11 @@ void MergeTreeData::stopOutdatedDataPartsLoadingTask() /// (Only files on the first level of nesting are considered). static bool isOldPartDirectory(const DiskPtr & disk, const String & directory_path, time_t threshold) { - if (!disk->isDirectory(directory_path) || disk->getLastModified(directory_path).epochTime() >= threshold) + if (!disk->isDirectory(directory_path) || disk->getLastModified(directory_path).epochTime() > threshold) return false; for (auto it = disk->iterateDirectory(directory_path); it->isValid(); it->next()) - if (disk->getLastModified(it->path()).epochTime() >= threshold) + if (disk->getLastModified(it->path()).epochTime() > threshold) return false; return true; From e712fbecb2ee7aeb2dd1bd1913218d3ba21d6252 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 14 Feb 2023 00:45:28 +0100 Subject: [PATCH 064/445] fix race between drop and create --- src/Interpreters/DatabaseCatalog.cpp | 1 + src/Interpreters/DatabaseCatalog.h | 4 +- src/Storages/StorageReplicatedMergeTree.cpp | 41 ++++++++++---- src/Storages/StorageReplicatedMergeTree.h | 2 +- tests/integration/helpers/cluster.py | 4 +- .../configs/config.d/storage_conf.xml | 2 + .../test.py | 56 +++++++++++++++++++ 7 files changed, 96 insertions(+), 14 deletions(-) diff --git a/src/Interpreters/DatabaseCatalog.cpp b/src/Interpreters/DatabaseCatalog.cpp index ad5d9d4d325..c3ac7122362 100644 --- a/src/Interpreters/DatabaseCatalog.cpp +++ b/src/Interpreters/DatabaseCatalog.cpp @@ -147,6 +147,7 @@ void DatabaseCatalog::initializeAndLoadTemporaryDatabase() unused_dir_hide_timeout_sec = getContext()->getConfigRef().getInt64("database_catalog_unused_dir_hide_timeout_sec", unused_dir_hide_timeout_sec); unused_dir_rm_timeout_sec = getContext()->getConfigRef().getInt64("database_catalog_unused_dir_rm_timeout_sec", unused_dir_rm_timeout_sec); unused_dir_cleanup_period_sec = getContext()->getConfigRef().getInt64("database_catalog_unused_dir_cleanup_period_sec", unused_dir_cleanup_period_sec); + drop_error_cooldown_sec = getContext()->getConfigRef().getInt64("database_catalog_drop_error_cooldown_sec", drop_error_cooldown_sec); auto db_for_temporary_and_external_tables = std::make_shared(TEMPORARY_DATABASE, getContext()); attachDatabase(TEMPORARY_DATABASE, db_for_temporary_and_external_tables); diff --git a/src/Interpreters/DatabaseCatalog.h b/src/Interpreters/DatabaseCatalog.h index ba3625626da..c37029d8b6b 100644 --- a/src/Interpreters/DatabaseCatalog.h +++ b/src/Interpreters/DatabaseCatalog.h @@ -278,7 +278,6 @@ private: bool maybeRemoveDirectory(const String & disk_name, const DiskPtr & disk, const String & unused_dir); static constexpr size_t reschedule_time_ms = 100; - static constexpr time_t drop_error_cooldown_sec = 5; mutable std::mutex databases_mutex; @@ -325,6 +324,9 @@ private: time_t unused_dir_rm_timeout_sec = default_unused_dir_rm_timeout_sec; static constexpr time_t default_unused_dir_cleanup_period_sec = 24 * 60 * 60; /// 1 day time_t unused_dir_cleanup_period_sec = default_unused_dir_cleanup_period_sec; + + static constexpr time_t default_drop_error_cooldown_sec = 5; + time_t drop_error_cooldown_sec = default_drop_error_cooldown_sec; }; /// This class is useful when creating a table or database. diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index d3590657a5c..c92d4efab0d 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -903,17 +903,16 @@ void StorageReplicatedMergeTree::drop() /// in this case, has_metadata_in_zookeeper = false, and we also permit to drop the table. bool maybe_has_metadata_in_zookeeper = !has_metadata_in_zookeeper.has_value() || *has_metadata_in_zookeeper; + zkutil::ZooKeeperPtr zookeeper; if (maybe_has_metadata_in_zookeeper) { /// Table can be shut down, restarting thread is not active /// and calling StorageReplicatedMergeTree::getZooKeeper()/getAuxiliaryZooKeeper() won't suffice. - zkutil::ZooKeeperPtr zookeeper = getZooKeeperIfTableShutDown(); + zookeeper = getZooKeeperIfTableShutDown(); /// If probably there is metadata in ZooKeeper, we don't allow to drop the table. if (!zookeeper) throw Exception(ErrorCodes::TABLE_IS_READ_ONLY, "Can't drop readonly replicated table (need to drop data in ZooKeeper as well)"); - - dropReplica(zookeeper, zookeeper_path, replica_name, log, getSettings()); } /// Wait for loading of all outdated parts because @@ -927,10 +926,13 @@ void StorageReplicatedMergeTree::drop() } dropAllData(); + + if (maybe_has_metadata_in_zookeeper) + dropReplica(zookeeper, zookeeper_path, replica_name, log, getSettings(), &has_metadata_in_zookeeper); } void StorageReplicatedMergeTree::dropReplica(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, const String & replica, - Poco::Logger * logger, MergeTreeSettingsPtr table_settings) + Poco::Logger * logger, MergeTreeSettingsPtr table_settings, std::optional * has_metadata_out) { if (zookeeper->expired()) throw Exception(ErrorCodes::TABLE_WAS_NOT_DROPPED, "Table was not dropped because ZooKeeper session has expired."); @@ -988,12 +990,16 @@ void StorageReplicatedMergeTree::dropReplica(zkutil::ZooKeeperPtr zookeeper, con Coordination::errorMessage(code), remote_replica_path); /// And finally remove everything else recursively - zookeeper->tryRemoveRecursive(remote_replica_path); - } + /// It may left some garbage if replica_path subtree is concurrently modified + zookeeper->tryRemoveChildrenRecursive(remote_replica_path); - /// It may left some garbage if replica_path subtree are concurrently modified - if (zookeeper->exists(remote_replica_path)) - LOG_ERROR(logger, "Replica was not completely removed from ZooKeeper, {} still exists and may contain some garbage.", remote_replica_path); + /// Update has_metadata_in_zookeeper to avoid retries. Otherwise we can accidentally remove metadata of a new table on retries + if (has_metadata_out) + *has_metadata_out = false; + + if (zookeeper->tryRemove(remote_replica_path) != Coordination::Error::ZOK) + LOG_ERROR(logger, "Replica was not completely removed from ZooKeeper, {} still exists and may contain some garbage.", remote_replica_path); + } /// Check that `zookeeper_path` exists: it could have been deleted by another replica after execution of previous line. Strings replicas; @@ -8152,6 +8158,12 @@ StorageReplicatedMergeTree::unlockSharedData(const IMergeTreeDataPart & part, co auto shared_id = getTableSharedID(); if (shared_id == toString(UUIDHelpers::Nil)) { + if (zookeeper->exists(zookeeper_path)) + { + LOG_WARNING(log, "Not removing shared data for part {} because replica does not have metadata in ZooKeeper, " + "but table path exist and other replicas may exist. It may leave some garbage on S3", part.name); + return std::make_pair(false, NameSet{}); + } LOG_TRACE(log, "Part {} blobs can be removed, because table {} completely dropped", part.name, getStorageID().getNameForLogs()); return std::make_pair(true, NameSet{}); } @@ -8177,9 +8189,18 @@ StorageReplicatedMergeTree::unlockSharedData(const IMergeTreeDataPart & part, co return std::make_pair(true, NameSet{}); } - /// If table was completely dropped (no meta in zookeeper) we can safely remove parts if (has_metadata_in_zookeeper.has_value() && !has_metadata_in_zookeeper) + { + if (zookeeper->exists(zookeeper_path)) + { + LOG_WARNING(log, "Not removing shared data for part {} because replica does not have metadata in ZooKeeper, " + "but table path exist and other replicas may exist. It may leave some garbage on S3", part.name); + return std::make_pair(false, NameSet{}); + } + + /// If table was completely dropped (no meta in zookeeper) we can safely remove parts return std::make_pair(true, NameSet{}); + } /// We remove parts during table shutdown. If exception happen, restarting thread will be already turned /// off and nobody will reconnect our zookeeper connection. In this case we use zookeeper connection from diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index 0d3856ce672..75e5629b627 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -228,7 +228,7 @@ public: /** Remove a specific replica from zookeeper. */ static void dropReplica(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, const String & replica, - Poco::Logger * logger, MergeTreeSettingsPtr table_settings = nullptr); + Poco::Logger * logger, MergeTreeSettingsPtr table_settings = nullptr, std::optional * has_metadata_out = nullptr); /// Removes table from ZooKeeper after the last replica was dropped static bool removeTableNodesFromZooKeeper(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 0073c25e5d5..0c6b60ee166 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -3263,7 +3263,7 @@ class ClickHouseInstance: sleep_time=0.5, check_callback=lambda x: True, ): - logging.debug(f"Executing query {sql} on {self.name}") + #logging.debug(f"Executing query {sql} on {self.name}") result = None for i in range(retry_count): try: @@ -3282,7 +3282,7 @@ class ClickHouseInstance: return result time.sleep(sleep_time) except Exception as ex: - logging.debug("Retry {} got exception {}".format(i + 1, ex)) + #logging.debug("Retry {} got exception {}".format(i + 1, ex)) time.sleep(sleep_time) if result is not None: diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml index bd59694f65a..15239041478 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml @@ -53,4 +53,6 @@ 0 + 3 + 0 diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 60a1b9b9746..434957de53f 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -1,9 +1,11 @@ import logging import random import string +import time import pytest from helpers.cluster import ClickHouseCluster +from helpers.network import PartitionManager logging.getLogger().setLevel(logging.INFO) logging.getLogger().addHandler(logging.StreamHandler()) @@ -127,3 +129,57 @@ def test_insert_select_replicated(cluster, min_rows_for_wide_part, files_per_par assert len( list(minio.list_objects(cluster.minio_bucket, "data/", recursive=True)) ) == (3 * FILES_OVERHEAD) + (files_per_part * 3) + + +def test_drop_table(cluster): + node = list(cluster.instances.values())[0] + node2 = list(cluster.instances.values())[1] + node.query( + "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') order by n partition by n % 99 settings storage_policy='s3'" + ) + node2.query( + "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '2') order by n partition by n % 99 settings storage_policy='s3'" + ) + node.query("insert into test_drop_table select * from numbers(1000)") + node2.query("system sync replica test_drop_table") + + with PartitionManager() as pm: + pm._add_rule( + { + "probability": 0.01, + "destination": node.ip_address, + "source_port": 2181, + "action": "REJECT --reject-with tcp-reset", + } + ) + pm._add_rule( + { + "probability": 0.01, + "source": node.ip_address, + "destination_port": 2181, + "action": "REJECT --reject-with tcp-reset", + } + ) + node.query("drop table test_drop_table") + for i in range(0, 100): + node.query_and_get_answer_with_error( + "create table if not exists test_drop_table (n int) " + "engine=ReplicatedMergeTree('/test/drop_table', '1') " + "order by n partition by n % 99 settings storage_policy='s3'" + ) + time.sleep(0.2) + + replicas = node.query_with_retry( + "select name from system.zookeeper where path='/test/drop_table/replicas'" + ) + if "1" in replicas and "test_drop_table" not in node.query("show tables"): + node2.query("system drop replica '1' from table test_drop_table") + node.query( + "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " + "order by n partition by n % 99 settings storage_policy='s3'" + ) + node.query("system sync replica test_drop_table", settings={"receive_timeout": 60}) + node2.query("drop table test_drop_table") + assert "1000\t499500\n" == node.query( + "select count(n), sum(n) from test_drop_table" + ) From dce8dca4e5f6d0391fd52e287aea42a09645bfa7 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Mon, 13 Feb 2023 23:53:15 +0000 Subject: [PATCH 065/445] Automatic style fix --- tests/integration/helpers/cluster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index 0c6b60ee166..f6445f07482 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -3263,7 +3263,7 @@ class ClickHouseInstance: sleep_time=0.5, check_callback=lambda x: True, ): - #logging.debug(f"Executing query {sql} on {self.name}") + # logging.debug(f"Executing query {sql} on {self.name}") result = None for i in range(retry_count): try: @@ -3282,7 +3282,7 @@ class ClickHouseInstance: return result time.sleep(sleep_time) except Exception as ex: - #logging.debug("Retry {} got exception {}".format(i + 1, ex)) + # logging.debug("Retry {} got exception {}".format(i + 1, ex)) time.sleep(sleep_time) if result is not None: From bbf94a2664be9b5ba3bdc5f10ccde150cae08f34 Mon Sep 17 00:00:00 2001 From: lzydmxy <13126752315@163.com> Date: Tue, 14 Feb 2023 17:29:44 +0800 Subject: [PATCH 066/445] Apply `ALTER TABLE table_name ON CLUSTER cluster MOVE PARTITION|PART partition_expr TO DISK|VOLUME 'disk_name'` to all replicas. Because `ALTER TABLE t MOVE` is not replicated. --- src/Interpreters/DDLWorker.cpp | 7 +++---- src/Parsers/ASTAlterQuery.cpp | 18 ++++++++++++++++++ src/Parsers/ASTAlterQuery.h | 2 ++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Interpreters/DDLWorker.cpp b/src/Interpreters/DDLWorker.cpp index 0f91212e6a9..54d1067de31 100644 --- a/src/Interpreters/DDLWorker.cpp +++ b/src/Interpreters/DDLWorker.cpp @@ -691,10 +691,9 @@ bool DDLWorker::taskShouldBeExecutedOnLeader(const ASTPtr & ast_ddl, const Stora if (auto * alter = ast_ddl->as()) { // Setting alters should be executed on all replicas - if (alter->isSettingsAlter()) - return false; - - if (alter->isFreezeAlter()) + if (alter->isSettingsAlter() || + alter->isFreezeAlter() || + alter->isMovePartitionToDiskOrVolumeAlter()) return false; } diff --git a/src/Parsers/ASTAlterQuery.cpp b/src/Parsers/ASTAlterQuery.cpp index 5d347446d37..426b63a9d28 100644 --- a/src/Parsers/ASTAlterQuery.cpp +++ b/src/Parsers/ASTAlterQuery.cpp @@ -533,6 +533,24 @@ bool ASTAlterQuery::isDropPartitionAlter() const return isOneCommandTypeOnly(ASTAlterCommand::DROP_PARTITION) || isOneCommandTypeOnly(ASTAlterCommand::DROP_DETACHED_PARTITION); } +bool ASTAlterQuery::isMovePartitionToDiskOrVolumeAlter() const +{ + if (command_list) + { + if (command_list->children.empty()) + return false; + for (const auto & child : command_list->children) + { + const auto & command = child->as(); + if (command.type != ASTAlterCommand::MOVE_PARTITION || + (command.move_destination_type != DataDestinationType::DISK && command.move_destination_type != DataDestinationType::VOLUME)) + return false; + } + return true; + } + return false; +} + /** Get the text that identifies this element. */ String ASTAlterQuery::getID(char delim) const diff --git a/src/Parsers/ASTAlterQuery.h b/src/Parsers/ASTAlterQuery.h index 4a8c9c14ea9..2a48f5bbd9e 100644 --- a/src/Parsers/ASTAlterQuery.h +++ b/src/Parsers/ASTAlterQuery.h @@ -239,6 +239,8 @@ public: bool isDropPartitionAlter() const; + bool isMovePartitionToDiskOrVolumeAlter() const; + String getID(char) const override; ASTPtr clone() const override; From d4518281bbefe830c2d964f4dc35133c536d5850 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 14 Feb 2023 17:03:04 +0000 Subject: [PATCH 067/445] test more headers --- .../02564_query_id_header.reference | 27 +++++++++++++++---- .../0_stateless/02564_query_id_header.sh | 27 +++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/tests/queries/0_stateless/02564_query_id_header.reference b/tests/queries/0_stateless/02564_query_id_header.reference index 655f9fa9eaa..413e8929f36 100644 --- a/tests/queries/0_stateless/02564_query_id_header.reference +++ b/tests/queries/0_stateless/02564_query_id_header.reference @@ -1,5 +1,22 @@ -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id -X-ClickHouse-Query-Id +CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory +< Content-Type: text/plain; charset=UTF-8 +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +INSERT INTO t_query_id_header VALUES (1) +< Content-Type: text/plain; charset=UTF-8 +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +EXISTS TABLE t_query_id_header +< Content-Type: text/tab-separated-values; charset=UTF-8 +< X-ClickHouse-Format: TabSeparated +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +SELECT * FROM t_query_id_header +< Content-Type: text/tab-separated-values; charset=UTF-8 +< X-ClickHouse-Format: TabSeparated +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone +DROP TABLE t_query_id_header +< Content-Type: text/plain; charset=UTF-8 +< X-ClickHouse-Query-Id: query_id +< X-ClickHouse-Timezone: timezone diff --git a/tests/queries/0_stateless/02564_query_id_header.sh b/tests/queries/0_stateless/02564_query_id_header.sh index 440182cd243..67ddbcfcc46 100755 --- a/tests/queries/0_stateless/02564_query_id_header.sh +++ b/tests/queries/0_stateless/02564_query_id_header.sh @@ -4,10 +4,27 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh +CLICKHOUSE_TIMEZONE_ESCAPED=$($CLICKHOUSE_CLIENT --query="SELECT timezone()" | sed 's/[]\/$*.^+:()[]/\\&/g') + +function run_and_check_headers() +{ + query=$1 + query_id="${CLICKHOUSE_DATABASE}_${RANDOM}" + + echo "$query" + + ${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}&query_id=$query_id" -d "$1" 2>&1 \ + | grep -e "< X-ClickHouse-Query-Id" -e "< X-ClickHouse-Timezone" -e "< X-ClickHouse-Format" -e "< Content-Type" \ + | sed "s/$CLICKHOUSE_TIMEZONE_ESCAPED/timezone/" \ + | sed "s/$query_id/query_id/" \ + | sed "s/\r$//" \ + | sort +} + ${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS t_query_id_header" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "INSERT INTO t_query_id_header VALUES (1)" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "EXISTS TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "SELECT * FROM t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" -${CLICKHOUSE_CURL} -sS -v "${CLICKHOUSE_URL}" -d "DROP TABLE t_query_id_header" 2>&1 | grep -o "X-ClickHouse-Query-Id" +run_and_check_headers "CREATE TABLE t_query_id_header (a UInt64) ENGINE = Memory" +run_and_check_headers "INSERT INTO t_query_id_header VALUES (1)" +run_and_check_headers "EXISTS TABLE t_query_id_header" +run_and_check_headers "SELECT * FROM t_query_id_header" +run_and_check_headers "DROP TABLE t_query_id_header" From ff5023049b0b8ea72c1ee911e6cd4c46a01e0242 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Feb 2023 02:17:02 +0300 Subject: [PATCH 068/445] Update test.py --- .../test_replicated_merge_tree_s3_zero_copy/test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 434957de53f..158d559d4b9 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -174,10 +174,10 @@ def test_drop_table(cluster): ) if "1" in replicas and "test_drop_table" not in node.query("show tables"): node2.query("system drop replica '1' from table test_drop_table") - node.query( - "create table test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " - "order by n partition by n % 99 settings storage_policy='s3'" - ) + node.query( + "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " + "order by n partition by n % 99 settings storage_policy='s3'" + ) node.query("system sync replica test_drop_table", settings={"receive_timeout": 60}) node2.query("drop table test_drop_table") assert "1000\t499500\n" == node.query( From 9fd2226c4c5395baf8f9fdea0eced26959ba7a62 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 15 Feb 2023 15:13:04 +0100 Subject: [PATCH 069/445] Update NativeReader.h --- src/Formats/NativeReader.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Formats/NativeReader.h b/src/Formats/NativeReader.h index 64d3e4d6df0..2d8b16e06eb 100644 --- a/src/Formats/NativeReader.h +++ b/src/Formats/NativeReader.h @@ -49,9 +49,9 @@ private: ReadBuffer & istr; Block header; UInt64 server_revision; - bool skip_unknown_columns; - bool null_as_default; - BlockMissingValues * block_missing_values; + bool skip_unknown_columns = false; + bool null_as_default = false; + BlockMissingValues * block_missing_values = nullptr; bool use_index = false; IndexForNativeFormat::Blocks::const_iterator index_block_it; From 885f14311c2ba31bb40e714a47e1f088bb204429 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Feb 2023 16:37:44 +0100 Subject: [PATCH 070/445] fix --- src/Storages/StorageReplicatedMergeTree.cpp | 4 ++++ .../configs/config.d/storage_conf.xml | 7 +++++++ .../test_replicated_merge_tree_s3_zero_copy/test.py | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index c92d4efab0d..4a8f38da349 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -928,7 +928,11 @@ void StorageReplicatedMergeTree::drop() dropAllData(); if (maybe_has_metadata_in_zookeeper) + { + /// Session could expire, get it again + zookeeper = getZooKeeperIfTableShutDown(); dropReplica(zookeeper, zookeeper_path, replica_name, log, getSettings(), &has_metadata_in_zookeeper); + } } void StorageReplicatedMergeTree::dropReplica(zkutil::ZooKeeperPtr zookeeper, const String & zookeeper_path, const String & replica, diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml index 15239041478..801e2072f17 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml @@ -55,4 +55,11 @@ 3 0 + + + system + zookeeper_log
+ 5000 + 5000 +
diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index 158d559d4b9..f955b0cb754 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -178,7 +178,7 @@ def test_drop_table(cluster): "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " "order by n partition by n % 99 settings storage_policy='s3'" ) - node.query("system sync replica test_drop_table", settings={"receive_timeout": 60}) + node.query_with_retry("system sync replica test_drop_table", settings={"receive_timeout": 10}, retry_count=5) node2.query("drop table test_drop_table") assert "1000\t499500\n" == node.query( "select count(n), sum(n) from test_drop_table" From 69463f852e6b770d69357ca7ac57f2f5b6ec3a66 Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Wed, 15 Feb 2023 15:44:19 +0000 Subject: [PATCH 071/445] Automatic style fix --- .../test_replicated_merge_tree_s3_zero_copy/test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index f955b0cb754..fb93dc8aa7c 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -178,7 +178,11 @@ def test_drop_table(cluster): "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " "order by n partition by n % 99 settings storage_policy='s3'" ) - node.query_with_retry("system sync replica test_drop_table", settings={"receive_timeout": 10}, retry_count=5) + node.query_with_retry( + "system sync replica test_drop_table", + settings={"receive_timeout": 10}, + retry_count=5, + ) node2.query("drop table test_drop_table") assert "1000\t499500\n" == node.query( "select count(n), sum(n) from test_drop_table" From ac62356477c87a84142609afa2e315a1ce81f4cd Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 16 Feb 2023 13:05:13 +0100 Subject: [PATCH 072/445] fix --- .../configs/config.d/storage_conf.xml | 7 ------- .../test.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml index 801e2072f17..15239041478 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/configs/config.d/storage_conf.xml @@ -55,11 +55,4 @@ 3 0 - - - system - zookeeper_log
- 5000 - 5000 -
diff --git a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py index fb93dc8aa7c..f0bc12e3125 100644 --- a/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py +++ b/tests/integration/test_replicated_merge_tree_s3_zero_copy/test.py @@ -160,7 +160,11 @@ def test_drop_table(cluster): "action": "REJECT --reject-with tcp-reset", } ) + + # Will drop in background with retries node.query("drop table test_drop_table") + + # It should not be possible to create a replica with the same path until the previous one is completely dropped for i in range(0, 100): node.query_and_get_answer_with_error( "create table if not exists test_drop_table (n int) " @@ -169,11 +173,21 @@ def test_drop_table(cluster): ) time.sleep(0.2) + # Wait for drop to actually finish + node.wait_for_log_line( + "Removing metadata /var/lib/clickhouse/metadata_dropped/default.test_drop_table", + timeout=60, + look_behind_lines=1000000, + ) + + # It could leave some leftovers, remove them replicas = node.query_with_retry( "select name from system.zookeeper where path='/test/drop_table/replicas'" ) if "1" in replicas and "test_drop_table" not in node.query("show tables"): node2.query("system drop replica '1' from table test_drop_table") + + # Just in case table was not created due to connection errors node.query( "create table if not exists test_drop_table (n int) engine=ReplicatedMergeTree('/test/drop_table', '1') " "order by n partition by n % 99 settings storage_policy='s3'" From 3a635e428a48b93d4b99f52a263f8ad6a58d71e0 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Thu, 16 Feb 2023 11:03:41 -0800 Subject: [PATCH 073/445] Fix xxhash endian issue for s390x --- src/Functions/FunctionsHashing.h | 106 ++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index 69c3a299eea..6bf1a2db3ac 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -55,7 +55,7 @@ #include #include #include - +#include namespace DB { @@ -1025,17 +1025,58 @@ private: if constexpr (Impl::use_int_hash_for_pods) { - if constexpr (std::is_same_v) - h = IntHash64Impl::apply(bit_cast(vec_from[i])); + if constexpr (std::endian::native == std::endian::little) + { + if constexpr (std::is_same_v) + h = IntHash64Impl::apply(bit_cast(vec_from[i])); + else + h = IntHash32Impl::apply(bit_cast(vec_from[i])); + } else - h = IntHash32Impl::apply(bit_cast(vec_from[i])); + { + if constexpr (std::is_same_v) + { + UInt64 v = bit_cast(vec_from[i]); + v = __builtin_bswap64(v); + h = IntHash64Impl::apply(v); + } + else + { + UInt32 v = bit_cast(vec_from[i]); + v = __builtin_bswap32(v); + h = IntHash32Impl::apply(v); + } + } } else { - if (std::is_same_v) - h = JavaHashImpl::apply(vec_from[i]); + if constexpr (std::endian::native == std::endian::little) + { + if (std::is_same_v) + h = JavaHashImpl::apply(vec_from[i]); + else + h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); + } else - h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); + { + if (std::is_same_v) + h = JavaHashImpl::apply(vec_from[i]); + else + { + if constexpr (std::is_same_v) + { + UInt64 v = bit_cast(vec_from[i]); + v = __builtin_bswap64(v); + h = apply(key, reinterpret_cast(&v), sizeof(vec_from[i])); + } + else + { + UInt32 v = bit_cast(vec_from[i]); + v = __builtin_bswap32(v); + h = apply(key, reinterpret_cast(&v), sizeof(vec_from[i])); + } + } + } } if constexpr (first) @@ -1048,11 +1089,28 @@ private: { auto value = col_from_const->template getValue(); ToType hash; - if constexpr (std::is_same_v) - hash = IntHash64Impl::apply(bit_cast(value)); + if constexpr (std::endian::native == std::endian::little) + { + if constexpr (std::is_same_v) + hash = IntHash64Impl::apply(bit_cast(value)); + else + hash = IntHash32Impl::apply(bit_cast(value)); + } else - hash = IntHash32Impl::apply(bit_cast(value)); - + { + if constexpr (std::is_same_v) + { + UInt64 v = bit_cast(value); + v = __builtin_bswap64(v); + hash = IntHash64Impl::apply(v); + } + else + { + UInt32 v = bit_cast(value); + v = __builtin_bswap32(v); + hash = IntHash32Impl::apply(bit_cast(v)); + } + } size_t size = vec_to.size(); if constexpr (first) { @@ -1080,8 +1138,17 @@ private: size_t size = vec_from.size(); for (size_t i = 0; i < size; ++i) { - ToType h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); - + ToType h; + if constexpr (std::endian::native == std::endian::little) + { + h = apply(key, reinterpret_cast(&vec_from[i]), sizeof(vec_from[i])); + } + else + { + char tmp_buffer[sizeof(vec_from[i])]; + reverseMemcpy(tmp_buffer, &vec_from[i], sizeof(vec_from[i])); + h = apply(key, reinterpret_cast(tmp_buffer), sizeof(vec_from[i])); + } if constexpr (first) vec_to[i] = h; else @@ -1092,8 +1159,17 @@ private: { auto value = col_from_const->template getValue(); - ToType h = apply(key, reinterpret_cast(&value), sizeof(value)); - + ToType h; + if constexpr (std::endian::native == std::endian::little) + { + h = apply(key, reinterpret_cast(&value), sizeof(value)); + } + else + { + char tmp_buffer[sizeof(value)]; + reverseMemcpy(tmp_buffer, &value, sizeof(value)); + h = apply(key, reinterpret_cast(tmp_buffer), sizeof(value)); + } size_t size = vec_to.size(); if constexpr (first) { From 1a352b9021895c63946544301bff1011b7ba8ba0 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 17 Feb 2023 10:21:52 +0100 Subject: [PATCH 074/445] Fix flakiness of test_backup_restore_on_cluster/test_disallow_concurrency The problem is that if you will run first test_concurrent_restores_on_different_node and after test_concurrent_restores_on_same_node, like in [1], the first one will leave the RESTORE and the second will fail. [1]: https://s3.amazonaws.com/clickhouse-test-reports/45282/5af2967f65c3293b277872a002cb5d570200c008/integration_tests__asan__[3/6].html Signed-off-by: Azat Khuzhin --- .../test_disallow_concurrency.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py index 43e7682ec1d..d689e44e4d4 100644 --- a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py +++ b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py @@ -229,3 +229,9 @@ def test_concurrent_restores_on_different_node(): assert "Concurrent restores not supported" in nodes[1].query_and_get_error( f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name}" ) + + assert_eq_with_retry( + nodes[0], + f"SELECT status FROM system.backups WHERE status == 'RESTORED'", + "RESTORED", + ) From 4496db66b2423fc331668f9b25077cc84e262241 Mon Sep 17 00:00:00 2001 From: pufit Date: Fri, 17 Feb 2023 23:46:09 -0500 Subject: [PATCH 075/445] Implement `system.server_settings` --- programs/server/Server.cpp | 175 +++++++----------- src/Core/ServerSettings.cpp | 19 ++ src/Core/ServerSettings.h | 78 ++++++++ src/Core/SettingsFields.cpp | 6 + src/Core/SettingsFields.h | 6 + .../System/StorageSystemServerSettings.cpp | 37 ++++ .../System/StorageSystemServerSettings.h | 27 +++ src/Storages/System/attachSystemTables.cpp | 2 + 8 files changed, 247 insertions(+), 103 deletions(-) create mode 100644 src/Core/ServerSettings.cpp create mode 100644 src/Core/ServerSettings.h create mode 100644 src/Storages/System/StorageSystemServerSettings.cpp create mode 100644 src/Storages/System/StorageSystemServerSettings.h diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index b97b48d9c68..5a62bb88427 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -92,6 +92,7 @@ #include #include #include +#include #include #include @@ -663,7 +664,10 @@ try MainThreadStatus::getInstance(); - StackTrace::setShowAddresses(config().getBool("show_addresses_in_stack_traces", true)); + ServerSettings server_settings; + server_settings.loadSettingsFromConfig(config()); + + StackTrace::setShowAddresses(server_settings.show_addresses_in_stack_traces); #if USE_HDFS /// This will point libhdfs3 to the right location for its config. @@ -748,9 +752,9 @@ try // nodes (`from_zk`), because ZooKeeper interface uses the pool. We will // ignore `max_thread_pool_size` in configs we fetch from ZK, but oh well. GlobalThreadPool::initialize( - config().getUInt("max_thread_pool_size", 10000), - config().getUInt("max_thread_pool_free_size", 1000), - config().getUInt("thread_pool_queue_size", 10000)); + server_settings.max_thread_pool_size, + server_settings.max_thread_pool_free_size, + server_settings.thread_pool_queue_size); #if USE_AZURE_BLOB_STORAGE /// It makes sense to deinitialize libxml after joining of all threads @@ -766,9 +770,9 @@ try #endif IOThreadPool::initialize( - config().getUInt("max_io_thread_pool_size", 100), - config().getUInt("max_io_thread_pool_free_size", 0), - config().getUInt("io_thread_pool_queue_size", 10000)); + server_settings.max_io_thread_pool_size, + server_settings.max_io_thread_pool_free_size, + server_settings.io_thread_pool_queue_size); NamedCollectionUtils::loadFromConfig(config()); @@ -786,15 +790,15 @@ try } } - Poco::ThreadPool server_pool(3, config().getUInt("max_connections", 1024)); + Poco::ThreadPool server_pool(3, server_settings.max_connections); std::mutex servers_lock; std::vector servers; std::vector servers_to_start_before_tables; /// This object will periodically calculate some metrics. ServerAsynchronousMetrics async_metrics( global_context, - config().getUInt("asynchronous_metrics_update_period_s", 1), - config().getUInt("asynchronous_heavy_metrics_update_period_s", 120), + server_settings.asynchronous_metrics_update_period_s, + server_settings.asynchronous_heavy_metrics_update_period_s, [&]() -> std::vector { std::vector metrics; @@ -809,7 +813,7 @@ try } ); - ConnectionCollector::init(global_context, config().getUInt("max_threads_for_connection_collector", 10)); + ConnectionCollector::init(global_context, server_settings.max_threads_for_connection_collector); bool has_zookeeper = config().has("zookeeper"); @@ -828,6 +832,9 @@ try Settings::checkNoSettingNamesAtTopLevel(config(), config_path); + /// We need to reload server settings because config could be updated via zookeeper. + server_settings.loadSettingsFromConfig(config()); + #if defined(OS_LINUX) std::string executable_path = getExecutablePath(); @@ -947,7 +954,7 @@ try std::string path_str = getCanonicalPath(config().getString("path", DBMS_DEFAULT_PATH)); fs::path path = path_str; - std::string default_database = config().getString("default_database", "default"); + std::string default_database = server_settings.default_database.toString(); /// Check that the process user id matches the owner of the data. const auto effective_user_id = geteuid(); @@ -1038,21 +1045,18 @@ try LOG_TRACE(log, "Initialized DateLUT with time zone '{}'.", DateLUT::instance().getTimeZone()); /// Storage with temporary data for processing of heavy queries. - if (auto temporary_policy = config().getString("tmp_policy", ""); !temporary_policy.empty()) + if (!server_settings.tmp_policy.value.empty()) { - size_t max_size = config().getUInt64("max_temporary_data_on_disk_size", 0); - global_context->setTemporaryStoragePolicy(temporary_policy, max_size); + global_context->setTemporaryStoragePolicy(server_settings.tmp_policy, server_settings.max_temporary_data_on_disk_size); } - else if (auto temporary_cache = config().getString("temporary_data_in_cache", ""); !temporary_cache.empty()) + else if (!server_settings.temporary_data_in_cache.value.empty()) { - size_t max_size = config().getUInt64("max_temporary_data_on_disk_size", 0); - global_context->setTemporaryStorageInCache(temporary_cache, max_size); + global_context->setTemporaryStorageInCache(server_settings.temporary_data_in_cache, server_settings.max_temporary_data_on_disk_size); } else { std::string temporary_path = config().getString("tmp_path", path / "tmp/"); - size_t max_size = config().getUInt64("max_temporary_data_on_disk_size", 0); - global_context->setTemporaryStoragePath(temporary_path, max_size); + global_context->setTemporaryStoragePath(temporary_path, server_settings.max_temporary_data_on_disk_size); } /** Directory with 'flags': files indicating temporary settings for the server set by system administrator. @@ -1189,10 +1193,12 @@ try { Settings::checkNoSettingNamesAtTopLevel(*config, config_path); - /// Limit on total memory usage - size_t max_server_memory_usage = config->getUInt64("max_server_memory_usage", 0); + ServerSettings server_settings; + server_settings.loadSettingsFromConfig(*config); - double max_server_memory_usage_to_ram_ratio = config->getDouble("max_server_memory_usage_to_ram_ratio", 0.9); + size_t max_server_memory_usage = server_settings.max_server_memory_usage; + + double max_server_memory_usage_to_ram_ratio = server_settings.max_server_memory_usage_to_ram_ratio; size_t default_max_server_memory_usage = static_cast(memory_amount * max_server_memory_usage_to_ram_ratio); if (max_server_memory_usage == 0) @@ -1220,8 +1226,7 @@ try total_memory_tracker.setDescription("(total)"); total_memory_tracker.setMetric(CurrentMetrics::MemoryTracking); - bool allow_use_jemalloc_memory = config->getBool("allow_use_jemalloc_memory", true); - total_memory_tracker.setAllowUseJemallocMemory(allow_use_jemalloc_memory); + total_memory_tracker.setAllowUseJemallocMemory(server_settings.allow_use_jemalloc_memory); auto * global_overcommit_tracker = global_context->getGlobalOvercommitTracker(); total_memory_tracker.setOvercommitTracker(global_overcommit_tracker); @@ -1239,36 +1244,23 @@ try global_context->setRemoteHostFilter(*config); - /// Setup protection to avoid accidental DROP for big tables (that are greater than 50 GB by default) - if (config->has("max_table_size_to_drop")) - global_context->setMaxTableSizeToDrop(config->getUInt64("max_table_size_to_drop")); - - if (config->has("max_partition_size_to_drop")) - global_context->setMaxPartitionSizeToDrop(config->getUInt64("max_partition_size_to_drop")); + global_context->setMaxTableSizeToDrop(server_settings.max_table_size_to_drop); + global_context->setMaxPartitionSizeToDrop(server_settings.max_partition_size_to_drop); ConcurrencyControl::SlotCount concurrent_threads_soft_limit = ConcurrencyControl::Unlimited; - if (config->has("concurrent_threads_soft_limit_num")) + if (server_settings.concurrent_threads_soft_limit_num > 0 && server_settings.concurrent_threads_soft_limit_num < concurrent_threads_soft_limit) + concurrent_threads_soft_limit = server_settings.concurrent_threads_soft_limit_num; + if (server_settings.concurrent_threads_soft_limit_ratio_to_cores > 0) { - auto value = config->getUInt64("concurrent_threads_soft_limit_num", 0); - if (value > 0 && value < concurrent_threads_soft_limit) - concurrent_threads_soft_limit = value; - } - if (config->has("concurrent_threads_soft_limit_ratio_to_cores")) - { - auto value = config->getUInt64("concurrent_threads_soft_limit_ratio_to_cores", 0) * std::thread::hardware_concurrency(); + auto value = server_settings.concurrent_threads_soft_limit_ratio_to_cores * std::thread::hardware_concurrency(); if (value > 0 && value < concurrent_threads_soft_limit) concurrent_threads_soft_limit = value; } ConcurrencyControl::instance().setMaxConcurrency(concurrent_threads_soft_limit); - if (config->has("max_concurrent_queries")) - global_context->getProcessList().setMaxSize(config->getInt("max_concurrent_queries", 0)); - - if (config->has("max_concurrent_insert_queries")) - global_context->getProcessList().setMaxInsertQueriesAmount(config->getInt("max_concurrent_insert_queries", 0)); - - if (config->has("max_concurrent_select_queries")) - global_context->getProcessList().setMaxSelectQueriesAmount(config->getInt("max_concurrent_select_queries", 0)); + global_context->getProcessList().setMaxSize(server_settings.max_concurrent_queries); + global_context->getProcessList().setMaxInsertQueriesAmount(server_settings.max_concurrent_insert_queries); + global_context->getProcessList().setMaxSelectQueriesAmount(server_settings.max_concurrent_select_queries); if (config->has("keeper_server")) global_context->updateKeeperConfiguration(*config); @@ -1277,56 +1269,43 @@ try /// Note: If you specified it in the top level config (not it config of default profile) /// then ClickHouse will use it exactly. /// This is done for backward compatibility. - if (global_context->areBackgroundExecutorsInitialized() && (config->has("background_pool_size") || config->has("background_merges_mutations_concurrency_ratio"))) + if (global_context->areBackgroundExecutorsInitialized() && (server_settings.background_pool_size.changed || server_settings.background_merges_mutations_concurrency_ratio.changed)) { - auto new_pool_size = config->getUInt64("background_pool_size", 16); - auto new_ratio = config->getUInt64("background_merges_mutations_concurrency_ratio", 2); + auto new_pool_size = server_settings.background_pool_size; + auto new_ratio = server_settings.background_merges_mutations_concurrency_ratio; global_context->getMergeMutateExecutor()->increaseThreadsAndMaxTasksCount(new_pool_size, new_pool_size * new_ratio); - auto new_scheduling_policy = config->getString("background_merges_mutations_scheduling_policy", "round_robin"); - global_context->getMergeMutateExecutor()->updateSchedulingPolicy(new_scheduling_policy); + global_context->getMergeMutateExecutor()->updateSchedulingPolicy(server_settings.background_merges_mutations_scheduling_policy.toString()); } - if (global_context->areBackgroundExecutorsInitialized() && config->has("background_move_pool_size")) + if (global_context->areBackgroundExecutorsInitialized() && server_settings.background_move_pool_size.changed) { - auto new_pool_size = config->getUInt64("background_move_pool_size"); + auto new_pool_size = server_settings.background_move_pool_size; global_context->getMovesExecutor()->increaseThreadsAndMaxTasksCount(new_pool_size, new_pool_size); } - if (global_context->areBackgroundExecutorsInitialized() && config->has("background_fetches_pool_size")) + if (global_context->areBackgroundExecutorsInitialized() && server_settings.background_fetches_pool_size.changed) { - auto new_pool_size = config->getUInt64("background_fetches_pool_size"); + auto new_pool_size = server_settings.background_fetches_pool_size; global_context->getFetchesExecutor()->increaseThreadsAndMaxTasksCount(new_pool_size, new_pool_size); } - if (global_context->areBackgroundExecutorsInitialized() && config->has("background_common_pool_size")) + if (global_context->areBackgroundExecutorsInitialized() && server_settings.background_common_pool_size.changed) { - auto new_pool_size = config->getUInt64("background_common_pool_size"); + auto new_pool_size = server_settings.background_common_pool_size; global_context->getCommonExecutor()->increaseThreadsAndMaxTasksCount(new_pool_size, new_pool_size); } - if (config->has("background_buffer_flush_schedule_pool_size")) - { - auto new_pool_size = config->getUInt64("background_buffer_flush_schedule_pool_size"); - global_context->getBufferFlushSchedulePool().increaseThreadsCount(new_pool_size); - } + if (server_settings.background_buffer_flush_schedule_pool_size.changed) + global_context->getBufferFlushSchedulePool().increaseThreadsCount(server_settings.background_buffer_flush_schedule_pool_size); - if (config->has("background_schedule_pool_size")) - { - auto new_pool_size = config->getUInt64("background_schedule_pool_size"); - global_context->getSchedulePool().increaseThreadsCount(new_pool_size); - } + if (server_settings.background_schedule_pool_size.changed) + global_context->getSchedulePool().increaseThreadsCount(server_settings.background_schedule_pool_size); - if (config->has("background_message_broker_schedule_pool_size")) - { - auto new_pool_size = config->getUInt64("background_message_broker_schedule_pool_size"); - global_context->getMessageBrokerSchedulePool().increaseThreadsCount(new_pool_size); - } + if (server_settings.background_message_broker_schedule_pool_size.changed) + global_context->getMessageBrokerSchedulePool().increaseThreadsCount(server_settings.background_message_broker_schedule_pool_size); - if (config->has("background_distributed_schedule_pool_size")) - { - auto new_pool_size = config->getUInt64("background_distributed_schedule_pool_size"); - global_context->getDistributedSchedulePool().increaseThreadsCount(new_pool_size); - } + if (server_settings.background_distributed_schedule_pool_size.changed) + global_context->getDistributedSchedulePool().increaseThreadsCount(server_settings.background_distributed_schedule_pool_size); if (config->has("resources")) { @@ -1471,18 +1450,15 @@ try }); /// Limit on total number of concurrently executed queries. - global_context->getProcessList().setMaxSize(config().getInt("max_concurrent_queries", 0)); + global_context->getProcessList().setMaxSize(server_settings.max_concurrent_queries); /// Set up caches. - /// Lower cache size on low-memory systems. - double cache_size_to_ram_max_ratio = config().getDouble("cache_size_to_ram_max_ratio", 0.5); - size_t max_cache_size = static_cast(memory_amount * cache_size_to_ram_max_ratio); + size_t max_cache_size = static_cast(memory_amount * server_settings.cache_size_to_ram_max_ratio); - /// Size of cache for uncompressed blocks. Zero means disabled. - String uncompressed_cache_policy = config().getString("uncompressed_cache_policy", "SLRU"); + String uncompressed_cache_policy = server_settings.uncompressed_cache_policy; LOG_INFO(log, "Uncompressed cache policy name {}", uncompressed_cache_policy); - size_t uncompressed_cache_size = config().getUInt64("uncompressed_cache_size", 0); + size_t uncompressed_cache_size = server_settings.uncompressed_cache_size; if (uncompressed_cache_size > max_cache_size) { uncompressed_cache_size = max_cache_size; @@ -1504,9 +1480,8 @@ try global_context, settings.async_insert_threads)); - /// Size of cache for marks (index of MergeTree family of tables). - size_t mark_cache_size = config().getUInt64("mark_cache_size", 5368709120); - String mark_cache_policy = config().getString("mark_cache_policy", "SLRU"); + size_t mark_cache_size = server_settings.mark_cache_size; + String mark_cache_policy = server_settings.mark_cache_policy; if (!mark_cache_size) LOG_ERROR(log, "Too low mark cache size will lead to severe performance degradation."); if (mark_cache_size > max_cache_size) @@ -1517,20 +1492,14 @@ try } global_context->setMarkCache(mark_cache_size, mark_cache_policy); - /// Size of cache for uncompressed blocks of MergeTree indices. Zero means disabled. - size_t index_uncompressed_cache_size = config().getUInt64("index_uncompressed_cache_size", 0); - if (index_uncompressed_cache_size) - global_context->setIndexUncompressedCache(index_uncompressed_cache_size); + if (server_settings.index_uncompressed_cache_size) + global_context->setIndexUncompressedCache(server_settings.index_uncompressed_cache_size); - /// Size of cache for index marks (index of MergeTree skip indices). - size_t index_mark_cache_size = config().getUInt64("index_mark_cache_size", 0); - if (index_mark_cache_size) - global_context->setIndexMarkCache(index_mark_cache_size); + if (server_settings.index_mark_cache_size) + global_context->setIndexMarkCache(server_settings.index_mark_cache_size); - /// A cache for mmapped files. - size_t mmap_cache_size = config().getUInt64("mmap_cache_size", 1000); /// The choice of default is arbitrary. - if (mmap_cache_size) - global_context->setMMappedFileCache(mmap_cache_size); + if (server_settings.mmap_cache_size) + global_context->setMMappedFileCache(server_settings.mmap_cache_size); /// A cache for query results. global_context->setQueryCache(config()); @@ -1616,7 +1585,7 @@ try /// context is destroyed. /// In addition this object has to be created before the loading of the tables. std::unique_ptr dns_cache_updater; - if (config().has("disable_internal_dns_cache") && config().getInt("disable_internal_dns_cache")) + if (server_settings.disable_internal_dns_cache) { /// Disable DNS caching at all DNSResolver::instance().setDisableCacheFlag(); @@ -1626,7 +1595,7 @@ try { /// Initialize a watcher periodically updating DNS cache dns_cache_updater = std::make_unique( - global_context, config().getInt("dns_cache_update_period", 15), config().getUInt("dns_max_consecutive_failures", 5)); + global_context, server_settings.dns_cache_update_period, server_settings.dns_max_consecutive_failures); } if (dns_cache_updater) @@ -1890,7 +1859,7 @@ try LOG_INFO(log, "Closed all listening sockets."); /// Killing remaining queries. - if (!config().getBool("shutdown_wait_unfinished_queries", false)) + if (server_settings.shutdown_wait_unfinished_queries) global_context->getProcessList().killAllQueries(); if (current_connections) diff --git a/src/Core/ServerSettings.cpp b/src/Core/ServerSettings.cpp new file mode 100644 index 00000000000..c50a67b04c9 --- /dev/null +++ b/src/Core/ServerSettings.cpp @@ -0,0 +1,19 @@ +#include "ServerSettings.h" +#include + +namespace DB +{ + +IMPLEMENT_SETTINGS_TRAITS(ServerSettingsTraits, SERVER_SETTINGS) + +void ServerSettings::loadSettingsFromConfig(const Poco::Util::AbstractConfiguration & config) +{ + for (auto setting : all()) + { + const auto & name = setting.getName(); + if (config.has(name)) + set(name, config.getString(name)); + } +} + +} diff --git a/src/Core/ServerSettings.h b/src/Core/ServerSettings.h new file mode 100644 index 00000000000..d597cdf1c66 --- /dev/null +++ b/src/Core/ServerSettings.h @@ -0,0 +1,78 @@ +#pragma once + + +#include + + +namespace Poco::Util +{ +class AbstractConfiguration; +} + +namespace DB +{ + +#define SERVER_SETTINGS(M, ALIAS) \ + M(Bool, show_addresses_in_stack_traces, true, "If it is set true will show addresses in stack traces", 0) \ + M(Bool, shutdown_wait_unfinished_queries, false, "If set true ClickHouse will wait for running queries finish before shutdown.", 0) \ + M(UInt64, max_thread_pool_size, 10000, "Max size for global thread pool.", 0) \ + M(UInt64, max_thread_pool_free_size, 1000, "Max free size for global thread pool.", 0) \ + M(UInt64, thread_pool_queue_size, 10000, "Queue size for global thread pool.", 0) \ + M(UInt64, max_io_thread_pool_size, 100, "Max size for IO thread pool.", 0) \ + M(UInt64, max_io_thread_pool_free_size, 0, "Max free size for IO thread pool.", 0) \ + M(UInt64, io_thread_pool_queue_size, 10000, "Queue size for IO thread pool.", 0) \ + M(Int32, max_connections, 1024, "Max server connections.", 0) \ + M(UInt32, asynchronous_metrics_update_period_s, 1, "Period in seconds for updating asynchronous metrics.", 0) \ + M(UInt32, asynchronous_heavy_metrics_update_period_s, 120, "Period in seconds for updating asynchronous metrics.", 0) \ + M(UInt32, max_threads_for_connection_collector, 100, "Max thread count for connection collector.", 0) \ + M(String, default_database, "default", "Default database name.", 0) \ + M(String, tmp_policy, "", "Policy for storage with temporary data.", 0) \ + M(UInt64, max_temporary_data_on_disk_size, 0, "Max data size for temporary storage.", 0) \ + M(String, temporary_data_in_cache, "", "Cache disk name for temporary data.", 0) \ + M(UInt64, max_server_memory_usage, 0, "Limit on total memory usage. Zero means Unlimited.", 0) \ + M(Double, max_server_memory_usage_to_ram_ratio, 0.9, "Same as max_server_memory_usage but in to ram ratio. Allows to lower max memory on low-memory systems.", 0) \ + M(Bool, allow_use_jemalloc_memory, true, "Allows to use jemalloc memory.", 0) \ + \ + M(UInt64, max_concurrent_queries, 0, "Limit on total number of concurrently executed queries. Zero means Unlimited.", 0) \ + M(UInt64, max_concurrent_insert_queries, 0, "Limit on total number of concurrently insert queries. Zero means Unlimited.", 0) \ + M(UInt64, max_concurrent_select_queries, 0, "Limit on total number of concurrently select queries. Zero means Unlimited.", 0) \ + \ + M(Double, cache_size_to_ram_max_ratio, 0.5, "Set cache size ro ram max ratio. Allows to lower cache size on low-memory systems.", 0) \ + M(String, uncompressed_cache_policy, "SLRU", "Uncompressed cache policy name.", 0) \ + M(UInt64, uncompressed_cache_size, 0, "Size of cache for uncompressed blocks. Zero means disabled.", 0) \ + M(UInt64, mark_cache_size, 5368709120, "Size of cache for marks (index of MergeTree family of tables).", 0) \ + M(String, mark_cache_policy, "SLRU", "Mark cache policy name.", 0) \ + M(UInt64, index_uncompressed_cache_size, 0, "Size of cache for uncompressed blocks of MergeTree indices. Zero means disabled.", 0) \ + M(UInt64, index_mark_cache_size, 0, "Size of cache for uncompressed blocks of MergeTree indices. Zero means disabled.", 0) \ + M(UInt64, mmap_cache_size, 1000, "A cache for mmapped files.", 0) /* The choice of default is arbitrary. */ \ + \ + M(Bool, disable_internal_dns_cache, false, "Disable internal DNS caching at all.", 0) \ + M(Int32, dns_cache_update_period, 15, "Internal DNS cache update period in seconds.", 0) \ + M(UInt32, dns_max_consecutive_failures, 1024, "Max server connections.", 0) \ + \ + M(UInt64, max_table_size_to_drop, 50000000000lu, "If size of a table is greater than this value (in bytes) than table could not be dropped with any DROP query.", 0) \ + M(UInt64, max_partition_size_to_drop, 50000000000lu, "Same as max_table_size_to_drop, but for the partitions.", 0) \ + M(UInt64, concurrent_threads_soft_limit_num, 0, "Sets how many concurrent thread can be allocated before applying CPU pressure. Zero means Unlimited.", 0) \ + M(UInt64, concurrent_threads_soft_limit_ratio_to_cores, 1024, "Same as concurrent_threads_soft_limit_num, but with ratio to cores.", 0) \ + \ + M(UInt64, background_pool_size, 16, "Sets background pool size.", 0) \ + M(UInt64, background_merges_mutations_concurrency_ratio, 2, "Sets background merges mutations concurrency ratio.", 0) \ + M(String, background_merges_mutations_scheduling_policy, "round_robin", "Sets background merges mutations scheduling policy.", 0) \ + M(UInt64, background_move_pool_size, 8, "Sets background move pool size.", 0) \ + M(UInt64, background_fetches_pool_size, 8, "Sets background fetches pool size.", 0) \ + M(UInt64, background_common_pool_size, 8, "Sets background common pool size.", 0) \ + M(UInt64, background_buffer_flush_schedule_pool_size, 16, "Sets background flush schedule pool size.", 0) \ + M(UInt64, background_schedule_pool_size, 16, "Sets background schedule pool size.", 0) \ + M(UInt64, background_message_broker_schedule_pool_size, 16, "Sets background message broker schedule pool size.", 0) \ + M(UInt64, background_distributed_schedule_pool_size, 16, "Sets background distributed schedule pool size.", 0) \ + + +DECLARE_SETTINGS_TRAITS(ServerSettingsTraits, SERVER_SETTINGS) + +struct ServerSettings : public BaseSettings +{ + void loadSettingsFromConfig(const Poco::Util::AbstractConfiguration & config); +}; + +} + diff --git a/src/Core/SettingsFields.cpp b/src/Core/SettingsFields.cpp index 4164bf1e27e..06cd53013ec 100644 --- a/src/Core/SettingsFields.cpp +++ b/src/Core/SettingsFields.cpp @@ -150,10 +150,16 @@ template struct SettingFieldNumber; template struct SettingFieldNumber; template struct SettingFieldNumber; template struct SettingFieldNumber; +template struct SettingFieldNumber; +template struct SettingFieldNumber; +template struct SettingFieldNumber; template struct SettingAutoWrapper>; template struct SettingAutoWrapper>; template struct SettingAutoWrapper>; +template struct SettingAutoWrapper>; +template struct SettingAutoWrapper>; +template struct SettingAutoWrapper>; namespace { diff --git a/src/Core/SettingsFields.h b/src/Core/SettingsFields.h index c6fe46c9f6b..3994e402c9a 100644 --- a/src/Core/SettingsFields.h +++ b/src/Core/SettingsFields.h @@ -55,7 +55,10 @@ struct SettingFieldNumber using SettingFieldUInt64 = SettingFieldNumber; using SettingFieldInt64 = SettingFieldNumber; +using SettingFieldUInt32 = SettingFieldNumber; +using SettingFieldInt32 = SettingFieldNumber; using SettingFieldFloat = SettingFieldNumber; +using SettingFieldDouble = SettingFieldNumber; using SettingFieldBool = SettingFieldNumber; /** Wraps any SettingField to support special value 'auto' that can be checked with `is_auto` flag. @@ -129,6 +132,9 @@ struct SettingAutoWrapper using SettingFieldUInt64Auto = SettingAutoWrapper; using SettingFieldInt64Auto = SettingAutoWrapper; using SettingFieldFloatAuto = SettingAutoWrapper; +using SettingFieldUInt32Auto = SettingAutoWrapper; +using SettingFieldInt32Auto = SettingAutoWrapper; +using SettingFieldDoubleAuto = SettingAutoWrapper; /* Similar to SettingFieldUInt64Auto with small differences to behave like regular UInt64, supported to compatibility. * When setting to 'auto' it becomes equal to the number of processor cores without taking into account SMT. diff --git a/src/Storages/System/StorageSystemServerSettings.cpp b/src/Storages/System/StorageSystemServerSettings.cpp new file mode 100644 index 00000000000..0e803d720a9 --- /dev/null +++ b/src/Storages/System/StorageSystemServerSettings.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +namespace DB +{ +NamesAndTypesList StorageSystemServerSettings::getNamesAndTypes() +{ + return { + {"name", std::make_shared()}, + {"value", std::make_shared()}, + {"changed", std::make_shared()}, + {"description", std::make_shared()}, + {"type", std::make_shared()}, + }; +} + +void StorageSystemServerSettings::fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo &) const +{ + const auto & config = context->getConfigRef(); + ServerSettings settings; + settings.loadSettingsFromConfig(config); + + for (const auto & setting : settings.all()) + { + const auto & setting_name = setting.getName(); + res_columns[0]->insert(setting_name); + res_columns[1]->insert(setting.getValueString()); + res_columns[2]->insert(setting.isValueChanged()); + res_columns[3]->insert(setting.getDescription()); + res_columns[4]->insert(setting.getTypeName()); + } +} + +} diff --git a/src/Storages/System/StorageSystemServerSettings.h b/src/Storages/System/StorageSystemServerSettings.h new file mode 100644 index 00000000000..b3aa8055853 --- /dev/null +++ b/src/Storages/System/StorageSystemServerSettings.h @@ -0,0 +1,27 @@ +#pragma once + +#include + + +namespace DB +{ + +class Context; + + +/** implements system table "settings", which allows to get information about the current settings. + */ +class StorageSystemServerSettings final : public IStorageSystemOneBlock +{ +public: + std::string getName() const override { return "SystemServerSettings"; } + + static NamesAndTypesList getNamesAndTypes(); + +protected: + using IStorageSystemOneBlock::IStorageSystemOneBlock; + + void fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo & query_info) const override; +}; + +} diff --git a/src/Storages/System/attachSystemTables.cpp b/src/Storages/System/attachSystemTables.cpp index 07db151069f..61329ab834b 100644 --- a/src/Storages/System/attachSystemTables.cpp +++ b/src/Storages/System/attachSystemTables.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,7 @@ void attachSystemTablesLocal(ContextPtr context, IDatabase & system_database) attach(context, system_database, "functions"); attach(context, system_database, "events"); attach(context, system_database, "settings"); + attach(context, system_database, "server_settings"); attach(context, system_database, "settings_changes"); attach>(context, system_database, "merge_tree_settings"); attach>(context, system_database, "replicated_merge_tree_settings"); From fb35a6852ea55a693250449dcc5a07fb30ac4c2e Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 18 Feb 2023 11:07:04 +0100 Subject: [PATCH 076/445] More flakiness fiexes for test_backup_restore_on_cluster/test_disallow_concurrency Signed-off-by: Azat Khuzhin --- .../test_disallow_concurrency.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py index d689e44e4d4..ad16725e266 100644 --- a/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py +++ b/tests/integration/test_backup_restore_on_cluster/test_disallow_concurrency.py @@ -186,10 +186,14 @@ def test_concurrent_restores_on_same_node(): ) nodes[0].query(f"DROP TABLE tbl ON CLUSTER 'cluster' NO DELAY") - nodes[0].query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + restore_id = ( + nodes[0] + .query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + .split("\t")[0] + ) assert_eq_with_retry( nodes[0], - f"SELECT status FROM system.backups WHERE status == 'RESTORING'", + f"SELECT status FROM system.backups WHERE status == 'RESTORING' AND id == '{restore_id}'", "RESTORING", ) assert "Concurrent restores not supported" in nodes[0].query_and_get_error( @@ -220,7 +224,11 @@ def test_concurrent_restores_on_different_node(): ) nodes[0].query(f"DROP TABLE tbl ON CLUSTER 'cluster' NO DELAY") - nodes[0].query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + restore_id = ( + nodes[0] + .query(f"RESTORE TABLE tbl ON CLUSTER 'cluster' FROM {backup_name} ASYNC") + .split("\t")[0] + ) assert_eq_with_retry( nodes[0], f"SELECT status FROM system.backups WHERE status == 'RESTORING'", @@ -232,6 +240,6 @@ def test_concurrent_restores_on_different_node(): assert_eq_with_retry( nodes[0], - f"SELECT status FROM system.backups WHERE status == 'RESTORED'", + f"SELECT status FROM system.backups WHERE status == 'RESTORED' AND id == '{restore_id}'", "RESTORED", ) From 5b10fafbb03d66469512f7863f8f50e456693176 Mon Sep 17 00:00:00 2001 From: pufit Date: Sat, 18 Feb 2023 13:45:33 -0500 Subject: [PATCH 077/445] Additional tests --- tests/integration/test_config_xml_full/test.py | 14 ++++++++++++++ tests/integration/test_config_xml_yaml_mix/test.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/tests/integration/test_config_xml_full/test.py b/tests/integration/test_config_xml_full/test.py index ada3dc3f027..5f0679e5ef8 100644 --- a/tests/integration/test_config_xml_full/test.py +++ b/tests/integration/test_config_xml_full/test.py @@ -58,5 +58,19 @@ def test_xml_full_conf(): == "64999\n" ) + assert ( + node.query( + "select value from system.server_settings where name = 'max_connections'" + ) + == "4096\n" + ) + + assert ( + node.query( + "select changed from system.server_settings where name = 'max_connections'" + ) + == "1\n" + ) + finally: cluster.shutdown() diff --git a/tests/integration/test_config_xml_yaml_mix/test.py b/tests/integration/test_config_xml_yaml_mix/test.py index 373c42b2dea..9896d700ac1 100644 --- a/tests/integration/test_config_xml_yaml_mix/test.py +++ b/tests/integration/test_config_xml_yaml_mix/test.py @@ -64,6 +64,12 @@ def test_extra_yaml_mix(): ) == "64999\n" ) + assert ( + node.query( + "select value from system.server_settings where name = 'mark_cache_size'" + ) + == "8956\n" + ) finally: cluster.shutdown() From ff4e23aca87bea7182be122a5e738e2655ca138d Mon Sep 17 00:00:00 2001 From: pufit Date: Sun, 19 Feb 2023 16:48:55 -0500 Subject: [PATCH 078/445] Update programs/server/Server.cpp Co-authored-by: Sergei Trifonov --- 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 5a62bb88427..1f74cc53961 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -1269,7 +1269,7 @@ try /// Note: If you specified it in the top level config (not it config of default profile) /// then ClickHouse will use it exactly. /// This is done for backward compatibility. - if (global_context->areBackgroundExecutorsInitialized() && (server_settings.background_pool_size.changed || server_settings.background_merges_mutations_concurrency_ratio.changed)) + if (global_context->areBackgroundExecutorsInitialized() && (server_settings.background_pool_size.changed || server_settings.background_merges_mutations_concurrency_ratio.changed || server_settings.background_merges_mutations_scheduling_policy.changed)) { auto new_pool_size = server_settings.background_pool_size; auto new_ratio = server_settings.background_merges_mutations_concurrency_ratio; From ec49fbec231c2ad3e10081c118ecd18a8d0a855b Mon Sep 17 00:00:00 2001 From: pufit Date: Sun, 19 Feb 2023 16:49:01 -0500 Subject: [PATCH 079/445] Update src/Core/ServerSettings.h Co-authored-by: Sergei Trifonov --- src/Core/ServerSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/ServerSettings.h b/src/Core/ServerSettings.h index d597cdf1c66..43a158839bf 100644 --- a/src/Core/ServerSettings.h +++ b/src/Core/ServerSettings.h @@ -53,7 +53,7 @@ namespace DB M(UInt64, max_table_size_to_drop, 50000000000lu, "If size of a table is greater than this value (in bytes) than table could not be dropped with any DROP query.", 0) \ M(UInt64, max_partition_size_to_drop, 50000000000lu, "Same as max_table_size_to_drop, but for the partitions.", 0) \ M(UInt64, concurrent_threads_soft_limit_num, 0, "Sets how many concurrent thread can be allocated before applying CPU pressure. Zero means Unlimited.", 0) \ - M(UInt64, concurrent_threads_soft_limit_ratio_to_cores, 1024, "Same as concurrent_threads_soft_limit_num, but with ratio to cores.", 0) \ + M(UInt64, concurrent_threads_soft_limit_ratio_to_cores, 0, "Same as concurrent_threads_soft_limit_num, but with ratio to cores.", 0) \ \ M(UInt64, background_pool_size, 16, "Sets background pool size.", 0) \ M(UInt64, background_merges_mutations_concurrency_ratio, 2, "Sets background merges mutations concurrency ratio.", 0) \ From ccf87a6afd1fa93ea6ad8fe454ee27edd75c73ef Mon Sep 17 00:00:00 2001 From: lzydmxy <13126752315@163.com> Date: Mon, 20 Feb 2023 16:52:55 +0800 Subject: [PATCH 080/445] add integration test for move partition to disk on cluster --- .../configs/config.d/cluster.xml | 17 ++++ .../config.d/storage_configuration.xml | 28 ++++++ .../test.py | 94 +++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/test.py diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml new file mode 100644 index 00000000000..2316050b629 --- /dev/null +++ b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/cluster.xml @@ -0,0 +1,17 @@ + + + + + true + + node1 + 9000 + + + node2 + 9000 + + + + + \ No newline at end of file diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml new file mode 100644 index 00000000000..3289186c175 --- /dev/null +++ b/tests/integration/test_move_partition_to_disk_on_cluster/configs/config.d/storage_configuration.xml @@ -0,0 +1,28 @@ + + + + + + /jbod1/ + + + /external/ + + + + + + +
+ jbod1 +
+ + external + +
+
+
+ +
+ +
diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/test.py b/tests/integration/test_move_partition_to_disk_on_cluster/test.py new file mode 100644 index 00000000000..fe8606bd549 --- /dev/null +++ b/tests/integration/test_move_partition_to_disk_on_cluster/test.py @@ -0,0 +1,94 @@ +import pytest +from helpers.client import QueryRuntimeException +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__) + +node1 = cluster.add_instance( + "node1", + main_configs=[ + "configs/config.d/storage_configuration.xml", + "configs/config.d/cluster.xml", + ], + with_zookeeper=True, + stay_alive=True, + tmpfs=["/jbod1:size=10M", "/external:size=10M"], + macros={"shard": 0, "replica": 1}, +) + +node2 = cluster.add_instance( + "node2", + main_configs=[ + "configs/config.d/storage_configuration.xml", + "configs/config.d/cluster.xml", + ], + with_zookeeper=True, + stay_alive=True, + tmpfs=["/jbod1:size=10M", "/external:size=10M"], + macros={"shard": 0, "replica": 2}, +) + + +@pytest.fixture(scope="module") +def start_cluster(): + try: + cluster.start() + yield cluster + + finally: + cluster.shutdown() + + +def test_move_partition_to_disk_on_cluster(start_cluster): + for node in [node1, node2]: + node.query( + sql="CREATE TABLE test_local_table" + "(x UInt64) " + "ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_local_table', '{replica}') " + "ORDER BY tuple()" + "SETTINGS storage_policy = 'jbod_with_external';", + ) + + node1.query("INSERT INTO test_local_table VALUES (0)") + node1.query("SYSTEM SYNC REPLICA test_local_table", timeout=30) + + try: + node1.query( + sql="ALTER TABLE test_local_table ON CLUSTER 'test_cluster' MOVE PARTITION tuple() TO DISK 'jbod1';", + ) + except QueryRuntimeException: + pass + + for node in [node1, node2]: + assert ( + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" + ) + + node1.query( + sql="ALTER TABLE test_local_table ON CLUSTER 'test_cluster' MOVE PARTITION tuple() TO DISK 'external';", + ) + + for node in [node1, node2]: + assert ( + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','external')" + ) + + node1.query( + sql="ALTER TABLE test_local_table ON CLUSTER 'test_cluster' MOVE PARTITION tuple() TO VOLUME 'main';", + ) + + for node in [node1, node2]: + assert ( + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" + ) + + From 2273acaa30548da72c31f39bb5f8adde06ce4a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 20 Feb 2023 16:47:25 +0100 Subject: [PATCH 081/445] Fix incorrect alias recursion in QueryNormalizer --- src/Interpreters/QueryNormalizer.cpp | 4 +++ ...2667_order_by_aggregation_result.reference | 4 +++ .../02667_order_by_aggregation_result.sql | 36 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 tests/queries/0_stateless/02667_order_by_aggregation_result.reference create mode 100644 tests/queries/0_stateless/02667_order_by_aggregation_result.sql diff --git a/src/Interpreters/QueryNormalizer.cpp b/src/Interpreters/QueryNormalizer.cpp index 4db61501d3d..df454aecac1 100644 --- a/src/Interpreters/QueryNormalizer.cpp +++ b/src/Interpreters/QueryNormalizer.cpp @@ -118,6 +118,8 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) alias_node->checkSize(data.settings.max_expanded_ast_elements); ast = alias_node->clone(); ast->setAlias(node_alias); + if (data.finished_asts.contains(alias_node)) + data.finished_asts[ast] = ast; } } else @@ -127,6 +129,8 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) auto alias_name = ast->getAliasOrColumnName(); ast = alias_node->clone(); ast->setAlias(alias_name); + if (data.finished_asts.contains(alias_node)) + data.finished_asts[ast] = ast; } } } diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.reference b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference new file mode 100644 index 00000000000..642fc2ed645 --- /dev/null +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference @@ -0,0 +1,4 @@ +0 0 +0 1 █████████████████████████████████████████████████▉ +1 2 0.5 +1 0.1 1.1 diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.sql b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql new file mode 100644 index 00000000000..277430888d7 --- /dev/null +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql @@ -0,0 +1,36 @@ +-- Github issues: +-- - https://github.com/ClickHouse/ClickHouse/issues/46268 +-- - https://github.com/ClickHouse/ClickHouse/issues/46273 + +-- Queries that the original PR (https://github.com/ClickHouse/ClickHouse/pull/42827) tried to fix +SELECT (number = 1) AND (number = 2) AS value, sum(value) OVER () FROM numbers(1) WHERE 1; +SELECT time, round(exp_smooth, 10), bar(exp_smooth, -9223372036854775807, 1048575, 50) AS bar FROM (SELECT 2 OR (number = 0) OR (number >= 1) AS value, number AS time, exponentialTimeDecayedSum(2147483646)(value, time) OVER (RANGE BETWEEN CURRENT ROW AND CURRENT ROW) AS exp_smooth FROM numbers(1) WHERE 10) WHERE 25; + +CREATE TABLE ttttttt +( + `timestamp` DateTime, + `col1` Float64, + `col2` Float64, + `col3` Float64 +) +ENGINE = MergeTree() +ORDER BY tuple(); + +INSERT INTO ttttttt VALUES ('2023-02-20 00:00:00', 1, 2, 3); + +-- Query that https://github.com/ClickHouse/ClickHouse/pull/42827 broke +SELECT + argMax(col1, timestamp) AS col1, + argMax(col2, timestamp) AS col2, + col1 / col2 AS final_col +FROM ttttttt +GROUP BY + col3 +ORDER BY final_col DESC; + +SELECT + argMax(col1, timestamp) AS col1, + col1 / 10 AS final_col, + final_col + 1 AS final_col2 +FROM ttttttt +GROUP BY col3; From 42ae0582d393c7795187ddf786ef07bcf8ac5fc1 Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 20 Feb 2023 17:46:54 +0000 Subject: [PATCH 082/445] Try to use parquet v2 instead of v1 in output format --- src/Processors/Formats/Impl/ParquetBlockOutputFormat.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Processors/Formats/Impl/ParquetBlockOutputFormat.cpp b/src/Processors/Formats/Impl/ParquetBlockOutputFormat.cpp index 0fce98f8a11..375d3878b10 100644 --- a/src/Processors/Formats/Impl/ParquetBlockOutputFormat.cpp +++ b/src/Processors/Formats/Impl/ParquetBlockOutputFormat.cpp @@ -44,6 +44,7 @@ void ParquetBlockOutputFormat::consume(Chunk chunk) auto sink = std::make_shared(out); parquet::WriterProperties::Builder builder; + builder.version(parquet::ParquetVersion::PARQUET_2_LATEST); #if USE_SNAPPY builder.compression(parquet::Compression::SNAPPY); #endif From 87a4fb025275d1837868952f7f47e0305023b230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Mon, 20 Feb 2023 19:08:54 +0100 Subject: [PATCH 083/445] Update aliases when clone happens --- src/Interpreters/QueryNormalizer.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Interpreters/QueryNormalizer.cpp b/src/Interpreters/QueryNormalizer.cpp index df454aecac1..56b81b3d224 100644 --- a/src/Interpreters/QueryNormalizer.cpp +++ b/src/Interpreters/QueryNormalizer.cpp @@ -118,8 +118,15 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) alias_node->checkSize(data.settings.max_expanded_ast_elements); ast = alias_node->clone(); ast->setAlias(node_alias); + + /// If the cloned AST was finished, this one should also be considered finished if (data.finished_asts.contains(alias_node)) data.finished_asts[ast] = ast; + + /// If we had an alias for node_alias, point it instead to the new node so we don't have to revisit it + /// on subsequent calls + if (auto existing_alias = data.aliases.find(node_alias); existing_alias != data.aliases.end()) + existing_alias->second = ast; } } else @@ -129,8 +136,15 @@ void QueryNormalizer::visit(ASTIdentifier & node, ASTPtr & ast, Data & data) auto alias_name = ast->getAliasOrColumnName(); ast = alias_node->clone(); ast->setAlias(alias_name); + + /// If the cloned AST was finished, this one should also be considered finished if (data.finished_asts.contains(alias_node)) data.finished_asts[ast] = ast; + + /// If we had an alias for node_alias, point it instead to the new node so we don't have to revisit it + /// on subsequent calls + if (auto existing_alias = data.aliases.find(node_alias); existing_alias != data.aliases.end()) + existing_alias->second = ast; } } } From 186a29a2aad4e05be56c4462e55ee3eb5a0ec360 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Mon, 20 Feb 2023 19:46:39 +0100 Subject: [PATCH 084/445] Resurrect processors_profile_log docs. --- docs/en/operations/settings/settings.md | 9 +++ .../system-tables/processors_profile_log.md | 74 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 docs/en/operations/system-tables/processors_profile_log.md diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index 1d1d8b32d1d..9b16188ad6d 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -851,6 +851,15 @@ Result: └─────────────┴───────────┘ ``` +## log_processors_profiles {#settings-log_processors_profiles} + +Write time that processor spent during execution/waiting for data to `system.processors_profile_log` table. + +See also: + +- [`system.processors_profile_log`](../../operations/system-tables/processors_profile_log.md#system-processors_profile_log) +- [`EXPLAIN PIPELINE`](../../sql-reference/statements/explain.md#explain-pipeline) + ## max_insert_block_size {#settings-max_insert_block_size} The size of blocks (in a count of rows) to form for insertion into a table. diff --git a/docs/en/operations/system-tables/processors_profile_log.md b/docs/en/operations/system-tables/processors_profile_log.md new file mode 100644 index 00000000000..269385deab6 --- /dev/null +++ b/docs/en/operations/system-tables/processors_profile_log.md @@ -0,0 +1,74 @@ +# system.processors_profile_log {#system-processors_profile_log} + +This table contains profiling on processors level (that you can find in [`EXPLAIN PIPELINE`](../../sql-reference/statements/explain.md#explain-pipeline)). + +Columns: + +- `event_date` ([Date](../../sql-reference/data-types/date.md)) — The date when the event happened. +- `event_time` ([DateTime64](../../sql-reference/data-types/datetime64.md)) — The date and time when the event happened. +- `id` ([UInt64](../../sql-reference/data-types/int-uint.md)) — ID of processor +- `parent_ids` ([Array(UInt64)](../../sql-reference/data-types/array.md)) — Parent processors IDs +- `query_id` ([String](../../sql-reference/data-types/string.md)) — ID of the query +- `name` ([LowCardinality(String)](../../sql-reference/data-types/lowcardinality.md)) — Name of the processor. +- `elapsed_us` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Number of microseconds this processor was executed. +- `input_wait_elapsed_us` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Number of microseconds this processor was waiting for data (from other processor). +- `output_wait_elapsed_us` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Number of microseconds this processor was waiting because output port was full. +- `plan_step` ([UInt64](../../sql-reference/data-types/int-uint.md)) — ID of the query plan step which created this processor. The value is zero if the processor was not added from any step. +- `plan_group` ([UInt64](../../sql-reference/data-types/int-uint.md)) — Group of the processor if it was created by query plan step. A group is a logical partitioning of processors added from the same query plan step. Group is used only for beautifying the result of EXPLAIN PIPELINE result. +- `input_rows` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of rows consumed by processor. +- `input_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of bytes consumed by processor. +- `output_rows` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of rows generated by processor. +- `output_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md)) — The number of bytes generated by processor. +**Example** + +Query: + +``` sql +EXPLAIN PIPELINE +SELECT sleep(1) +┌─explain─────────────────────────┐ +│ (Expression) │ +│ ExpressionTransform │ +│ (SettingQuotaAndLimits) │ +│ (ReadFromStorage) │ +│ SourceFromSingleChunk 0 → 1 │ +└─────────────────────────────────┘ +SELECT sleep(1) +SETTINGS log_processors_profiles = 1 +Query id: feb5ed16-1c24-4227-aa54-78c02b3b27d4 +┌─sleep(1)─┐ +│ 0 │ +└──────────┘ +1 rows in set. Elapsed: 1.018 sec. +SELECT + name, + elapsed_us, + input_wait_elapsed_us, + output_wait_elapsed_us +FROM system.processors_profile_log +WHERE query_id = 'feb5ed16-1c24-4227-aa54-78c02b3b27d4' +ORDER BY name ASC +``` + +Result: + +``` text +┌─name────────────────────┬─elapsed_us─┬─input_wait_elapsed_us─┬─output_wait_elapsed_us─┐ +│ ExpressionTransform │ 1000497 │ 2823 │ 197 │ +│ LazyOutputFormat │ 36 │ 1002188 │ 0 │ +│ LimitsCheckingTransform │ 10 │ 1002994 │ 106 │ +│ NullSource │ 5 │ 1002074 │ 0 │ +│ NullSource │ 1 │ 1002084 │ 0 │ +│ SourceFromSingleChunk │ 45 │ 4736 │ 1000819 │ +└─────────────────────────┴────────────┴───────────────────────┴────────────────────────┘ +``` + +Here you can see: + +- `ExpressionTransform` was executing `sleep(1)` function, so it `work` will takes 1e6, and so `elapsed_us` > 1e6. +- `SourceFromSingleChunk` need to wait, because `ExpressionTransform` does not accept any data during execution of `sleep(1)`, so it will be in `PortFull` state for 1e6 us, and so `output_wait_elapsed_us` > 1e6. +- `LimitsCheckingTransform`/`NullSource`/`LazyOutputFormat` need to wait until `ExpressionTransform` will execute `sleep(1)` to process the result, so `input_wait_elapsed_us` > 1e6. + +**See Also** + +- [`EXPLAIN PIPELINE`](../../sql-reference/statements/explain.md#explain-pipeline) \ No newline at end of file From a568704d63a5402de9af2852a06f5419afc3da38 Mon Sep 17 00:00:00 2001 From: avogar Date: Mon, 20 Feb 2023 20:43:28 +0000 Subject: [PATCH 085/445] Fix avro --- src/Processors/Formats/Impl/AvroRowInputFormat.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp index bedf3cc932e..c3ea1b5e23b 100644 --- a/src/Processors/Formats/Impl/AvroRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/AvroRowInputFormat.cpp @@ -778,15 +778,14 @@ AvroDeserializer::AvroDeserializer(const Block & header, avro::ValidSchema schem void AvroDeserializer::deserializeRow(MutableColumns & columns, avro::Decoder & decoder, RowReadExtension & ext) const { + size_t row = columns[0]->size() + 1; ext.read_columns.assign(columns.size(), false); row_action.execute(columns, decoder, ext); for (size_t i = 0; i < ext.read_columns.size(); ++i) { /// Insert default in missing columns. - if (!column_found[i]) - { + if (columns[i]->size() != row) columns[i]->insertDefault(); - } } } From ce2b04453245780406994bf9c0e25821878a7279 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Tue, 21 Feb 2023 03:29:14 +0000 Subject: [PATCH 086/445] analyzer - apply limit and offset settings --- src/Analyzer/QueryTreeBuilder.cpp | 11 ++++ src/Planner/Planner.cpp | 17 +++--- src/Storages/StorageView.cpp | 2 + .../02667_analyzer_limit_settings.reference | 54 +++++++++++++++++++ .../02667_analyzer_limit_settings.sql | 24 +++++++++ 5 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 tests/queries/0_stateless/02667_analyzer_limit_settings.reference create mode 100644 tests/queries/0_stateless/02667_analyzer_limit_settings.sql diff --git a/src/Analyzer/QueryTreeBuilder.cpp b/src/Analyzer/QueryTreeBuilder.cpp index 05b643aa6af..57b1ae1c994 100644 --- a/src/Analyzer/QueryTreeBuilder.cpp +++ b/src/Analyzer/QueryTreeBuilder.cpp @@ -233,6 +233,17 @@ QueryTreeNodePtr QueryTreeBuilder::buildSelectExpression(const ASTPtr & select_q auto select_settings = select_query_typed.settings(); SettingsChanges settings_changes; + if (is_subquery) + { + if (const Settings & settings_ref = updated_context->getSettingsRef(); settings_ref.limit || settings_ref.offset) + { + Settings settings = updated_context->getSettings(); + settings.limit = 0; + settings.offset = 0; + updated_context->setSettings(settings); + } + } + if (select_settings) { auto & set_query = select_settings->as(); diff --git a/src/Planner/Planner.cpp b/src/Planner/Planner.cpp index f0fe44e368f..7c2a3ffed78 100644 --- a/src/Planner/Planner.cpp +++ b/src/Planner/Planner.cpp @@ -216,12 +216,17 @@ public: limit_length = query_node.getLimit()->as().getValue().safeGet(); } + if (settings.limit) + limit_length = limit_length ? std::min(limit_length, settings.limit.value) : settings.limit; + if (query_node.hasOffset()) { /// Constness of offset is validated during query analysis stage limit_offset = query_node.getOffset()->as().getValue().safeGet(); } + limit_offset += settings.offset; + /// Partial sort can be done if there is LIMIT, but no DISTINCT, LIMIT WITH TIES, LIMIT BY, ARRAY JOIN if (limit_length != 0 && !query_node.isDistinct() && @@ -720,7 +725,7 @@ bool addPreliminaryLimitOptimizationStepIfNeeded(QueryPlan & query_plan, bool apply_limit = query_processing_info.getToStage() != QueryProcessingStage::WithMergeableStateAfterAggregation; bool apply_prelimit = apply_limit && - query_node.hasLimit() && + query_analysis_result.limit_length && !query_node.isLimitWithTies() && !query_node.isGroupByWithTotals() && !query_analysis_result.query_has_with_totals_in_any_subquery_in_join_tree && @@ -767,7 +772,7 @@ void addPreliminarySortOrDistinctOrLimitStepsIfNeeded(QueryPlan & query_plan, * Otherwise we can take several equal values from different streams * according to limit and skip some distinct values. */ - if (query_node.hasLimit() && query_node.isDistinct()) + if (query_analysis_result.limit_length && query_node.isDistinct()) { addDistinctStep(query_plan, query_analysis_result, @@ -785,7 +790,7 @@ void addPreliminarySortOrDistinctOrLimitStepsIfNeeded(QueryPlan & query_plan, addLimitByStep(query_plan, limit_by_analysis_result, query_node); } - if (query_node.hasLimit()) + if (query_analysis_result.limit_length) addPreliminaryLimitStep(query_plan, query_analysis_result, planner_context, true /*do_not_skip_offset*/); } @@ -1420,7 +1425,7 @@ void Planner::buildPlanForQueryNode() bool apply_offset = query_processing_info.getToStage() != QueryProcessingStage::WithMergeableStateAfterAggregationAndLimit; - if (query_node.hasLimit() && query_node.isLimitWithTies() && apply_offset) + if (query_analysis_result.limit_length && query_node.isLimitWithTies() && apply_offset) addLimitStep(query_plan, query_analysis_result, planner_context, query_node); addExtremesStepIfNeeded(query_plan, planner_context); @@ -1434,9 +1439,9 @@ void Planner::buildPlanForQueryNode() * This is the case for various optimizations for distributed queries, * and when LIMIT cannot be applied it will be applied on the initiator anyway. */ - if (query_node.hasLimit() && apply_limit && !limit_applied && apply_offset) + if (query_analysis_result.limit_length && apply_limit && !limit_applied && apply_offset) addLimitStep(query_plan, query_analysis_result, planner_context, query_node); - else if (!limit_applied && apply_offset && query_node.hasOffset()) + else if (!limit_applied && apply_offset && query_analysis_result.limit_length) addOffsetStep(query_plan, query_analysis_result); /// Project names is not done on shards, because initiator will not find columns in blocks diff --git a/src/Storages/StorageView.cpp b/src/Storages/StorageView.cpp index 1a7050b4dff..d3a2ec470cf 100644 --- a/src/Storages/StorageView.cpp +++ b/src/Storages/StorageView.cpp @@ -95,6 +95,8 @@ ContextPtr getViewContext(ContextPtr context) view_settings.max_result_rows = 0; view_settings.max_result_bytes = 0; view_settings.extremes = false; + view_settings.limit = 0; + view_settings.offset = 0; view_context->setSettings(view_settings); return view_context; } diff --git a/tests/queries/0_stateless/02667_analyzer_limit_settings.reference b/tests/queries/0_stateless/02667_analyzer_limit_settings.reference new file mode 100644 index 00000000000..9e38ed9a59b --- /dev/null +++ b/tests/queries/0_stateless/02667_analyzer_limit_settings.reference @@ -0,0 +1,54 @@ +-- { echoOn } +SET limit = 0; +SELECT * FROM numbers(10); +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +SELECT * FROM numbers(10) SETTINGS limit=5, offset=2; +2 +3 +4 +5 +6 +SELECT count(*) FROM (SELECT * FROM numbers(10)); +10 +SELECT count(*) FROM (SELECT * FROM numbers(10) SETTINGS limit=5); +5 +SELECT count(*) FROM (SELECT * FROM numbers(10)) SETTINGS limit=5; +10 +SELECT count(*) FROM view(SELECT * FROM numbers(10)); +10 +SELECT count(*) FROM view(SELECT * FROM numbers(10) SETTINGS limit=5); +5 +SELECT count(*) FROM view(SELECT * FROM numbers(10)) SETTINGS limit=5; +10 +SET limit = 3; +SELECT * FROM numbers(10); +0 +1 +2 +SELECT * FROM numbers(10) SETTINGS limit=5, offset=2; +2 +3 +4 +5 +6 +SELECT count(*) FROM (SELECT * FROM numbers(10)); +10 +SELECT count(*) FROM (SELECT * FROM numbers(10) SETTINGS limit=5); +5 +SELECT count(*) FROM (SELECT * FROM numbers(10)) SETTINGS limit=5; +10 +SELECT count(*) FROM view(SELECT * FROM numbers(10)); +10 +SELECT count(*) FROM view(SELECT * FROM numbers(10) SETTINGS limit=5); +5 +SELECT count(*) FROM view(SELECT * FROM numbers(10)) SETTINGS limit=5; +10 diff --git a/tests/queries/0_stateless/02667_analyzer_limit_settings.sql b/tests/queries/0_stateless/02667_analyzer_limit_settings.sql new file mode 100644 index 00000000000..35dd65ab33e --- /dev/null +++ b/tests/queries/0_stateless/02667_analyzer_limit_settings.sql @@ -0,0 +1,24 @@ +SET allow_experimental_analyzer = 1; + +-- { echoOn } +SET limit = 0; + +SELECT * FROM numbers(10); +SELECT * FROM numbers(10) SETTINGS limit=5, offset=2; +SELECT count(*) FROM (SELECT * FROM numbers(10)); +SELECT count(*) FROM (SELECT * FROM numbers(10) SETTINGS limit=5); +SELECT count(*) FROM (SELECT * FROM numbers(10)) SETTINGS limit=5; +SELECT count(*) FROM view(SELECT * FROM numbers(10)); +SELECT count(*) FROM view(SELECT * FROM numbers(10) SETTINGS limit=5); +SELECT count(*) FROM view(SELECT * FROM numbers(10)) SETTINGS limit=5; + +SET limit = 3; +SELECT * FROM numbers(10); +SELECT * FROM numbers(10) SETTINGS limit=5, offset=2; +SELECT count(*) FROM (SELECT * FROM numbers(10)); +SELECT count(*) FROM (SELECT * FROM numbers(10) SETTINGS limit=5); +SELECT count(*) FROM (SELECT * FROM numbers(10)) SETTINGS limit=5; +SELECT count(*) FROM view(SELECT * FROM numbers(10)); +SELECT count(*) FROM view(SELECT * FROM numbers(10) SETTINGS limit=5); +SELECT count(*) FROM view(SELECT * FROM numbers(10)) SETTINGS limit=5; +-- { echoOff } From 367cf9e77cefccbf40b9489ac59c3e64d6449a5b Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 20 Feb 2023 16:37:59 +0100 Subject: [PATCH 087/445] Fix 01179_insert_values_semicolon test Back in #19925 a check for reading data after semicolon had been added, but after #40474 it does not work, the test does not show the problem because of timeout does not work without stdin before (a more generic fix for timeouts in expect tests I will submit later). To make this test works, the only type that I can found that will work right now is DateTime64, other types does use peeking, or even if they do, they will fail while parsing the query as SQL expression. Signed-off-by: Azat Khuzhin --- .../0_stateless/01179_insert_values_semicolon.expect | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/queries/0_stateless/01179_insert_values_semicolon.expect b/tests/queries/0_stateless/01179_insert_values_semicolon.expect index 9d35941ae40..c0b67de5302 100755 --- a/tests/queries/0_stateless/01179_insert_values_semicolon.expect +++ b/tests/queries/0_stateless/01179_insert_values_semicolon.expect @@ -21,7 +21,7 @@ expect ":) " send -- "DROP TABLE IF EXISTS test_01179\r" expect "Ok." -send -- "CREATE TABLE test_01179 (date DateTime) ENGINE=Memory()\r" +send -- "CREATE TABLE test_01179 (date DateTime64(3)) ENGINE=Memory()\r" expect "Ok." send -- "INSERT INTO test_01179 values ('2020-01-01')\r" @@ -30,11 +30,11 @@ expect "Ok." send -- "INSERT INTO test_01179 values ('2020-01-01'); \r" expect "Ok." -send -- "INSERT INTO test_01179 values ('2020-01-01'); (1) \r" +send -- "INSERT INTO test_01179 values ('2020-01-01 0'); (1) \r" expect "Cannot read data after semicolon" send -- "SELECT date, count() FROM test_01179 GROUP BY date FORMAT TSV\r" -expect "2020-01-01 00:00:00\t3" +expect "2020-01-01 00:00:00.000\t2" send -- "DROP TABLE test_01179\r" expect "Ok." From 5cc183ac39836aca060590eb96a572e46b653058 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Mon, 20 Feb 2023 19:27:09 +0100 Subject: [PATCH 088/445] Fix timeouts in 01179_insert_values_semicolon Signed-off-by: Azat Khuzhin --- .../queries/0_stateless/01179_insert_values_semicolon.expect | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/01179_insert_values_semicolon.expect b/tests/queries/0_stateless/01179_insert_values_semicolon.expect index c0b67de5302..16c62443856 100755 --- a/tests/queries/0_stateless/01179_insert_values_semicolon.expect +++ b/tests/queries/0_stateless/01179_insert_values_semicolon.expect @@ -10,9 +10,9 @@ set timeout 60 match_max 100000 expect_after { # Do not ignore eof from expect - eof { exp_continue } + -i $any_spawn_id eof { exp_continue } # A default timeout action is to do nothing, change it to fail - timeout { exit 1 } + -i $any_spawn_id timeout { exit 1 } } spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_CLIENT_BINARY \$CLICKHOUSE_CLIENT_OPT --disable_suggestion" From 2dfc0d008b5f120a468f91ef66d9fb8b93743d9a Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Tue, 21 Feb 2023 11:48:28 +0000 Subject: [PATCH 089/445] Fix: remove redundant sorting optimization + incorrect sorting step removal in case of parent step has more than 1 children --- .../QueryPlan/Optimizations/removeRedundantSorting.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp index 20d964dcb4f..c7b945b755c 100644 --- a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp +++ b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp @@ -188,7 +188,15 @@ private: return false; /// remove sorting - parent_node->children.front() = sorting_node->children.front(); + // parent_node->children.front() = sorting_node->children.front(); + for (auto & child : parent_node->children) + { + if (child == sorting_node) + { + child = sorting_node->children.front(); + break; + } + } /// sorting removed, so need to update sorting traits for upstream steps const DataStream * input_stream = &parent_node->children.front()->step->getOutputStream(); From 6a1621fcad2f7810dcba9765cd4de7382d2e9ff0 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:15:01 +0100 Subject: [PATCH 090/445] Fix flaky test 01710_normal_projections --- tests/queries/0_stateless/01710_normal_projections.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01710_normal_projections.sh b/tests/queries/0_stateless/01710_normal_projections.sh index 70e38b3722a..8ee3f41ea28 100755 --- a/tests/queries/0_stateless/01710_normal_projections.sh +++ b/tests/queries/0_stateless/01710_normal_projections.sh @@ -4,7 +4,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x" +$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x SETTINGS index_granularity=8192" $CLICKHOUSE_CLIENT -q "insert into test_sort_proj select number, toUInt32(-number - 1) from numbers(100)" echo "select where x < 10" From c83583e46b71b2dc4c5e5d17b033b5a3b3ddfb84 Mon Sep 17 00:00:00 2001 From: Salvatore Mesoraca Date: Tue, 21 Feb 2023 14:04:51 +0100 Subject: [PATCH 091/445] ActionsDAG: do not change result of and() during optimization Previously `and` results were inconsistent. Some optimizations can try to simplify and completely remove a single-argument `and`, replacing it with a cast to UInt8. This works well for all numeric types except floats. A float passed to `and` is normally interpreted as `bool` and values between 0 and 1 are treated as 1. The same value cast to an integer type will be truncated to 0. --- src/Interpreters/ActionsDAG.cpp | 20 +++- .../02667_and_consistency.reference | 23 ++++ .../0_stateless/02667_and_consistency.sql | 106 ++++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02667_and_consistency.reference create mode 100644 tests/queries/0_stateless/02667_and_consistency.sql diff --git a/src/Interpreters/ActionsDAG.cpp b/src/Interpreters/ActionsDAG.cpp index 96e7daa306f..623ead29921 100644 --- a/src/Interpreters/ActionsDAG.cpp +++ b/src/Interpreters/ActionsDAG.cpp @@ -1943,6 +1943,10 @@ ActionsDAGPtr ActionsDAG::cloneActionsForFilterPushDown( } auto conjunction = getConjunctionNodes(predicate, allowed_nodes); + if (conjunction.allowed.size() == 1 && conjunction.rejected.size() == 1 + && WhichDataType{conjunction.rejected.front()->result_type}.isFloat()) + return nullptr; + auto actions = cloneActionsForConjunction(conjunction.allowed, all_inputs); if (!actions) return nullptr; @@ -2008,10 +2012,12 @@ ActionsDAGPtr ActionsDAG::cloneActionsForFilterPushDown( node.children.swap(new_children); *predicate = std::move(node); } - else + else if (!WhichDataType{new_children.front()->result_type}.isFloat()) { /// If type is different, cast column. /// This case is possible, cause AND can use any numeric type as argument. + /// But casting floats to UInt8 or Bool produces different results. + /// so we can't apply this optimization to them. Node node; node.type = ActionType::COLUMN; node.result_name = predicate->result_type->getName(); @@ -2033,8 +2039,20 @@ ActionsDAGPtr ActionsDAG::cloneActionsForFilterPushDown( else { /// Predicate is function AND, which still have more then one argument. + /// Or there is only one argument that is a float and we can't just + /// remove the AND. /// Just update children and rebuild it. predicate->children.swap(new_children); + if (WhichDataType{predicate->children.front()->result_type}.isFloat()) + { + Node node; + node.type = ActionType::COLUMN; + node.result_name = "1"; + node.column = DataTypeUInt8().createColumnConst(0, 1u); + node.result_type = std::make_shared(); + const auto * const_col = &nodes.emplace_back(std::move(node)); + predicate->children.emplace_back(const_col); + } auto arguments = prepareFunctionArguments(predicate->children); FunctionOverloadResolverPtr func_builder_and = std::make_unique(std::make_shared()); diff --git a/tests/queries/0_stateless/02667_and_consistency.reference b/tests/queries/0_stateless/02667_and_consistency.reference new file mode 100644 index 00000000000..bcb2b5aecfb --- /dev/null +++ b/tests/queries/0_stateless/02667_and_consistency.reference @@ -0,0 +1,23 @@ +true +===== +true +===== +true +===== +true +===== +===== +1 +===== +===== +allow_experimental_analyzer +true +#45440 +2086579505 0 1 0 0 +-542998757 -542998757 1 0 0 += +2086579505 0 1 0 0 +-542998757 -542998757 1 0 0 += +2086579505 0 1 0 0 +-542998757 -542998757 1 0 0 diff --git a/tests/queries/0_stateless/02667_and_consistency.sql b/tests/queries/0_stateless/02667_and_consistency.sql new file mode 100644 index 00000000000..f02185a1a52 --- /dev/null +++ b/tests/queries/0_stateless/02667_and_consistency.sql @@ -0,0 +1,106 @@ +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 0; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(1) +SETTINGS enable_optimize_predicate_expression = 0; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING x AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 1; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 0; + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 1; -- { serverError 59 } + +SELECT '====='; + +SELECT 1 and sin(1); + +SELECT '====='; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING x AND sin(1) +SETTINGS enable_optimize_predicate_expression = 0; -- { serverError 59 } + +SELECT '====='; +SELECT 'allow_experimental_analyzer'; + +SET allow_experimental_analyzer = 1; + +SELECT toBool(sin(SUM(number))) AS x +FROM +( + SELECT 1 AS number +) +GROUP BY number +HAVING 1 AND sin(sum(number)) +SETTINGS enable_optimize_predicate_expression = 1; + +select '#45440'; + +DROP TABLE IF EXISTS t2; +CREATE TABLE t2(c0 Int32) ENGINE = MergeTree ORDER BY c0; +INSERT INTO t2 VALUES (928386547), (1541944097), (2086579505), (1990427322), (-542998757), (390253678), (554855248), (203290629), (1504693323); + +SELECT + MAX(left.c0), + min2(left.c0, -(-left.c0) * (radians(left.c0) - radians(left.c0))) AS g, + (((-1925024212 IS NOT NULL) IS NOT NULL) != radians(tan(1216286224))) AND cos(lcm(MAX(left.c0), -1966575216) OR (MAX(left.c0) * 1180517420)) AS h, + NOT h, + h IS NULL +FROM t2 AS left +GROUP BY g; +select '='; +SELECT MAX(left.c0), min2(left.c0, -(-left.c0) * (radians(left.c0) - radians(left.c0))) as g, (((-1925024212 IS NOT NULL) IS NOT NULL) != radians(tan(1216286224))) AND cos(lcm(MAX(left.c0), -1966575216) OR (MAX(left.c0) * 1180517420)) as h, not h, h is null + FROM t2 AS left + GROUP BY g HAVING h SETTINGS enable_optimize_predicate_expression = 0; +select '='; +SELECT MAX(left.c0), min2(left.c0, -(-left.c0) * (radians(left.c0) - radians(left.c0))) as g, (((-1925024212 IS NOT NULL) IS NOT NULL) != radians(tan(1216286224))) AND cos(lcm(MAX(left.c0), -1966575216) OR (MAX(left.c0) * 1180517420)) as h, not h, h is null + FROM t2 AS left + GROUP BY g HAVING h SETTINGS enable_optimize_predicate_expression = 1; + +DROP TABLE IF EXISTS t2; From 341a7766174aad17b63fe31ae6fe38705a4d1786 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:41:05 +0100 Subject: [PATCH 092/445] Disable test --- tests/integration/test_storage_kafka/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_storage_kafka/test.py b/tests/integration/test_storage_kafka/test.py index 51871eae3d8..ca189d837ad 100644 --- a/tests/integration/test_storage_kafka/test.py +++ b/tests/integration/test_storage_kafka/test.py @@ -4440,6 +4440,7 @@ def test_block_based_formats_2(kafka_cluster): kafka_delete_topic(admin_client, format_name) +@pytest.mark.skip(reason="Debug") def test_bad_messages_parsing(kafka_cluster): admin_client = KafkaAdminClient( bootstrap_servers="localhost:{}".format(kafka_cluster.kafka_port) From a25255cabc505a41ea4f42c683e0fcc3027746f5 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 21 Feb 2023 16:57:44 +0100 Subject: [PATCH 093/445] Update StreamingFormatExecutor.h --- src/Processors/Executors/StreamingFormatExecutor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processors/Executors/StreamingFormatExecutor.h b/src/Processors/Executors/StreamingFormatExecutor.h index 46f22d6deb6..f5a1562a340 100644 --- a/src/Processors/Executors/StreamingFormatExecutor.h +++ b/src/Processors/Executors/StreamingFormatExecutor.h @@ -24,7 +24,7 @@ public: StreamingFormatExecutor( const Block & header_, InputFormatPtr format_, - ErrorCallback on_error_ = [](const MutableColumns &, Exception & e) -> size_t { throw e; }, + ErrorCallback on_error_ = [](const MutableColumns &, Exception & e) -> size_t { throw std::move(e); }, SimpleTransformPtr adding_defaults_transform_ = nullptr); /// Returns numbers of new read rows. From 1d4352d82af2cc354890018d1e5d715d2968f7a9 Mon Sep 17 00:00:00 2001 From: alesapin Date: Tue, 21 Feb 2023 17:01:19 +0100 Subject: [PATCH 094/445] Fix integration test: terminate old version without wait --- .../test_backup_with_other_granularity/test.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_backup_with_other_granularity/test.py b/tests/integration/test_backup_with_other_granularity/test.py index d30c45c3691..f456fae23a8 100644 --- a/tests/integration/test_backup_with_other_granularity/test.py +++ b/tests/integration/test_backup_with_other_granularity/test.py @@ -54,7 +54,8 @@ def test_backup_from_old_version(started_cluster): node1.query("ALTER TABLE source_table FREEZE PARTITION tuple();") - node1.restart_with_latest_version(fix_metadata=True) + # We don't want to wait old outdated version to finish properly, just terminate it + node1.restart_with_latest_version(fix_metadata=True, signal=9) node1.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table1', '1') ORDER BY tuple()" @@ -107,7 +108,8 @@ def test_backup_from_old_version_setting(started_cluster): node2.query("ALTER TABLE source_table FREEZE PARTITION tuple();") - node2.restart_with_latest_version(fix_metadata=True) + # We don't want to wait old outdated version to finish properly, just terminate it + node2.restart_with_latest_version(fix_metadata=True, signal=9) node2.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table2', '1') ORDER BY tuple() SETTINGS enable_mixed_granularity_parts = 1" @@ -163,7 +165,8 @@ def test_backup_from_old_version_config(started_cluster): "1", ) - node3.restart_with_latest_version(callback_onstop=callback, fix_metadata=True) + # We don't want to wait old outdated version to finish properly, just terminate it + node3.restart_with_latest_version(callback_onstop=callback, fix_metadata=True, signal=9) node3.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table3', '1') ORDER BY tuple() SETTINGS enable_mixed_granularity_parts = 1" From af677c7dcd6663cc89fa5756c2bff9c59bbb0d8d Mon Sep 17 00:00:00 2001 From: robot-clickhouse Date: Tue, 21 Feb 2023 16:08:13 +0000 Subject: [PATCH 095/445] Automatic style fix --- tests/integration/test_backup_with_other_granularity/test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_backup_with_other_granularity/test.py b/tests/integration/test_backup_with_other_granularity/test.py index f456fae23a8..2a82fc71951 100644 --- a/tests/integration/test_backup_with_other_granularity/test.py +++ b/tests/integration/test_backup_with_other_granularity/test.py @@ -166,7 +166,9 @@ def test_backup_from_old_version_config(started_cluster): ) # We don't want to wait old outdated version to finish properly, just terminate it - node3.restart_with_latest_version(callback_onstop=callback, fix_metadata=True, signal=9) + node3.restart_with_latest_version( + callback_onstop=callback, fix_metadata=True, signal=9 + ) node3.query( "CREATE TABLE dest_table (A Int64, B String, Y String) ENGINE = ReplicatedMergeTree('/test/dest_table3', '1') ORDER BY tuple() SETTINGS enable_mixed_granularity_parts = 1" From 45b1b66fd8d420db449eedb87b0271dcd53e0b08 Mon Sep 17 00:00:00 2001 From: Igor Nikonov Date: Tue, 21 Feb 2023 16:58:45 +0000 Subject: [PATCH 096/445] Remove unnecessary comment --- .../QueryPlan/Optimizations/removeRedundantSorting.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp index c7b945b755c..41e30dee83e 100644 --- a/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp +++ b/src/Processors/QueryPlan/Optimizations/removeRedundantSorting.cpp @@ -188,7 +188,6 @@ private: return false; /// remove sorting - // parent_node->children.front() = sorting_node->children.front(); for (auto & child : parent_node->children) { if (child == sorting_node) From 9a7c71b78e24e0f3b38a2a3d49a1f446b4a74e7e Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 21 Feb 2023 18:07:57 +0100 Subject: [PATCH 097/445] Allow to hide only values from system.named_collections --- src/Access/Common/AccessType.h | 1 + src/Access/UsersConfigAccessStorage.cpp | 6 ++++++ .../System/StorageSystemNamedCollections.cpp | 7 ++++++- .../configs/users.d/users.xml | 1 + .../integration/test_named_collections/test.py | 18 +++++++++++++++++- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Access/Common/AccessType.h b/src/Access/Common/AccessType.h index 497327c1bad..f57cc2886e3 100644 --- a/src/Access/Common/AccessType.h +++ b/src/Access/Common/AccessType.h @@ -135,6 +135,7 @@ enum class AccessType M(SHOW_SETTINGS_PROFILES, "SHOW PROFILES, SHOW CREATE SETTINGS PROFILE, SHOW CREATE PROFILE", GLOBAL, SHOW_ACCESS) \ M(SHOW_ACCESS, "", GROUP, ACCESS_MANAGEMENT) \ M(SHOW_NAMED_COLLECTIONS, "SHOW NAMED COLLECTIONS", GLOBAL, ACCESS_MANAGEMENT) \ + M(SHOW_NAMED_COLLECTIONS_SECRETS, "SHOW NAMED COLLECTIONS SECRETS", GLOBAL, ACCESS_MANAGEMENT) \ M(ACCESS_MANAGEMENT, "", GROUP, ALL) \ \ M(SYSTEM_SHUTDOWN, "SYSTEM KILL, SHUTDOWN", GLOBAL, SYSTEM) \ diff --git a/src/Access/UsersConfigAccessStorage.cpp b/src/Access/UsersConfigAccessStorage.cpp index 58edff039ca..b893554cb8a 100644 --- a/src/Access/UsersConfigAccessStorage.cpp +++ b/src/Access/UsersConfigAccessStorage.cpp @@ -239,6 +239,12 @@ namespace user->access.revoke(AccessType::SHOW_NAMED_COLLECTIONS); } + bool show_named_collections_secrets = config.getBool(user_config + ".show_named_collections_secrets", false); + if (!show_named_collections_secrets) + { + user->access.revoke(AccessType::SHOW_NAMED_COLLECTIONS_SECRETS); + } + String default_database = config.getString(user_config + ".default_database", ""); user->default_database = default_database; diff --git a/src/Storages/System/StorageSystemNamedCollections.cpp b/src/Storages/System/StorageSystemNamedCollections.cpp index bc1e3a45e6b..621799c37f2 100644 --- a/src/Storages/System/StorageSystemNamedCollections.cpp +++ b/src/Storages/System/StorageSystemNamedCollections.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace DB @@ -30,6 +31,7 @@ StorageSystemNamedCollections::StorageSystemNamedCollections(const StorageID & t void StorageSystemNamedCollections::fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo &) const { context->checkAccess(AccessType::SHOW_NAMED_COLLECTIONS); + const auto & access = context->getAccess(); auto collections = NamedCollectionFactory::instance().getAll(); for (const auto & [name, collection] : collections) @@ -47,7 +49,10 @@ void StorageSystemNamedCollections::fillData(MutableColumns & res_columns, Conte for (const auto & key : collection->getKeys()) { key_column.insertData(key.data(), key.size()); - value_column.insert(collection->get(key)); + if (access->isGranted(AccessType::SHOW_NAMED_COLLECTIONS_SECRETS)) + value_column.insert(collection->get(key)); + else + value_column.insert("[HIDDEN]"); size++; } diff --git a/tests/integration/test_named_collections/configs/users.d/users.xml b/tests/integration/test_named_collections/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_named_collections/configs/users.d/users.xml +++ b/tests/integration/test_named_collections/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_named_collections/test.py b/tests/integration/test_named_collections/test.py index 3b102f1aa70..612b894461b 100644 --- a/tests/integration/test_named_collections/test.py +++ b/tests/integration/test_named_collections/test.py @@ -102,7 +102,23 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert int(node.query("select count() from system.named_collections")) > 0 + assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" + replace_in_users_config( + node, "show_named_collections_secrets>1", "show_named_collections_secrets>0" + ) + assert "show_named_collections_secrets>0" in node.exec_in_container( + ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] + ) + node.restart_clickhouse() + assert node.query("select collection['key1'] from system.named_collections").strip() == "[HIDDEN]" + replace_in_users_config( + node, "show_named_collections_secrets>0", "show_named_collections_secrets>1" + ) + assert "show_named_collections_secrets>1" in node.exec_in_container( + ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] + ) + node.restart_clickhouse() + assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" def test_config_reload(cluster): From 07158e625302e596cd7b5280e71712bd79fd285b Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 13 Feb 2023 17:41:39 +0000 Subject: [PATCH 098/445] Add CrossToInnerJoinPass --- src/Analyzer/JoinNode.cpp | 18 ++ src/Analyzer/JoinNode.h | 2 + src/Analyzer/Passes/CrossToInnerJoinPass.cpp | 253 +++++++++++++++++++ src/Analyzer/Passes/CrossToInnerJoinPass.h | 24 ++ src/Analyzer/QueryTreePassManager.cpp | 2 + 5 files changed, 299 insertions(+) create mode 100644 src/Analyzer/Passes/CrossToInnerJoinPass.cpp create mode 100644 src/Analyzer/Passes/CrossToInnerJoinPass.h diff --git a/src/Analyzer/JoinNode.cpp b/src/Analyzer/JoinNode.cpp index 28a0c4ad7e0..86cf7e90c3f 100644 --- a/src/Analyzer/JoinNode.cpp +++ b/src/Analyzer/JoinNode.cpp @@ -15,6 +15,11 @@ namespace DB { +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + JoinNode::JoinNode(QueryTreeNodePtr left_table_expression_, QueryTreeNodePtr right_table_expression_, QueryTreeNodePtr join_expression_, @@ -113,4 +118,17 @@ ASTPtr JoinNode::toASTImpl() const return tables_in_select_query_ast; } +void JoinNode::crossToInner(const QueryTreeNodePtr & join_expression_) +{ + if (kind != JoinKind::Cross && kind != JoinKind::Comma) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot rewrite join {} to inner join, expected cross", toString(kind)); + + if (children[join_expression_child_index]) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Join expression is not empty: '{}'", children[join_expression_child_index]->formatConvertedASTForErrorMessage()); + + kind = JoinKind::Inner; + strictness = JoinStrictness::All; + children[join_expression_child_index] = std::move(join_expression_); +} + } diff --git a/src/Analyzer/JoinNode.h b/src/Analyzer/JoinNode.h index 15ba11a0122..d1cd64e67b7 100644 --- a/src/Analyzer/JoinNode.h +++ b/src/Analyzer/JoinNode.h @@ -126,6 +126,8 @@ public: return QueryTreeNodeType::JOIN; } + void crossToInner(const QueryTreeNodePtr & join_expression_); + void dumpTreeImpl(WriteBuffer & buffer, FormatState & format_state, size_t indent) const override; protected: diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp new file mode 100644 index 00000000000..dc03e340b15 --- /dev/null +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp @@ -0,0 +1,253 @@ +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int INCORRECT_QUERY; +} + +namespace +{ + +using EquiCondition = std::tuple; + +void exctractJoinConditions(const QueryTreeNodePtr & node, QueryTreeNodes & equi_conditions, QueryTreeNodes & other) +{ + if (auto * func = node->as()) + { + const auto & args = func->getArguments().getNodes(); + + if (args.size() == 2 && func->getFunctionName() == "equals") + { + equi_conditions.push_back(node); + return; + } + + if (func->getFunctionName() == "and") + { + for (auto & arg : args) + exctractJoinConditions(arg, equi_conditions, other); + return; + } + } + + other.push_back(node); +} + +const QueryTreeNodePtr & getEquiArgument(const QueryTreeNodePtr & cond, size_t index) +{ + const auto * func = cond->as(); + chassert(func && func->getFunctionName() == "equals" && func->getArguments().getNodes().size() == 2); + return func->getArguments().getNodes()[index]; +} + + +/// Check that node has only one source and return it. +/// {_, false} - multiple sources +/// {nullptr, true} - no sources +/// {source, true} - single source +std::pair getExpressionSource(const QueryTreeNodePtr & node) +{ + if (const auto * column = node->as()) + { + auto source = column->getColumnSourceOrNull(); + if (!source) + return {nullptr, false}; + return {source.get(), true}; + } + + if (const auto * func = node->as()) + { + const IQueryTreeNode * source = nullptr; + const auto & args = func->getArguments().getNodes(); + for (auto & arg : args) + { + auto [arg_source, is_ok] = getExpressionSource(arg); + if (!is_ok) + return {nullptr, false}; + + if (!source) + source = arg_source; + else if (arg_source && !source->isEqual(*arg_source)) + return {nullptr, false}; + } + return {source, true}; + + } + + if (node->as()) + return {nullptr, true}; + + return {nullptr, false}; +} + +bool findInTableExpression(const IQueryTreeNode * source, const QueryTreeNodePtr & table_expression) +{ + if (!source) + return true; + + if (source->isEqual(*table_expression)) + return true; + + if (const auto * join_node = table_expression->as()) + { + return findInTableExpression(source, join_node->getLeftTableExpression()) + || findInTableExpression(source, join_node->getRightTableExpression()); + } + + if (const auto * query_node = table_expression->as()) + { + return findInTableExpression(source, query_node->getJoinTree()); + } + + return false; +} + +class CrossToInnerJoinVisitor : public InDepthQueryTreeVisitorWithContext +{ +public: + using Base = InDepthQueryTreeVisitorWithContext; + using Base::Base; + + /// Returns false if can't rewrite cross to inner join + bool tryRewrite(JoinNode * join_node) + { + if (!isCrossOrComma(join_node->getKind())) + return true; + + if (where_stack.empty()) + return false; + + auto & where_condition = *where_stack.back(); + if (!where_condition) + return false; + + const auto & left_table = join_node->getLeftTableExpression(); + const auto & right_table = join_node->getRightTableExpression(); + + QueryTreeNodes equi_conditions; + QueryTreeNodes other_conditions; + exctractJoinConditions(where_condition, equi_conditions, other_conditions); + bool can_join_on_anything = false; + for (auto & cond : equi_conditions) + { + auto left_src = getExpressionSource(getEquiArgument(cond, 0)); + auto right_src = getExpressionSource(getEquiArgument(cond, 1)); + if (left_src.second && right_src.second && left_src.first && right_src.first) + { + bool can_join_on = (findInTableExpression(left_src.first, left_table) && findInTableExpression(right_src.first, right_table)) + || (findInTableExpression(left_src.first, right_table) && findInTableExpression(right_src.first, left_table)); + + if (can_join_on) + { + can_join_on_anything = true; + continue; + } + } + + /// Can't join on this condition, move it to other conditions + other_conditions.push_back(cond); + cond = nullptr; + } + + if (!can_join_on_anything) + return false; + + equi_conditions.erase(std::remove(equi_conditions.begin(), equi_conditions.end(), nullptr), equi_conditions.end()); + join_node->crossToInner(makeConjunction(equi_conditions)); + where_condition = makeConjunction(other_conditions); + return true; + } + + void visitImpl(QueryTreeNodePtr & node) + { + if (!isEnabled()) + return; + + if (auto * query_node = node->as()) + { + /// We are entering the subtree and can use WHERE condition from this subtree + if (auto & where_node = query_node->getWhere()) + where_stack.push_back(&where_node); + } + + if (auto * join_node = node->as()) + { + bool is_rewritten = tryRewrite(join_node); + if (!is_rewritten && forceRewrite(join_node->getKind())) + { + throw Exception(ErrorCodes::INCORRECT_QUERY, + "Failed to rewrite '{}' to INNER JOIN: " + "no equi-join conditions found in WHERE clause. " + "You may set setting `cross_to_inner_join_rewrite` to `1` to allow slow CROSS JOIN for this case", + join_node->formatASTForErrorMessage()); + } + } + + if (!where_stack.empty() && where_stack.back()->get() == node.get()) + { + /// We are visiting the WHERE clause. + /// It means that we have visited current subtree and will go out of WHERE scope. + where_stack.pop_back(); + } + } + +private: + bool isEnabled() const + { + return getSettings().cross_to_inner_join_rewrite; + } + + bool forceRewrite(JoinKind kind) const + { + if (kind == JoinKind::Cross) + return false; + /// Comma join can be forced to rewrite + return getSettings().cross_to_inner_join_rewrite >= 2; + } + + QueryTreeNodePtr makeConjunction(const QueryTreeNodes & nodes) + { + if (nodes.empty()) + return nullptr; + + if (nodes.size() == 1) + return nodes.front(); + + auto function_node = std::make_shared("and"); + for (auto & node : nodes) + function_node->getArguments().getNodes().push_back(node); + + const auto & function = FunctionFactory::instance().get("and", getContext()); + function_node->resolveAsFunction(function->build(function_node->getArgumentColumns())); + return function_node; + } + + std::deque where_stack; +}; + +} + +void CrossToInnerJoinPass::run(QueryTreeNodePtr query_tree_node, ContextPtr context) +{ + CrossToInnerJoinVisitor visitor(std::move(context)); + visitor.visit(query_tree_node); +} + +} diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.h b/src/Analyzer/Passes/CrossToInnerJoinPass.h new file mode 100644 index 00000000000..8f50f72e3d1 --- /dev/null +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace DB +{ + + +/** Replace CROSS JOIN with INNER JOIN. + */ +class CrossToInnerJoinPass final : public IQueryTreePass +{ +public: + String getName() override { return "CrossToInnerJoin"; } + + String getDescription() override + { + return "Replace CROSS JOIN with INNER JOIN"; + } + + void run(QueryTreeNodePtr query_tree_node, ContextPtr context) override; +}; + +} diff --git a/src/Analyzer/QueryTreePassManager.cpp b/src/Analyzer/QueryTreePassManager.cpp index 218e47d973f..9ba18e27f73 100644 --- a/src/Analyzer/QueryTreePassManager.cpp +++ b/src/Analyzer/QueryTreePassManager.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace DB @@ -268,6 +269,7 @@ void addQueryTreePasses(QueryTreePassManager & manager) manager.addPass(std::make_unique()); + manager.addPass(std::make_unique()); } } From 6296109be0901691bcd970bc7038fd6d0116f589 Mon Sep 17 00:00:00 2001 From: vdimir Date: Tue, 14 Feb 2023 13:12:10 +0000 Subject: [PATCH 099/445] Tests for CrossToInnerJoinPass --- .../00849_multiple_comma_join_2.reference | 232 +++--------------- .../00849_multiple_comma_join_2.sql | 108 ++++++-- .../02364_setting_cross_to_inner_rewrite.sql | 2 - .../02564_analyzer_cross_to_inner.reference | 10 + .../02564_analyzer_cross_to_inner.sql | 62 +++++ 5 files changed, 194 insertions(+), 220 deletions(-) create mode 100644 tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference create mode 100644 tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql diff --git a/tests/queries/0_stateless/00849_multiple_comma_join_2.reference b/tests/queries/0_stateless/00849_multiple_comma_join_2.reference index 2652a82ab54..16f228a5569 100644 --- a/tests/queries/0_stateless/00849_multiple_comma_join_2.reference +++ b/tests/queries/0_stateless/00849_multiple_comma_join_2.reference @@ -1,205 +1,33 @@ -SELECT a -FROM t1 -CROSS JOIN t2 -SELECT a -FROM t1 -ALL INNER JOIN t2 ON a = t2.a -WHERE a = t2.a -SELECT a -FROM t1 -ALL INNER JOIN t2 ON b = t2.b -WHERE b = t2.b -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` -) AS `--.s` -ALL INNER JOIN t3 ON `--t1.a` = a -WHERE (`--t1.a` = `--t2.a`) AND (`--t1.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - b AS `--t1.b`, - a AS `--t1.a`, - t2.b AS `--t2.b` - FROM t1 - ALL INNER JOIN t2 ON `--t1.b` = `--t2.b` -) AS `--.s` -ALL INNER JOIN t3 ON `--t1.b` = b -WHERE (`--t1.b` = `--t2.b`) AND (`--t1.b` = b) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t1.a` = `--t3.a` -) AS `--.s` -ALL INNER JOIN t4 ON `--t1.a` = a -WHERE (`--t1.a` = `--t2.a`) AND (`--t1.a` = `--t3.a`) AND (`--t1.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.b`, - `--t1.a`, - `--t2.b`, - b AS `--t3.b` - FROM - ( - SELECT - b AS `--t1.b`, - a AS `--t1.a`, - t2.b AS `--t2.b` - FROM t1 - ALL INNER JOIN t2 ON `--t1.b` = `--t2.b` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t1.b` = `--t3.b` -) AS `--.s` -ALL INNER JOIN t4 ON `--t1.b` = b -WHERE (`--t1.b` = `--t2.b`) AND (`--t1.b` = `--t3.b`) AND (`--t1.b` = b) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t2.a` = `--t1.a` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t2.a` = `--t3.a` -) AS `--.s` -ALL INNER JOIN t4 ON `--t2.a` = a -WHERE (`--t2.a` = `--t1.a`) AND (`--t2.a` = `--t3.a`) AND (`--t2.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - ALL INNER JOIN t3 ON (`--t3.a` = `--t1.a`) AND (`--t3.a` = `--t2.a`) -) AS `--.s` -ALL INNER JOIN t4 ON `--t3.a` = a -WHERE (`--t3.a` = `--t1.a`) AND (`--t3.a` = `--t2.a`) AND (`--t3.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - CROSS JOIN t3 -) AS `--.s` -ALL INNER JOIN t4 ON (a = `--t1.a`) AND (a = `--t2.a`) AND (a = `--t3.a`) -WHERE (a = `--t1.a`) AND (a = `--t2.a`) AND (a = `--t3.a`) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - `--t1.a`, - `--t2.a`, - a AS `--t3.a` - FROM - ( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` - ) AS `--.s` - ALL INNER JOIN t3 ON `--t2.a` = `--t3.a` -) AS `--.s` -ALL INNER JOIN t4 ON `--t3.a` = a -WHERE (`--t1.a` = `--t2.a`) AND (`--t2.a` = `--t3.a`) AND (`--t3.a` = a) -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT `--t1.a` - FROM - ( - SELECT a AS `--t1.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - CROSS JOIN t3 -) AS `--.s` -CROSS JOIN t4 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT `--t1.a` - FROM - ( - SELECT a AS `--t1.a` - FROM t1 - CROSS JOIN t2 - ) AS `--.s` - CROSS JOIN t3 -) AS `--.s` -CROSS JOIN t4 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT a AS `--t1.a` - FROM t1 - CROSS JOIN t2 -) AS `--.s` -CROSS JOIN t3 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT a AS `--t1.a` - FROM t1 - ALL INNER JOIN t2 USING (a) -) AS `--.s` -CROSS JOIN t3 -SELECT `--t1.a` AS `t1.a` -FROM -( - SELECT - a AS `--t1.a`, - t2.a AS `--t2.a` - FROM t1 - ALL INNER JOIN t2 ON `--t1.a` = `--t2.a` -) AS `--.s` -CROSS JOIN t3 +0 1 +0 1 +0 2 +0 2 +0 3 +0 3 +0 3 +1 2 +2 1 +0 3 +3 0 +3 0 +2 0 +1 1 +1 1 +0 1 +0 1 +0 2 +0 2 +0 3 +0 3 +0 3 +1 2 +2 1 +0 3 +3 0 +3 0 +2 0 +1 1 +1 1 SELECT * FROM t1, t2 1 1 1 1 1 1 1 \N diff --git a/tests/queries/0_stateless/00849_multiple_comma_join_2.sql b/tests/queries/0_stateless/00849_multiple_comma_join_2.sql index eb803450ff7..db8b27c4d4d 100644 --- a/tests/queries/0_stateless/00849_multiple_comma_join_2.sql +++ b/tests/queries/0_stateless/00849_multiple_comma_join_2.sql @@ -12,31 +12,107 @@ CREATE TABLE t2 (a UInt32, b Nullable(Int32)) ENGINE = Memory; CREATE TABLE t3 (a UInt32, b Nullable(Int32)) ENGINE = Memory; CREATE TABLE t4 (a UInt32, b Nullable(Int32)) ENGINE = Memory; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.a = t2.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.b = t2.b; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.a = t3.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.b = t2.b AND t1.b = t3.b; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t1.a = t3.a AND t1.a = t4.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.b = t2.b AND t1.b = t3.b AND t1.b = t4.b; +SET allow_experimental_analyzer = 0; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t2.a = t1.a AND t2.a = t3.a AND t2.a = t4.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t3.a = t1.a AND t3.a = t2.a AND t3.a = t4.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t4.a = t1.a AND t4.a = t2.a AND t4.a = t3.a; -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.a = t3.a AND t3.a = t4.a; +--- EXPLAIN SYNTAX (old AST based optimization) +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.a = t2.a); -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4; -EXPLAIN SYNTAX SELECT t1.a FROM t1 CROSS JOIN t2 CROSS JOIN t3 CROSS JOIN t4; +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 WHERE t1.b = t2.b); -EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 CROSS JOIN t3; -EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 USING a CROSS JOIN t3; -EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 ON t1.a = t2.a CROSS JOIN t3; +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3 WHERE t1.b = t2.b AND t1.b = t3.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t1.a = t3.a AND t1.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.b = t2.b AND t1.b = t3.b AND t1.b = t4.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t2.a = t1.a AND t2.a = t3.a AND t2.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t3.a = t1.a AND t3.a = t2.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t4.a = t1.a AND t4.a = t2.a AND t4.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.a = t3.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2, t3, t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1 CROSS JOIN t2 CROSS JOIN t3 CROSS JOIN t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1, t2 CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 USING a CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN SYNTAX SELECT t1.a FROM t1 JOIN t2 ON t1.a = t2.a CROSS JOIN t3); + +--- EXPLAIN QUERY TREE +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2 WHERE t1.a = t2.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2 WHERE t1.b = t2.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3 WHERE t1.b = t2.b AND t1.b = t3.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t1.a = t3.a AND t1.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.b = t2.b AND t1.b = t3.b AND t1.b = t4.b); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t2.a = t1.a AND t2.a = t3.a AND t2.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t3.a = t1.a AND t3.a = t2.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t4.a = t1.a AND t4.a = t2.a AND t4.a = t3.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.a = t3.a AND t3.a = t4.a); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2, t3, t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1 CROSS JOIN t2 CROSS JOIN t3 CROSS JOIN t4); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1, t2 CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1 JOIN t2 USING a CROSS JOIN t3); + +SELECT countIf(explain like '%COMMA%' OR explain like '%CROSS%'), countIf(explain like '%INNER%') FROM ( + EXPLAIN QUERY TREE SELECT t1.a FROM t1 JOIN t2 ON t1.a = t2.a CROSS JOIN t3); INSERT INTO t1 values (1,1), (2,2), (3,3), (4,4); INSERT INTO t2 values (1,1), (1, Null); INSERT INTO t3 values (1,1), (1, Null); INSERT INTO t4 values (1,1), (1, Null); +SET allow_experimental_analyzer = 1; + SELECT 'SELECT * FROM t1, t2'; SELECT * FROM t1, t2 ORDER BY t1.a, t2.b; diff --git a/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql b/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql index cdbac93937e..86a8414e799 100644 --- a/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql +++ b/tests/queries/0_stateless/02364_setting_cross_to_inner_rewrite.sql @@ -1,5 +1,3 @@ - - DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference new file mode 100644 index 00000000000..63d28b57a01 --- /dev/null +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference @@ -0,0 +1,10 @@ +5 6 5 6 5 +3 4 3 4 5 +3 4 3 4 7 +3 4 3 4 9 +5 6 5 6 5 +5 6 5 6 7 +5 6 5 6 9 +2 0 +0 2 +1 1 diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql new file mode 100644 index 00000000000..42446252ea7 --- /dev/null +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql @@ -0,0 +1,62 @@ +SET allow_experimental_analyzer = 1; + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; + +CREATE TABLE t1 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t1 VALUES (1, 2), (3, 4), (5, 6); + +CREATE TABLE t2 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t2 VALUES (3, 4), (5, 6), (7, 8); + +CREATE TABLE t3 (a UInt64, b UInt64) ENGINE = Memory; +INSERT INTO t3 VALUES (5, 6), (7, 8), (9, 10); + +SET cross_to_inner_join_rewrite = 1; + +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +; + +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) +ORDER BY t1.a, t2.a, t3.x +; + +-- rewrite two joins +SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( + EXPLAIN QUERY TREE + SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 + WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +) WHERE explain like '% JOIN % kind: %' +SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain +; + +-- setting is disabled +SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( + EXPLAIN QUERY TREE + SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 + WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 + SETTINGS cross_to_inner_join_rewrite = 0 +) WHERE explain like '% JOIN % kind: %' +SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain +; + +-- only one join can be rewritten +SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( + EXPLAIN QUERY TREE + SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 + WHERE t1.a = if(t2.b > 0, t2.a, 0) +) WHERE explain like '% JOIN % kind: %' +SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain +; + +-- throw in force mode +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) +SETTINGS cross_to_inner_join_rewrite = 2; -- { serverError INCORRECT_QUERY } + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; From ba04a3cc1f10df1dad9e40890d0de55d97830390 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 20 Feb 2023 11:49:57 +0000 Subject: [PATCH 100/445] Fixes for CrossToInnerPass --- src/Analyzer/JoinNode.cpp | 7 ++-- src/Analyzer/JoinNode.h | 5 +++ src/Analyzer/Passes/CrossToInnerJoinPass.cpp | 37 ++++++++++---------- src/Analyzer/Passes/CrossToInnerJoinPass.h | 4 +++ 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/Analyzer/JoinNode.cpp b/src/Analyzer/JoinNode.cpp index 86cf7e90c3f..fe4dd2c5016 100644 --- a/src/Analyzer/JoinNode.cpp +++ b/src/Analyzer/JoinNode.cpp @@ -121,14 +121,15 @@ ASTPtr JoinNode::toASTImpl() const void JoinNode::crossToInner(const QueryTreeNodePtr & join_expression_) { if (kind != JoinKind::Cross && kind != JoinKind::Comma) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot rewrite join {} to inner join, expected cross", toString(kind)); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot rewrite {} to INNER JOIN, expected CROSS", toString(kind)); if (children[join_expression_child_index]) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Join expression is not empty: '{}'", children[join_expression_child_index]->formatConvertedASTForErrorMessage()); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Join expression is expected to be empty for CROSS JOIN, got '{}'", + children[join_expression_child_index]->formatConvertedASTForErrorMessage()); kind = JoinKind::Inner; strictness = JoinStrictness::All; - children[join_expression_child_index] = std::move(join_expression_); + children[join_expression_child_index] = join_expression_; } } diff --git a/src/Analyzer/JoinNode.h b/src/Analyzer/JoinNode.h index d1cd64e67b7..0d856985794 100644 --- a/src/Analyzer/JoinNode.h +++ b/src/Analyzer/JoinNode.h @@ -126,6 +126,11 @@ public: return QueryTreeNodeType::JOIN; } + /* + * Convert CROSS to INNER JOIN - changes JOIN kind and sets a new join expression + * (that was moved from WHERE clause). + * Expects the current kind to be CROSS (and join expression to be null because of that). + */ void crossToInner(const QueryTreeNodePtr & join_expression_); void dumpTreeImpl(WriteBuffer & buffer, FormatState & format_state, size_t indent) const override; diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp index dc03e340b15..400c760cd20 100644 --- a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp @@ -25,29 +25,30 @@ namespace ErrorCodes namespace { -using EquiCondition = std::tuple; - void exctractJoinConditions(const QueryTreeNodePtr & node, QueryTreeNodes & equi_conditions, QueryTreeNodes & other) { - if (auto * func = node->as()) + auto * func = node->as(); + if (!func) { - const auto & args = func->getArguments().getNodes(); - - if (args.size() == 2 && func->getFunctionName() == "equals") - { - equi_conditions.push_back(node); - return; - } - - if (func->getFunctionName() == "and") - { - for (auto & arg : args) - exctractJoinConditions(arg, equi_conditions, other); - return; - } + other.push_back(node); + return; } - other.push_back(node); + const auto & args = func->getArguments().getNodes(); + + if (args.size() == 2 && func->getFunctionName() == "equals") + { + equi_conditions.push_back(node); + } + else if (func->getFunctionName() == "and") + { + for (auto & arg : args) + exctractJoinConditions(arg, equi_conditions, other); + } + else + { + other.push_back(node); + } } const QueryTreeNodePtr & getEquiArgument(const QueryTreeNodePtr & cond, size_t index) diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.h b/src/Analyzer/Passes/CrossToInnerJoinPass.h index 8f50f72e3d1..127d26dc41d 100644 --- a/src/Analyzer/Passes/CrossToInnerJoinPass.h +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.h @@ -7,6 +7,10 @@ namespace DB /** Replace CROSS JOIN with INNER JOIN. + * Example: + * SELECT * FROM t1 CROSS JOIN t2 WHERE t1.a = t2.a AND t1.b > 10 AND t2.b = t2.c + * We can move equality condition to ON section of INNER JOIN: + * SELECT * FROM t1 INNER JOIN t2 ON t1.a = t2.a WHERE t1.b > 10 AND t2.b = t2.c */ class CrossToInnerJoinPass final : public IQueryTreePass { From 7cf7d31b4b4b24c98d0f5a16ee940ca7b90483d4 Mon Sep 17 00:00:00 2001 From: vdimir Date: Mon, 20 Feb 2023 11:50:28 +0000 Subject: [PATCH 101/445] Update 02564_analyzer_cross_to_inner --- .../02564_analyzer_cross_to_inner.reference | 201 +++++++++++++++++- .../02564_analyzer_cross_to_inner.sql | 40 ++-- 2 files changed, 212 insertions(+), 29 deletions(-) diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference index 63d28b57a01..e4d7ff55b86 100644 --- a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.reference @@ -5,6 +5,201 @@ 5 6 5 6 5 5 6 5 6 7 5 6 5 6 9 -2 0 -0 2 -1 1 +-- { echoOn } + +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1; +QUERY id: 0 + PROJECTION COLUMNS + t1.a UInt64 + t1.b UInt64 + t2.a UInt64 + t2.b UInt64 + x UInt64 + PROJECTION + LIST id: 1, nodes: 5 + COLUMN id: 2, column_name: a, result_type: UInt64, source_id: 3 + COLUMN id: 4, column_name: b, result_type: UInt64, source_id: 3 + COLUMN id: 5, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 7, column_name: b, result_type: UInt64, source_id: 6 + COLUMN id: 8, column_name: x, result_type: UInt64, source_id: 9 + JOIN TREE + JOIN id: 10, strictness: ALL, kind: INNER + LEFT TABLE EXPRESSION + JOIN id: 11, strictness: ALL, kind: INNER + LEFT TABLE EXPRESSION + TABLE id: 3, table_name: default.t1 + RIGHT TABLE EXPRESSION + TABLE id: 6, table_name: default.t2 + JOIN EXPRESSION + FUNCTION id: 12, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 13, nodes: 2 + COLUMN id: 14, column_name: a, result_type: UInt64, source_id: 3 + FUNCTION id: 15, function_name: if, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 16, nodes: 3 + FUNCTION id: 17, function_name: greater, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 18, nodes: 2 + COLUMN id: 19, column_name: b, result_type: UInt64, source_id: 6 + CONSTANT id: 20, constant_value: UInt64_0, constant_value_type: UInt8 + COLUMN id: 21, column_name: a, result_type: UInt64, source_id: 6 + CONSTANT id: 22, constant_value: UInt64_0, constant_value_type: UInt8 + RIGHT TABLE EXPRESSION + QUERY id: 9, alias: t3, is_subquery: 1 + PROJECTION COLUMNS + x UInt64 + PROJECTION + LIST id: 23, nodes: 1 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + JOIN TREE + TABLE id: 25, table_name: default.t3 + WHERE + FUNCTION id: 26, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 27, nodes: 2 + FUNCTION id: 28, function_name: plus, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 29, nodes: 2 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + CONSTANT id: 30, constant_value: UInt64_1, constant_value_type: UInt8 + COLUMN id: 31, column_name: b, result_type: UInt64, source_id: 25 + JOIN EXPRESSION + FUNCTION id: 32, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 33, nodes: 2 + COLUMN id: 21, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 34, column_name: x, result_type: UInt64, source_id: 9 + WHERE + CONSTANT id: 35, constant_value: UInt64_1, constant_value_type: UInt8 +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +SETTINGS cross_to_inner_join_rewrite = 0; +QUERY id: 0 + PROJECTION COLUMNS + t1.a UInt64 + t1.b UInt64 + t2.a UInt64 + t2.b UInt64 + x UInt64 + PROJECTION + LIST id: 1, nodes: 5 + COLUMN id: 2, column_name: a, result_type: UInt64, source_id: 3 + COLUMN id: 4, column_name: b, result_type: UInt64, source_id: 3 + COLUMN id: 5, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 7, column_name: b, result_type: UInt64, source_id: 6 + COLUMN id: 8, column_name: x, result_type: UInt64, source_id: 9 + JOIN TREE + JOIN id: 10, kind: COMMA + LEFT TABLE EXPRESSION + JOIN id: 11, kind: COMMA + LEFT TABLE EXPRESSION + TABLE id: 3, table_name: default.t1 + RIGHT TABLE EXPRESSION + TABLE id: 6, table_name: default.t2 + RIGHT TABLE EXPRESSION + QUERY id: 9, alias: t3, is_subquery: 1 + PROJECTION COLUMNS + x UInt64 + PROJECTION + LIST id: 12, nodes: 1 + COLUMN id: 13, column_name: a, result_type: UInt64, source_id: 14 + JOIN TREE + TABLE id: 14, table_name: default.t3 + WHERE + FUNCTION id: 15, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 16, nodes: 2 + FUNCTION id: 17, function_name: plus, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 18, nodes: 2 + COLUMN id: 13, column_name: a, result_type: UInt64, source_id: 14 + CONSTANT id: 19, constant_value: UInt64_1, constant_value_type: UInt8 + COLUMN id: 20, column_name: b, result_type: UInt64, source_id: 14 + WHERE + FUNCTION id: 21, function_name: and, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 22, nodes: 3 + FUNCTION id: 23, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 24, nodes: 2 + COLUMN id: 25, column_name: a, result_type: UInt64, source_id: 3 + FUNCTION id: 26, function_name: if, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 27, nodes: 3 + FUNCTION id: 28, function_name: greater, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 29, nodes: 2 + COLUMN id: 30, column_name: b, result_type: UInt64, source_id: 6 + CONSTANT id: 31, constant_value: UInt64_0, constant_value_type: UInt8 + COLUMN id: 32, column_name: a, result_type: UInt64, source_id: 6 + CONSTANT id: 33, constant_value: UInt64_0, constant_value_type: UInt8 + FUNCTION id: 34, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 35, nodes: 2 + COLUMN id: 32, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 36, column_name: x, result_type: UInt64, source_id: 9 + CONSTANT id: 37, constant_value: UInt64_1, constant_value_type: UInt8 + SETTINGS cross_to_inner_join_rewrite=0 +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0); +QUERY id: 0 + PROJECTION COLUMNS + t1.a UInt64 + t1.b UInt64 + t2.a UInt64 + t2.b UInt64 + x UInt64 + PROJECTION + LIST id: 1, nodes: 5 + COLUMN id: 2, column_name: a, result_type: UInt64, source_id: 3 + COLUMN id: 4, column_name: b, result_type: UInt64, source_id: 3 + COLUMN id: 5, column_name: a, result_type: UInt64, source_id: 6 + COLUMN id: 7, column_name: b, result_type: UInt64, source_id: 6 + COLUMN id: 8, column_name: x, result_type: UInt64, source_id: 9 + JOIN TREE + JOIN id: 10, kind: COMMA + LEFT TABLE EXPRESSION + JOIN id: 11, strictness: ALL, kind: INNER + LEFT TABLE EXPRESSION + TABLE id: 3, table_name: default.t1 + RIGHT TABLE EXPRESSION + TABLE id: 6, table_name: default.t2 + JOIN EXPRESSION + FUNCTION id: 12, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 13, nodes: 2 + COLUMN id: 14, column_name: a, result_type: UInt64, source_id: 3 + FUNCTION id: 15, function_name: if, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 16, nodes: 3 + FUNCTION id: 17, function_name: greater, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 18, nodes: 2 + COLUMN id: 19, column_name: b, result_type: UInt64, source_id: 6 + CONSTANT id: 20, constant_value: UInt64_0, constant_value_type: UInt8 + COLUMN id: 21, column_name: a, result_type: UInt64, source_id: 6 + CONSTANT id: 22, constant_value: UInt64_0, constant_value_type: UInt8 + RIGHT TABLE EXPRESSION + QUERY id: 9, alias: t3, is_subquery: 1 + PROJECTION COLUMNS + x UInt64 + PROJECTION + LIST id: 23, nodes: 1 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + JOIN TREE + TABLE id: 25, table_name: default.t3 + WHERE + FUNCTION id: 26, function_name: equals, function_type: ordinary, result_type: UInt8 + ARGUMENTS + LIST id: 27, nodes: 2 + FUNCTION id: 28, function_name: plus, function_type: ordinary, result_type: UInt64 + ARGUMENTS + LIST id: 29, nodes: 2 + COLUMN id: 24, column_name: a, result_type: UInt64, source_id: 25 + CONSTANT id: 30, constant_value: UInt64_1, constant_value_type: UInt8 + COLUMN id: 31, column_name: b, result_type: UInt64, source_id: 25 diff --git a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql index 42446252ea7..a83cd238982 100644 --- a/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql +++ b/tests/queries/0_stateless/02564_analyzer_cross_to_inner.sql @@ -24,35 +24,23 @@ WHERE t1.a = if(t2.b > 0, t2.a, 0) ORDER BY t1.a, t2.a, t3.x ; --- rewrite two joins -SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( - EXPLAIN QUERY TREE - SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 - WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 -) WHERE explain like '% JOIN % kind: %' -SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain -; +-- { echoOn } --- setting is disabled -SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( - EXPLAIN QUERY TREE - SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 - WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 - SETTINGS cross_to_inner_join_rewrite = 0 -) WHERE explain like '% JOIN % kind: %' -SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain -; +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1; --- only one join can be rewritten -SELECT countIf(explain like '%strictness: ALL, %kind: INNER%'), countIf(explain like '%kind: COMMA%') FROM ( - EXPLAIN QUERY TREE - SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 - WHERE t1.a = if(t2.b > 0, t2.a, 0) -) WHERE explain like '% JOIN % kind: %' -SETTINGS allow_experimental_analyzer = 0 -- workaround for viewExplain -; +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0) AND t2.a = t3.x AND 1 +SETTINGS cross_to_inner_join_rewrite = 0; + +EXPLAIN QUERY TREE +SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 +WHERE t1.a = if(t2.b > 0, t2.a, 0); + +-- { echoOff } --- throw in force mode SELECT * FROM t1, t2, (SELECT a as x from t3 where a + 1 = b ) as t3 WHERE t1.a = if(t2.b > 0, t2.a, 0) SETTINGS cross_to_inner_join_rewrite = 2; -- { serverError INCORRECT_QUERY } From 08b0e3c6309f520cdf1dcc97fd205ba5d4ffbd19 Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 21 Feb 2023 18:27:37 +0100 Subject: [PATCH 102/445] Fix style check --- tests/integration/test_named_collections/test.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_named_collections/test.py b/tests/integration/test_named_collections/test.py index 612b894461b..ba62880e9de 100644 --- a/tests/integration/test_named_collections/test.py +++ b/tests/integration/test_named_collections/test.py @@ -102,7 +102,10 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" + assert ( + node.query("select collection['key1'] from system.named_collections").strip() + == "value1" + ) replace_in_users_config( node, "show_named_collections_secrets>1", "show_named_collections_secrets>0" ) @@ -110,7 +113,10 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert node.query("select collection['key1'] from system.named_collections").strip() == "[HIDDEN]" + assert ( + node.query("select collection['key1'] from system.named_collections").strip() + == "[HIDDEN]" + ) replace_in_users_config( node, "show_named_collections_secrets>0", "show_named_collections_secrets>1" ) @@ -118,7 +124,10 @@ def test_access(cluster): ["bash", "-c", f"cat /etc/clickhouse-server/users.d/users.xml"] ) node.restart_clickhouse() - assert node.query("select collection['key1'] from system.named_collections").strip() == "value1" + assert ( + node.query("select collection['key1'] from system.named_collections").strip() + == "value1" + ) def test_config_reload(cluster): From bf9f1663bb73bd969fcb35242b060c7e55a61024 Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 21 Feb 2023 18:15:16 +0000 Subject: [PATCH 103/445] Fix totals and extremes with constants in clickhouse-local --- src/Client/ClientBase.cpp | 4 ++-- .../02556_local_with_totals_and_extremes.reference | 6 ++++++ .../0_stateless/02556_local_with_totals_and_extremes.sh | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference create mode 100755 tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index bc8c43af8c6..96aff9aa304 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -481,14 +481,14 @@ void ClientBase::onLogData(Block & block) void ClientBase::onTotals(Block & block, ASTPtr parsed_query) { initOutputFormat(block, parsed_query); - output_format->setTotals(block); + output_format->setTotals(materializeBlock(block)); } void ClientBase::onExtremes(Block & block, ASTPtr parsed_query) { initOutputFormat(block, parsed_query); - output_format->setExtremes(block); + output_format->setExtremes(materializeBlock(block)); } diff --git a/tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference new file mode 100644 index 00000000000..0b9836e530b --- /dev/null +++ b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.reference @@ -0,0 +1,6 @@ +1,1 + +1,1 + +1,1 +1,1 diff --git a/tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh new file mode 100755 index 00000000000..ef31b3855cd --- /dev/null +++ b/tests/queries/0_stateless/02556_local_with_totals_and_extremes.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_LOCAL -q "SELECT 1, sum(1) with totals format CSV settings extremes=1" + From 1f0ab8d427b7819baf13176234ae94acbd9addd7 Mon Sep 17 00:00:00 2001 From: kssenii Date: Tue, 21 Feb 2023 19:24:01 +0100 Subject: [PATCH 104/445] Hide disk setting arguemtns --- src/Core/Field.h | 6 +- src/Parsers/ASTSetQuery.cpp | 6 +- src/Parsers/FieldFromAST.cpp | 55 +++++++++++++++++++ src/Parsers/FieldFromAST.h | 4 +- ...54_create_table_with_custom_disk.reference | 2 +- 5 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Core/Field.h b/src/Core/Field.h index 95ce43ccd44..2e772a64afc 100644 --- a/src/Core/Field.h +++ b/src/Core/Field.h @@ -108,7 +108,8 @@ struct CustomType { virtual ~CustomTypeImpl() = default; virtual const char * getTypeName() const = 0; - virtual String toString() const = 0; + virtual String toString(bool show_secrets) const = 0; + virtual bool isSecret() const = 0; virtual bool operator < (const CustomTypeImpl &) const = 0; virtual bool operator <= (const CustomTypeImpl &) const = 0; @@ -120,8 +121,9 @@ struct CustomType CustomType() = default; explicit CustomType(std::shared_ptr impl_) : impl(impl_) {} + bool isSecret() const { return impl->isSecret(); } const char * getTypeName() const { return impl->getTypeName(); } - String toString() const { return impl->toString(); } + String toString(bool show_secrets = true) const { return impl->toString(show_secrets); } const CustomTypeImpl & getImpl() { return *impl; } bool operator < (const CustomType & rhs) const { return *impl < *rhs.impl; } diff --git a/src/Parsers/ASTSetQuery.cpp b/src/Parsers/ASTSetQuery.cpp index 26420f4988c..0b8d76dbb89 100644 --- a/src/Parsers/ASTSetQuery.cpp +++ b/src/Parsers/ASTSetQuery.cpp @@ -34,7 +34,11 @@ void ASTSetQuery::formatImpl(const FormatSettings & format, FormatState &, Forma first = false; formatSettingName(change.name, format.ostr); - format.ostr << " = " << applyVisitor(FieldVisitorToString(), change.value); + CustomType custom; + if (!format.show_secrets && change.value.tryGet(custom) && custom.isSecret()) + format.ostr << " = " << custom.toString(false); + else + format.ostr << " = " << applyVisitor(FieldVisitorToString(), change.value); } for (const auto & setting_name : default_settings) diff --git a/src/Parsers/FieldFromAST.cpp b/src/Parsers/FieldFromAST.cpp index 7b7302696ed..5889699c081 100644 --- a/src/Parsers/FieldFromAST.cpp +++ b/src/Parsers/FieldFromAST.cpp @@ -1,10 +1,18 @@ #include +#include +#include +#include +#include +#include +#include + namespace DB { namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int BAD_ARGUMENTS; } Field createFieldFromAST(ASTPtr ast) @@ -17,4 +25,51 @@ Field createFieldFromAST(ASTPtr ast) throw Exception(ErrorCodes::LOGICAL_ERROR, "Method {} not implemented for {}", method, getTypeName()); } +bool FieldFromASTImpl::isSecret() const +{ + return isDiskFunction(ast); +} + +String FieldFromASTImpl::toString(bool show_secrets) const +{ + if (!show_secrets && isDiskFunction(ast)) + { + auto hidden = ast->clone(); + auto & disk_function = assert_cast(*hidden); + auto * disk_function_args_expr = assert_cast(disk_function.arguments.get()); + auto & disk_function_args = disk_function_args_expr->children; + + auto is_secret_arg = [](const std::string & arg_name) + { + return arg_name != "type"; + }; + + for (auto & arg : disk_function_args) + { + auto * setting_function = arg->as(); + if (!setting_function || setting_function->name != "equals") + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected equals function"); + + auto * function_args_expr = assert_cast(setting_function->arguments.get()); + if (!function_args_expr) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected arguments"); + + auto & function_args = function_args_expr->children; + if (function_args.empty()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected non zero number of arguments"); + + auto * key_identifier = function_args[0]->as(); + if (!key_identifier) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Bad format: expected Identifier"); + + const std::string & key = key_identifier->name(); + if (is_secret_arg(key)) + function_args[1] = std::make_shared("[HIDDEN]"); + } + return serializeAST(*hidden); + } + + return serializeAST(*ast); +} + } diff --git a/src/Parsers/FieldFromAST.h b/src/Parsers/FieldFromAST.h index 132f7e3e705..a69c086a170 100644 --- a/src/Parsers/FieldFromAST.h +++ b/src/Parsers/FieldFromAST.h @@ -1,7 +1,6 @@ #pragma once #include #include -#include namespace DB { @@ -13,7 +12,8 @@ struct FieldFromASTImpl : public CustomType::CustomTypeImpl explicit FieldFromASTImpl(ASTPtr ast_) : ast(ast_) {} const char * getTypeName() const override { return name; } - String toString() const override { return serializeAST(*ast); } + String toString(bool show_secrets) const override; + bool isSecret() const override; [[noreturn]] void throwNotImplemented(std::string_view method) const; diff --git a/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference b/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference index 378722b5166..1d8610c59c9 100644 --- a/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference +++ b/tests/queries/0_stateless/02454_create_table_with_custom_disk.reference @@ -6,6 +6,6 @@ ENGINE = MergeTree ORDER BY tuple() SETTINGS disk = disk(type = local, path = \'/var/lib/clickhouse/disks/local/\') 100 -CREATE TABLE default.test\n(\n `a` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS disk = disk(type = local, path = \'/var/lib/clickhouse/disks/local/\'), index_granularity = 8192 +CREATE TABLE default.test\n(\n `a` Int32\n)\nENGINE = MergeTree\nORDER BY tuple()\nSETTINGS disk = disk(type = local, path = \'[HIDDEN]\'), index_granularity = 8192 a Int32 200 From cfef911f0d1be82e9a36edce0234e78438fd33c0 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 22 Feb 2023 00:32:55 +0300 Subject: [PATCH 105/445] Update 01710_normal_projections.sh --- tests/queries/0_stateless/01710_normal_projections.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01710_normal_projections.sh b/tests/queries/0_stateless/01710_normal_projections.sh index 8ee3f41ea28..3f2114b9a2b 100755 --- a/tests/queries/0_stateless/01710_normal_projections.sh +++ b/tests/queries/0_stateless/01710_normal_projections.sh @@ -4,7 +4,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x SETTINGS index_granularity=8192" +$CLICKHOUSE_CLIENT -q "CREATE TABLE test_sort_proj (x UInt32, y UInt32, PROJECTION p (SELECT x, y ORDER BY y)) ENGINE = MergeTree ORDER BY x SETTINGS index_granularity=8192, index_granularity_bytes='10Mi'" $CLICKHOUSE_CLIENT -q "insert into test_sort_proj select number, toUInt32(-number - 1) from numbers(100)" echo "select where x < 10" From c009c2f4cbf7333407734f92780d16c8a0aed908 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 22 Feb 2023 00:50:21 +0300 Subject: [PATCH 106/445] Update test_ttl_move_memory_usage.py --- .../test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py b/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py index 29177b6a67b..5fbe426074f 100644 --- a/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py +++ b/tests/integration/test_s3_zero_copy_ttl/test_ttl_move_memory_usage.py @@ -2,6 +2,12 @@ import time import pytest + +# FIXME This test is too flaky +# https://github.com/ClickHouse/ClickHouse/issues/45887 + +pytestmark = pytest.mark.skip + from helpers.cluster import ClickHouseCluster From cf493d1dfb83e66af712986f6346d8812da7420f Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Tue, 21 Feb 2023 22:36:02 +0000 Subject: [PATCH 107/445] replace settings limit and offset with corresponding expression nodes --- src/Analyzer/QueryTreeBuilder.cpp | 65 +++++++++++++++---- src/Common/SettingsChanges.cpp | 25 +++++++ src/Common/SettingsChanges.h | 7 ++ src/Planner/Planner.cpp | 5 -- .../02667_analyzer_limit_settings.reference | 16 +++++ .../02667_analyzer_limit_settings.sql | 6 ++ 6 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/Analyzer/QueryTreeBuilder.cpp b/src/Analyzer/QueryTreeBuilder.cpp index 57b1ae1c994..e9e084615ca 100644 --- a/src/Analyzer/QueryTreeBuilder.cpp +++ b/src/Analyzer/QueryTreeBuilder.cpp @@ -233,22 +233,43 @@ QueryTreeNodePtr QueryTreeBuilder::buildSelectExpression(const ASTPtr & select_q auto select_settings = select_query_typed.settings(); SettingsChanges settings_changes; - if (is_subquery) + /// We are going to remove settings LIMIT and OFFSET and + /// further replace them with corresponding expression nodes + UInt64 limit = 0; + UInt64 offset = 0; + + /// remove global settings limit and offset + if (const Settings & settings_ref = updated_context->getSettingsRef(); settings_ref.limit || settings_ref.offset) { - if (const Settings & settings_ref = updated_context->getSettingsRef(); settings_ref.limit || settings_ref.offset) - { - Settings settings = updated_context->getSettings(); - settings.limit = 0; - settings.offset = 0; - updated_context->setSettings(settings); - } + Settings settings = updated_context->getSettings(); + limit = settings.limit; + offset = settings.offset; + settings.limit = 0; + settings.offset = 0; + updated_context->setSettings(settings); } if (select_settings) { auto & set_query = select_settings->as(); - updated_context->applySettingsChanges(set_query.changes); - settings_changes = set_query.changes; + + /// remove expression settings limit and offset + if (auto * limit_field = set_query.changes.tryGet("limit")) + { + limit = limit_field->safeGet(); + set_query.changes.removeSetting("limit"); + } + if (auto * offset_field = set_query.changes.tryGet("offset")) + { + offset = offset_field->safeGet(); + set_query.changes.removeSetting("offset"); + } + + if (!set_query.changes.empty()) + { + updated_context->applySettingsChanges(set_query.changes); + settings_changes = set_query.changes; + } } auto current_query_tree = std::make_shared(std::move(updated_context), std::move(settings_changes)); @@ -334,12 +355,32 @@ QueryTreeNodePtr QueryTreeBuilder::buildSelectExpression(const ASTPtr & select_q if (select_limit_by) current_query_tree->getLimitByNode() = buildExpressionList(select_limit_by, current_context); + /// combine limit expression with limit setting auto select_limit = select_query_typed.limitLength(); - if (select_limit) + if (select_limit && limit) + { + auto function_node = std::make_shared("least"); + function_node->getArguments().getNodes().push_back(buildExpression(select_limit, current_context)); + function_node->getArguments().getNodes().push_back(std::make_shared(limit)); + current_query_tree->getLimit() = function_node; + } + else if (limit) + current_query_tree->getLimit() = std::make_shared(limit); + else if (select_limit) current_query_tree->getLimit() = buildExpression(select_limit, current_context); + /// combine offset expression with offset setting auto select_offset = select_query_typed.limitOffset(); - if (select_offset) + if (select_offset && offset) + { + auto function_node = std::make_shared("plus"); + function_node->getArguments().getNodes().push_back(buildExpression(select_offset, current_context)); + function_node->getArguments().getNodes().push_back(std::make_shared(offset)); + current_query_tree->getOffset() = function_node; + } + else if (offset) + current_query_tree->getOffset() = std::make_shared(offset); + else if (select_offset) current_query_tree->getOffset() = buildExpression(select_offset, current_context); return current_query_tree; diff --git a/src/Common/SettingsChanges.cpp b/src/Common/SettingsChanges.cpp index 9fb4f361e09..45490f86abc 100644 --- a/src/Common/SettingsChanges.cpp +++ b/src/Common/SettingsChanges.cpp @@ -46,4 +46,29 @@ Field * SettingsChanges::tryGet(std::string_view name) return &change->value; } +bool SettingsChanges::insertSetting(std::string_view name, const Field & value) +{ + if (std::find_if(begin(), end(), [&name](const SettingChange & change) { return change.name == name; }) != end()) + return false; + emplace_back(name, value); + return true; +} + +void SettingsChanges::setSetting(std::string_view name, const Field & value) +{ + if (auto * v = tryGet(name)) + *v = value; + else + insertSetting(name, value); +} + +bool SettingsChanges::removeSetting(std::string_view name) +{ + auto it = std::find_if(begin(), end(), [&name](const SettingChange & change) { return change.name == name; }); + if (it == end()) + return false; + erase(it); + return true; +} + } diff --git a/src/Common/SettingsChanges.h b/src/Common/SettingsChanges.h index 776dacb93e8..d16934dbc76 100644 --- a/src/Common/SettingsChanges.h +++ b/src/Common/SettingsChanges.h @@ -28,6 +28,13 @@ public: bool tryGet(std::string_view name, Field & out_value) const; const Field * tryGet(std::string_view name) const; Field * tryGet(std::string_view name); + + /// inserts element if doesn't exists and returns true, else just returns false + bool insertSetting(std::string_view name, const Field & value); + /// sets element to value, inserts if doesn't exist + void setSetting(std::string_view name, const Field & value); + /// if element exists - removes it and returns true, else returns false + bool removeSetting(std::string_view name); }; } diff --git a/src/Planner/Planner.cpp b/src/Planner/Planner.cpp index 7c2a3ffed78..307e8d73b29 100644 --- a/src/Planner/Planner.cpp +++ b/src/Planner/Planner.cpp @@ -216,17 +216,12 @@ public: limit_length = query_node.getLimit()->as().getValue().safeGet(); } - if (settings.limit) - limit_length = limit_length ? std::min(limit_length, settings.limit.value) : settings.limit; - if (query_node.hasOffset()) { /// Constness of offset is validated during query analysis stage limit_offset = query_node.getOffset()->as().getValue().safeGet(); } - limit_offset += settings.offset; - /// Partial sort can be done if there is LIMIT, but no DISTINCT, LIMIT WITH TIES, LIMIT BY, ARRAY JOIN if (limit_length != 0 && !query_node.isDistinct() && diff --git a/tests/queries/0_stateless/02667_analyzer_limit_settings.reference b/tests/queries/0_stateless/02667_analyzer_limit_settings.reference index 9e38ed9a59b..6f23097612e 100644 --- a/tests/queries/0_stateless/02667_analyzer_limit_settings.reference +++ b/tests/queries/0_stateless/02667_analyzer_limit_settings.reference @@ -52,3 +52,19 @@ SELECT count(*) FROM view(SELECT * FROM numbers(10) SETTINGS limit=5); 5 SELECT count(*) FROM view(SELECT * FROM numbers(10)) SETTINGS limit=5; 10 +SET limit = 4; +SET offset = 1; +SELECT * FROM numbers(10); +1 +2 +3 +4 +SELECT * FROM numbers(10) LIMIT 3 OFFSET 2; +3 +4 +5 +SELECT * FROM numbers(10) LIMIT 5 OFFSET 2; +3 +4 +5 +6 diff --git a/tests/queries/0_stateless/02667_analyzer_limit_settings.sql b/tests/queries/0_stateless/02667_analyzer_limit_settings.sql index 35dd65ab33e..7c02c2d0d20 100644 --- a/tests/queries/0_stateless/02667_analyzer_limit_settings.sql +++ b/tests/queries/0_stateless/02667_analyzer_limit_settings.sql @@ -21,4 +21,10 @@ SELECT count(*) FROM (SELECT * FROM numbers(10)) SETTINGS limit=5; SELECT count(*) FROM view(SELECT * FROM numbers(10)); SELECT count(*) FROM view(SELECT * FROM numbers(10) SETTINGS limit=5); SELECT count(*) FROM view(SELECT * FROM numbers(10)) SETTINGS limit=5; + +SET limit = 4; +SET offset = 1; +SELECT * FROM numbers(10); +SELECT * FROM numbers(10) LIMIT 3 OFFSET 2; +SELECT * FROM numbers(10) LIMIT 5 OFFSET 2; -- { echoOff } From 0bf0fe488eb4b6637e863c95c2ed8f07c4509ec8 Mon Sep 17 00:00:00 2001 From: AVMusorin Date: Sun, 19 Feb 2023 21:09:40 +0100 Subject: [PATCH 108/445] added last_exception_time column into distribution_queue table --- src/Storages/Distributed/DirectoryMonitor.cpp | 1 + src/Storages/Distributed/DirectoryMonitor.h | 1 + src/Storages/System/StorageSystemDistributionQueue.cpp | 2 ++ .../0_stateless/01555_system_distribution_queue_mask.reference | 2 +- .../0_stateless/01555_system_distribution_queue_mask.sql | 2 +- .../0_stateless/02117_show_create_table_system.reference | 3 ++- 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Storages/Distributed/DirectoryMonitor.cpp b/src/Storages/Distributed/DirectoryMonitor.cpp index 7aa7aac2ef3..cb6659e59ce 100644 --- a/src/Storages/Distributed/DirectoryMonitor.cpp +++ b/src/Storages/Distributed/DirectoryMonitor.cpp @@ -465,6 +465,7 @@ void StorageDistributedDirectoryMonitor::run() tryLogCurrentException(getLoggerName().data()); status.last_exception = std::current_exception(); + status.last_exception_time = std::chrono::system_clock::now(); } } else diff --git a/src/Storages/Distributed/DirectoryMonitor.h b/src/Storages/Distributed/DirectoryMonitor.h index 7015fca0311..030d6acf6e2 100644 --- a/src/Storages/Distributed/DirectoryMonitor.h +++ b/src/Storages/Distributed/DirectoryMonitor.h @@ -58,6 +58,7 @@ public: struct InternalStatus { std::exception_ptr last_exception; + std::chrono::system_clock::time_point last_exception_time; size_t error_count = 0; diff --git a/src/Storages/System/StorageSystemDistributionQueue.cpp b/src/Storages/System/StorageSystemDistributionQueue.cpp index 5297c4eb93c..34cff7df65d 100644 --- a/src/Storages/System/StorageSystemDistributionQueue.cpp +++ b/src/Storages/System/StorageSystemDistributionQueue.cpp @@ -101,6 +101,7 @@ NamesAndTypesList StorageSystemDistributionQueue::getNamesAndTypes() { "broken_data_files", std::make_shared() }, { "broken_data_compressed_bytes", std::make_shared() }, { "last_exception", std::make_shared() }, + { "last_exception_time", std::make_shared() }, }; } @@ -190,6 +191,7 @@ void StorageSystemDistributionQueue::fillData(MutableColumns & res_columns, Cont res_columns[col_num++]->insert(getExceptionMessage(status.last_exception, false)); else res_columns[col_num++]->insertDefault(); + res_columns[col_num++]->insert(static_cast(std::chrono::system_clock::to_time_t(status.last_exception_time))); } } } diff --git a/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference b/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference index bd0eac10816..745160a517e 100644 --- a/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference +++ b/tests/queries/0_stateless/01555_system_distribution_queue_mask.reference @@ -1,4 +1,4 @@ masked -3,"default:*@127%2E0%2E0%2E1:9000,default:*@127%2E0%2E0%2E2:9000" +3,"default:*@127%2E0%2E0%2E1:9000,default:*@127%2E0%2E0%2E2:9000","AUTHENTICATION_FAILED",1 no masking 1,"default@localhost:9000" diff --git a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql index bdcde1adbad..285e93a4f90 100644 --- a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql +++ b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql @@ -18,7 +18,7 @@ create table dist_01555 (key Int) Engine=Distributed(test_cluster_with_incorrect insert into dist_01555 values (1)(2); -- since test_cluster_with_incorrect_pw contains incorrect password ignore error system flush distributed dist_01555; -- { serverError 516; } -select length(splitByChar('*', data_path)), replaceRegexpOne(data_path, '^.*/([^/]*)/' , '\\1') from system.distribution_queue where database = currentDatabase() and table = 'dist_01555' format CSV; +select length(splitByChar('*', data_path)), replaceRegexpOne(data_path, '^.*/([^/]*)/' , '\\1'), extract(last_exception, 'AUTHENTICATION_FAILED'), dateDiff('s', last_exception_time, now()) < 5 from system.distribution_queue where database = currentDatabase() and table = 'dist_01555' format CSV; drop table dist_01555; diff --git a/tests/queries/0_stateless/02117_show_create_table_system.reference b/tests/queries/0_stateless/02117_show_create_table_system.reference index aabe05ea5e2..1840c5aa5a3 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -229,7 +229,8 @@ CREATE TABLE system.distribution_queue `data_compressed_bytes` UInt64, `broken_data_files` UInt64, `broken_data_compressed_bytes` UInt64, - `last_exception` String + `last_exception` String, + `last_exception_time` DateTime ) ENGINE = SystemDistributionQueue COMMENT 'SYSTEM TABLE is built on the fly.' From f93b95c6ab771d1acb6fd01f13fc18ed8598c34c Mon Sep 17 00:00:00 2001 From: "ducle.canh" Date: Wed, 22 Feb 2023 10:32:47 +0800 Subject: [PATCH 109/445] fix aggre arithmetic operations in aggregate optimization --- ...egateFunctionsArithmericOperationsPass.cpp | 41 ++++++++++--------- .../ArithmeticOperationsInAgrFuncOptimize.cpp | 19 ++++++--- ...nctions_arithmetic_operators_bug.reference | 2 + ...ate_functions_arithmetic_operators_bug.sql | 2 + 4 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference create mode 100644 tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql diff --git a/src/Analyzer/Passes/AggregateFunctionsArithmericOperationsPass.cpp b/src/Analyzer/Passes/AggregateFunctionsArithmericOperationsPass.cpp index df96d83316c..1476a66c892 100644 --- a/src/Analyzer/Passes/AggregateFunctionsArithmericOperationsPass.cpp +++ b/src/Analyzer/Passes/AggregateFunctionsArithmericOperationsPass.cpp @@ -102,19 +102,21 @@ public: if (!left_argument_constant_node && !right_argument_constant_node) return; - /** If we extract negative constant, aggregate function name must be updated. + /** Need reverse max <-> min for: * - * Example: SELECT min(-1 * id); - * Result: SELECT -1 * max(id); + * max(-1*value) -> -1*min(value) + * max(value/-2) -> min(value)/-2 + * max(1-value) -> 1-min(value) */ - std::string aggregate_function_name_if_constant_is_negative; - if (arithmetic_function_name == "multiply" || arithmetic_function_name == "divide") + auto get_reverse_aggregate_function_name = [](const std::string & aggregate_function_name) -> std::string { - if (lower_aggregate_function_name == "min") - aggregate_function_name_if_constant_is_negative = "max"; - else if (lower_aggregate_function_name == "max") - aggregate_function_name_if_constant_is_negative = "min"; - } + if (aggregate_function_name == "min") + return "max"; + else if (aggregate_function_name == "max") + return "min"; + else + return aggregate_function_name; + }; size_t arithmetic_function_argument_index = 0; @@ -126,11 +128,11 @@ public: /// Rewrite `aggregate_function(inner_function(constant, argument))` into `inner_function(constant, aggregate_function(argument))` const auto & left_argument_constant_value_literal = left_argument_constant_node->getValue(); - if (!aggregate_function_name_if_constant_is_negative.empty() && - left_argument_constant_value_literal < zeroField(left_argument_constant_value_literal)) - { - lower_aggregate_function_name = aggregate_function_name_if_constant_is_negative; - } + bool need_reverse = (arithmetic_function_name == "multiply" && left_argument_constant_value_literal < zeroField(left_argument_constant_value_literal)) + || (arithmetic_function_name == "minus"); + + if (need_reverse) + lower_aggregate_function_name = get_reverse_aggregate_function_name(lower_aggregate_function_name); arithmetic_function_argument_index = 1; } @@ -138,11 +140,10 @@ public: { /// Rewrite `aggregate_function(inner_function(argument, constant))` into `inner_function(aggregate_function(argument), constant)` const auto & right_argument_constant_value_literal = right_argument_constant_node->getValue(); - if (!aggregate_function_name_if_constant_is_negative.empty() && - right_argument_constant_value_literal < zeroField(right_argument_constant_value_literal)) - { - lower_aggregate_function_name = aggregate_function_name_if_constant_is_negative; - } + bool need_reverse = (arithmetic_function_name == "multiply" || arithmetic_function_name == "divide") && right_argument_constant_value_literal < zeroField(right_argument_constant_value_literal); + + if (need_reverse) + lower_aggregate_function_name = get_reverse_aggregate_function_name(lower_aggregate_function_name); arithmetic_function_argument_index = 0; } diff --git a/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp b/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp index 66e0813b977..fc4fa166f3d 100644 --- a/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp +++ b/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp @@ -73,11 +73,11 @@ Field zeroField(const Field & value) throw Exception(ErrorCodes::BAD_TYPE_OF_FIELD, "Unexpected literal type in function"); } -const String & changeNameIfNeeded(const String & func_name, const String & child_name, const ASTLiteral & literal) +const String & changeNameIfNeeded(const String & func_name, const String & child_name, bool need_reverse) { static const std::unordered_map> matches = { - { "min", { "multiply", "divide" } }, - { "max", { "multiply", "divide" } } + { "min", { "multiply", "divide", "minus" } }, + { "max", { "multiply", "divide", "minus" } } }; static const std::unordered_map swap_to = { @@ -85,7 +85,7 @@ const String & changeNameIfNeeded(const String & func_name, const String & child { "max", "min" } }; - if (literal.value < zeroField(literal.value) && matches.contains(func_name) && matches.find(func_name)->second.contains(child_name)) + if (need_reverse && matches.contains(func_name) && matches.find(func_name)->second.contains(child_name)) return swap_to.find(func_name)->second; return func_name; @@ -119,13 +119,20 @@ ASTPtr tryExchangeFunctions(const ASTFunction & func) /// It's possible to rewrite 'sum(1/n)' with 'sum(1) * div(1/n)' but we lose accuracy. Ignored. if (child_func->name == "divide") return {}; + /** Need reverse max <-> min for: + * + * max(-1*value) -> -1*min(value) + * max(value/-2) -> min(value)/-2 + * max(1-value) -> 1 - min(value) + */ + bool need_reverse = first_literal->value < zeroField(first_literal->value) || ((lower_name == "min" || lower_name == "max") && child_func->name == "minus"); - const String & new_name = changeNameIfNeeded(lower_name, child_func->name, *first_literal); + const String & new_name = changeNameIfNeeded(lower_name, child_func->name, need_reverse); optimized_ast = exchangeExtractFirstArgument(new_name, *child_func); } else if (second_literal) /// second or both are consts { - const String & new_name = changeNameIfNeeded(lower_name, child_func->name, *second_literal); + const String & new_name = changeNameIfNeeded(lower_name, child_func->name, second_literal->value < zeroField(second_literal->value)); optimized_ast = exchangeExtractSecondArgument(new_name, *child_func); } diff --git a/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference b/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference new file mode 100644 index 00000000000..53c33afbf47 --- /dev/null +++ b/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference @@ -0,0 +1,2 @@ +100 99 +100 99 diff --git a/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql b/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql new file mode 100644 index 00000000000..15d2115d84d --- /dev/null +++ b/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql @@ -0,0 +1,2 @@ +SELECT max(100-number), min(100-number) FROM numbers(2) SETTINGS optimize_arithmetic_operations_in_aggregate_functions = 1; +SELECT max(100-number), min(100-number) FROM numbers(2) SETTINGS optimize_arithmetic_operations_in_aggregate_functions = 1, allow_experimental_analyzer = 1; From ef33d11e3fbc9c8eebf6ed1b2dfd40eac9a32a75 Mon Sep 17 00:00:00 2001 From: HarryLeeIBM Date: Tue, 21 Feb 2023 18:40:11 -0800 Subject: [PATCH 110/445] Refactor code according to code review --- src/Functions/FunctionsHashing.h | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/Functions/FunctionsHashing.h b/src/Functions/FunctionsHashing.h index 6bf1a2db3ac..59d573df3d1 100644 --- a/src/Functions/FunctionsHashing.h +++ b/src/Functions/FunctionsHashing.h @@ -1025,27 +1025,19 @@ private: if constexpr (Impl::use_int_hash_for_pods) { - if constexpr (std::endian::native == std::endian::little) + if constexpr (std::is_same_v) { - if constexpr (std::is_same_v) - h = IntHash64Impl::apply(bit_cast(vec_from[i])); - else - h = IntHash32Impl::apply(bit_cast(vec_from[i])); + UInt64 v = bit_cast(vec_from[i]); + if constexpr (std::endian::native == std::endian::big) + v = __builtin_bswap64(v); + h = IntHash64Impl::apply(v); } else { - if constexpr (std::is_same_v) - { - UInt64 v = bit_cast(vec_from[i]); - v = __builtin_bswap64(v); - h = IntHash64Impl::apply(v); - } - else - { - UInt32 v = bit_cast(vec_from[i]); + UInt32 v = bit_cast(vec_from[i]); + if constexpr (std::endian::native == std::endian::big) v = __builtin_bswap32(v); - h = IntHash32Impl::apply(v); - } + h = IntHash32Impl::apply(v); } } else From 6d70e97d01ae04dc8cb24ade4514c33328be491c Mon Sep 17 00:00:00 2001 From: "ducle.canh" Date: Wed, 22 Feb 2023 13:30:57 +0800 Subject: [PATCH 111/445] fix logic + better test --- .../ArithmeticOperationsInAgrFuncOptimize.cpp | 56 +++++++++---------- ...tic_operations_in_aggr_func_long.reference | 52 ++++++++--------- ...nctions_arithmetic_operators_bug.reference | 2 - ...ate_functions_arithmetic_operators_bug.sql | 2 - ...s_arithmetic_operations_pass_fix.reference | 1 + ...nctions_arithmetic_operations_pass_fix.sql | 2 + 6 files changed, 56 insertions(+), 59 deletions(-) delete mode 100644 tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference delete mode 100644 tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql diff --git a/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp b/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp index fc4fa166f3d..ef3e6739f8a 100644 --- a/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp +++ b/src/Interpreters/ArithmeticOperationsInAgrFuncOptimize.cpp @@ -73,24 +73,6 @@ Field zeroField(const Field & value) throw Exception(ErrorCodes::BAD_TYPE_OF_FIELD, "Unexpected literal type in function"); } -const String & changeNameIfNeeded(const String & func_name, const String & child_name, bool need_reverse) -{ - static const std::unordered_map> matches = { - { "min", { "multiply", "divide", "minus" } }, - { "max", { "multiply", "divide", "minus" } } - }; - - static const std::unordered_map swap_to = { - { "min", "max" }, - { "max", "min" } - }; - - if (need_reverse && matches.contains(func_name) && matches.find(func_name)->second.contains(child_name)) - return swap_to.find(func_name)->second; - - return func_name; -} - ASTPtr tryExchangeFunctions(const ASTFunction & func) { static const std::unordered_map> supported @@ -114,26 +96,42 @@ ASTPtr tryExchangeFunctions(const ASTFunction & func) ASTPtr optimized_ast; + /** Need reverse max <-> min for: + * + * max(-1*value) -> -1*min(value) + * max(value/-2) -> min(value)/-2 + * max(1-value) -> 1-min(value) + */ + auto get_reverse_aggregate_function_name = [](const std::string & aggregate_function_name) -> std::string + { + if (aggregate_function_name == "min") + return "max"; + else if (aggregate_function_name == "max") + return "min"; + else + return aggregate_function_name; + }; + if (first_literal && !second_literal) { /// It's possible to rewrite 'sum(1/n)' with 'sum(1) * div(1/n)' but we lose accuracy. Ignored. if (child_func->name == "divide") return {}; - /** Need reverse max <-> min for: - * - * max(-1*value) -> -1*min(value) - * max(value/-2) -> min(value)/-2 - * max(1-value) -> 1 - min(value) - */ - bool need_reverse = first_literal->value < zeroField(first_literal->value) || ((lower_name == "min" || lower_name == "max") && child_func->name == "minus"); + bool need_reverse + = (child_func->name == "multiply" && first_literal->value < zeroField(first_literal->value)) || child_func->name == "minus"; + if (need_reverse) + lower_name = get_reverse_aggregate_function_name(lower_name); - const String & new_name = changeNameIfNeeded(lower_name, child_func->name, need_reverse); - optimized_ast = exchangeExtractFirstArgument(new_name, *child_func); + optimized_ast = exchangeExtractFirstArgument(lower_name, *child_func); } else if (second_literal) /// second or both are consts { - const String & new_name = changeNameIfNeeded(lower_name, child_func->name, second_literal->value < zeroField(second_literal->value)); - optimized_ast = exchangeExtractSecondArgument(new_name, *child_func); + bool need_reverse + = (child_func->name == "multiply" || child_func->name == "divide") && second_literal->value < zeroField(second_literal->value); + if (need_reverse) + lower_name = get_reverse_aggregate_function_name(lower_name); + + optimized_ast = exchangeExtractSecondArgument(lower_name, *child_func); } if (optimized_ast) diff --git a/tests/queries/0_stateless/01271_optimize_arithmetic_operations_in_aggr_func_long.reference b/tests/queries/0_stateless/01271_optimize_arithmetic_operations_in_aggr_func_long.reference index b50519b9b3a..ea04f155f24 100644 --- a/tests/queries/0_stateless/01271_optimize_arithmetic_operations_in_aggr_func_long.reference +++ b/tests/queries/0_stateless/01271_optimize_arithmetic_operations_in_aggr_func_long.reference @@ -22,7 +22,7 @@ SELECT min(n) + 1, 1 + min(n), min(n) - 1, - 1 - min(n) + 1 - max(n) FROM ( SELECT number AS n @@ -42,7 +42,7 @@ SELECT max(n) + 1, 1 + max(n), max(n) - 1, - 1 - max(n) + 1 - min(n) FROM ( SELECT number AS n @@ -82,7 +82,7 @@ SELECT min(n) + -1, -1 + min(n), min(n) - -1, - -1 - min(n) + -1 - max(n) FROM ( SELECT number AS n @@ -102,7 +102,7 @@ SELECT max(n) + -1, -1 + max(n), max(n) - -1, - -1 - max(n) + -1 - min(n) FROM ( SELECT number AS n @@ -142,7 +142,7 @@ SELECT min(abs(2)) + 1, min(abs(2) + n), min(n - abs(2)), - 1 - min(abs(2)) + 1 - max(abs(2)) FROM ( SELECT number AS n @@ -162,7 +162,7 @@ SELECT max(abs(2)) + 1, max(abs(2) + n), max(n - abs(2)), - 1 - max(abs(2)) + 1 - min(abs(2)) FROM ( SELECT number AS n @@ -202,7 +202,7 @@ SELECT min(abs(n)) + 1, min(abs(n) + n), min(n - abs(n)), - 1 - min(abs(n)) + 1 - max(abs(n)) FROM ( SELECT number AS n @@ -222,7 +222,7 @@ SELECT max(abs(n)) + 1, max(abs(n) + n), max(n - abs(n)), - 1 - max(abs(n)) + 1 - min(abs(n)) FROM ( SELECT number AS n @@ -262,7 +262,7 @@ SELECT min(n * n) + 1, 1 + min(n * n), min(n * n) - 1, - 1 - min(n * n) + 1 - max(n * n) FROM ( SELECT number AS n @@ -282,7 +282,7 @@ SELECT max(n * n) + 1, 1 + max(n * n), max(n * n) - 1, - 1 - max(n * n) + 1 - min(n * n) FROM ( SELECT number AS n @@ -382,7 +382,7 @@ SELECT (min(n) + -1) + -1, (-1 + min(n)) + -1, (min(n) - -1) + -1, - (-1 - min(n)) + -1 + (-1 - max(n)) + -1 FROM ( SELECT number AS n @@ -402,7 +402,7 @@ SELECT (max(n) + -1) + -1, (-1 + max(n)) + -1, (max(n) - -1) + -1, - (-1 - max(n)) + -1 + (-1 - min(n)) + -1 FROM ( SELECT number AS n @@ -430,7 +430,7 @@ FROM SELECT number AS n FROM numbers(10) ) -SELECT (((min(n) + 1) + (1 + min(n))) + (min(n) - 1)) + (1 - min(n)) +SELECT (((min(n) + 1) + (1 + min(n))) + (min(n) - 1)) + (1 - max(n)) FROM ( SELECT number AS n @@ -442,7 +442,7 @@ FROM SELECT number AS n FROM numbers(10) ) -SELECT (((max(n) + 1) + (1 + max(n))) + (max(n) - 1)) + (1 - max(n)) +SELECT (((max(n) + 1) + (1 + max(n))) + (max(n) - 1)) + (1 - min(n)) FROM ( SELECT number AS n @@ -456,15 +456,15 @@ FROM ) 55 55 35 -35 90 90 22.5 inf -1 1 -1 1 +1 1 -1 -8 0 0 0 0.1111111111111111 -10 10 8 -8 +10 10 8 1 18 18 4.5 inf 35 35 55 -55 -90 -90 -22.5 -inf --1 -1 1 -1 +-1 -1 1 -10 -18 -18 -4.5 -inf -8 8 10 -10 +8 8 10 -1 0 0 -0 -0.1111111111111111 30 65 25 -10 40 90 22.5 5 @@ -474,15 +474,15 @@ FROM 4 18 4.5 0.5 55 90 0 -35 90 285 nan inf -1 0 0 1 +1 0 0 -8 0 0 nan 0.1111111111111111 -10 18 0 -8 +10 18 0 1 18 81 nan inf 295 295 275 -275 570 570 142.5 nan -1 1 -1 1 +1 1 -1 -80 0 0 0 nan -82 82 80 -80 +82 82 80 1 162 162 40.5 nan 65 65 45 -25 100 100 32.5 inf @@ -492,15 +492,15 @@ FROM 19 19 5.5 inf 25 25 45 -65 90 90 22.5 inf --2 -2 0 -2 +-2 -2 0 -11 0 0 0 0.1111111111111111 -7 7 9 -11 +7 7 9 -2 18 18 4.5 inf 110 inf -2 +-7 0.1111111111111111 -20 +29 inf -15444 68.62157087543459 diff --git a/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference b/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference deleted file mode 100644 index 53c33afbf47..00000000000 --- a/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.reference +++ /dev/null @@ -1,2 +0,0 @@ -100 99 -100 99 diff --git a/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql b/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql deleted file mode 100644 index 15d2115d84d..00000000000 --- a/tests/queries/0_stateless/02498_aggregate_functions_arithmetic_operators_bug.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT max(100-number), min(100-number) FROM numbers(2) SETTINGS optimize_arithmetic_operations_in_aggregate_functions = 1; -SELECT max(100-number), min(100-number) FROM numbers(2) SETTINGS optimize_arithmetic_operations_in_aggregate_functions = 1, allow_experimental_analyzer = 1; diff --git a/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.reference b/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.reference index 4f9430ef608..43282d09bab 100644 --- a/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.reference +++ b/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.reference @@ -1 +1,2 @@ 4 2 +100 99 diff --git a/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.sql b/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.sql index e3e508e17be..72be274b222 100644 --- a/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.sql +++ b/tests/queries/0_stateless/02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix.sql @@ -12,3 +12,5 @@ INSERT INTO test_table VALUES (1, 1); INSERT INTO test_table VALUES (1, 1); SELECT sum((2 * id) as func), func FROM test_table GROUP BY id; + +SELECT max(100-number), min(100-number) FROM numbers(2); \ No newline at end of file From 2ca47a6eb60ba886f689122ab6e8b22b2d2bbb84 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 22 Feb 2023 10:41:57 +0100 Subject: [PATCH 112/445] BackgroundSchedulePool should not have any query context BackgroundSchedulePool is used for some peridic jobs, not from the query context, i.e. flush of Buffer table. And for such jobs there cannot be any query context, and more importantly it will not work correctly since that query_context will eventually expires. And this is the reason of this failures [1]. [1]: https://s3.amazonaws.com/clickhouse-test-reports/46668/015991bc5e20c787851050c2eaa13f0fef3aac00/stateless_tests_flaky_check__asan_.html Signed-off-by: Azat Khuzhin --- src/Core/BackgroundSchedulePool.cpp | 28 ------------------- src/Core/BackgroundSchedulePool.h | 5 ---- src/Interpreters/ConcurrentHashJoin.h | 1 - .../MergeTree/MergeTreePrefetchedReadPool.h | 1 - 4 files changed, 35 deletions(-) diff --git a/src/Core/BackgroundSchedulePool.cpp b/src/Core/BackgroundSchedulePool.cpp index 165d8902e85..993cfb6ef04 100644 --- a/src/Core/BackgroundSchedulePool.cpp +++ b/src/Core/BackgroundSchedulePool.cpp @@ -252,36 +252,10 @@ void BackgroundSchedulePool::cancelDelayedTask(const TaskInfoPtr & task, std::lo } -scope_guard BackgroundSchedulePool::attachToThreadGroup() -{ - scope_guard guard = [&]() - { - if (thread_group) - CurrentThread::detachQueryIfNotDetached(); - }; - - std::lock_guard lock(delayed_tasks_mutex); - - if (thread_group) - { - /// Put all threads to one thread pool - CurrentThread::attachTo(thread_group); - } - else - { - CurrentThread::initializeQuery(); - thread_group = CurrentThread::getGroup(); - } - return guard; -} - - void BackgroundSchedulePool::threadFunction() { setThreadName(thread_name.c_str()); - auto detach_thread_guard = attachToThreadGroup(); - while (!shutdown) { TaskInfoPtr task; @@ -311,8 +285,6 @@ void BackgroundSchedulePool::delayExecutionThreadFunction() { setThreadName((thread_name + "/D").c_str()); - auto detach_thread_guard = attachToThreadGroup(); - while (!shutdown) { TaskInfoPtr task; diff --git a/src/Core/BackgroundSchedulePool.h b/src/Core/BackgroundSchedulePool.h index ba1be312f27..0fb70b1f715 100644 --- a/src/Core/BackgroundSchedulePool.h +++ b/src/Core/BackgroundSchedulePool.h @@ -90,13 +90,8 @@ private: /// Tasks ordered by scheduled time. DelayedTasks delayed_tasks; - /// Thread group used for profiling purposes - ThreadGroupStatusPtr thread_group; - CurrentMetrics::Metric tasks_metric; std::string thread_name; - - [[nodiscard]] scope_guard attachToThreadGroup(); }; diff --git a/src/Interpreters/ConcurrentHashJoin.h b/src/Interpreters/ConcurrentHashJoin.h index a00c3ed1326..5e53f9845aa 100644 --- a/src/Interpreters/ConcurrentHashJoin.h +++ b/src/Interpreters/ConcurrentHashJoin.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include diff --git a/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h b/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h index bad158cd7a7..98cfe28c563 100644 --- a/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h +++ b/src/Storages/MergeTree/MergeTreePrefetchedReadPool.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include From d4bb84e68b6b91e9168df1555ecd08ecf53fb547 Mon Sep 17 00:00:00 2001 From: vdimir Date: Wed, 22 Feb 2023 09:56:10 +0000 Subject: [PATCH 113/445] make clang-tidy happy about CrossToInnerJoinPass --- src/Analyzer/Passes/CrossToInnerJoinPass.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp index 400c760cd20..0c4fa0ed82f 100644 --- a/src/Analyzer/Passes/CrossToInnerJoinPass.cpp +++ b/src/Analyzer/Passes/CrossToInnerJoinPass.cpp @@ -42,7 +42,7 @@ void exctractJoinConditions(const QueryTreeNodePtr & node, QueryTreeNodes & equi } else if (func->getFunctionName() == "and") { - for (auto & arg : args) + for (const auto & arg : args) exctractJoinConditions(arg, equi_conditions, other); } else @@ -77,7 +77,7 @@ std::pair getExpressionSource(const QueryTreeNodeP { const IQueryTreeNode * source = nullptr; const auto & args = func->getArguments().getNodes(); - for (auto & arg : args) + for (const auto & arg : args) { auto [arg_source, is_ok] = getExpressionSource(arg); if (!is_ok) @@ -232,7 +232,7 @@ private: return nodes.front(); auto function_node = std::make_shared("and"); - for (auto & node : nodes) + for (const auto & node : nodes) function_node->getArguments().getNodes().push_back(node); const auto & function = FunctionFactory::instance().get("and", getContext()); From 21fcc3b69c6355945617e1a5d77aa57de4694e91 Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 12:01:18 +0100 Subject: [PATCH 114/445] Add iceberg doc --- .../table-engines/integrations/deltalake.md | 23 +++++++- .../table-engines/integrations/hudi.md | 23 +++++++- .../table-engines/integrations/iceberg.md | 52 +++++++++++++++++ .../sql-reference/table-functions/iceberg.md | 58 +++++++++++++++++++ 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 docs/en/engines/table-engines/integrations/iceberg.md create mode 100644 docs/en/sql-reference/table-functions/iceberg.md diff --git a/docs/en/engines/table-engines/integrations/deltalake.md b/docs/en/engines/table-engines/integrations/deltalake.md index 83526ac944d..64ef7ec4dfc 100644 --- a/docs/en/engines/table-engines/integrations/deltalake.md +++ b/docs/en/engines/table-engines/integrations/deltalake.md @@ -19,7 +19,9 @@ CREATE TABLE deltalake **Engine parameters** - `url` — Bucket url with path to the existing Delta Lake table. -- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. For more information see [Using S3 for Data Storage](../mergetree-family/mergetree.md#table_engine-mergetree-s3). +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) **Example** @@ -27,7 +29,24 @@ CREATE TABLE deltalake CREATE TABLE deltalake ENGINE=DeltaLake('http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/test_table/', 'ABC123', 'Abc+123') ``` +Using named collections: + +``` xml + + + + http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/ + ABC123 + Abc+123 + + + +``` + +```sql +CREATE TABLE iceberg_table ENGINE=DeltaLake(deltalake_conf, filename = 'test_table') +``` + ## See also - [deltaLake table function](../../../sql-reference/table-functions/deltalake.md) - diff --git a/docs/en/engines/table-engines/integrations/hudi.md b/docs/en/engines/table-engines/integrations/hudi.md index 4e335e6c075..eb916b17cf9 100644 --- a/docs/en/engines/table-engines/integrations/hudi.md +++ b/docs/en/engines/table-engines/integrations/hudi.md @@ -19,7 +19,9 @@ CREATE TABLE hudi_table **Engine parameters** - `url` — Bucket url with the path to an existing Hudi table. -- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. For more information see [Using S3 for Data Storage](../mergetree-family/mergetree.md#table_engine-mergetree-s3). +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) **Example** @@ -27,7 +29,24 @@ CREATE TABLE hudi_table CREATE TABLE hudi_table ENGINE=Hudi('http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/test_table/', 'ABC123', 'Abc+123') ``` +Using named collections: + +``` xml + + + + http://mars-doc-test.s3.amazonaws.com/clickhouse-bucket-3/ + ABC123 + Abc+123 + + + +``` + +```sql +CREATE TABLE iceberg_table ENGINE=Hudi(hudi_conf, filename = 'test_table') +``` + ## See also - [hudi table function](/docs/en/sql-reference/table-functions/hudi.md) - diff --git a/docs/en/engines/table-engines/integrations/iceberg.md b/docs/en/engines/table-engines/integrations/iceberg.md new file mode 100644 index 00000000000..33ec5f877bf --- /dev/null +++ b/docs/en/engines/table-engines/integrations/iceberg.md @@ -0,0 +1,52 @@ +--- +slug: /en/engines/table-engines/integrations/iceberg +sidebar_label: Iceberg +--- + +# Iceberg Table Engine + +This engine provides a read-only integration with existing Apache [Iceberg](https://iceberg.apache.org/) tables in Amazon S3. + +## Create Table + +Note that the Iceberg table must already exist in S3, this command does not take DDL parameters to create a new table. + +``` sql +CREATE TABLE iceberg_table + ENGINE = Iceberg(url, [aws_access_key_id, aws_secret_access_key,]) +``` + +**Engine parameters** + +- `url` — url with the path to an existing Iceberg table. +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) + +**Example** + +```sql +CREATE TABLE iceberg_table ENGINE=Iceberg('http://test.s3.amazonaws.com/clickhouse-bucket/test_table', 'test', 'test') +``` + +Using named collections: + +``` xml + + + + http://test.s3.amazonaws.com/clickhouse-bucket/ + test + test + + + +``` + +```sql +CREATE TABLE iceberg_table ENGINE=Iceberg(iceberg_conf, filename = 'test_table') +``` + +## See also + +- [iceberg table function](/docs/en/sql-reference/table-functions/iceberg.md) diff --git a/docs/en/sql-reference/table-functions/iceberg.md b/docs/en/sql-reference/table-functions/iceberg.md new file mode 100644 index 00000000000..036c1379847 --- /dev/null +++ b/docs/en/sql-reference/table-functions/iceberg.md @@ -0,0 +1,58 @@ +--- +slug: /en/sql-reference/table-functions/iceberg +sidebar_label: Iceberg +--- + +# iceberg Table Function + +Provides a read-only table-like interface to Apache [Iceberg](https://iceberg.apache.org/) tables in Amazon S3. + +## Syntax + +``` sql +iceberg(url [,aws_access_key_id, aws_secret_access_key] [,format] [,structure]) +``` + +## Arguments + +- `url` — Bucket url with the path to an existing Iceberg table in S3. +- `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. These parameters are optional. If credentials are not specified, they are used from the ClickHouse configuration. For more information see [Using S3 for Data Storage](/docs/en/engines/table-engines/mergetree-family/mergetree.md/#table_engine-mergetree-s3). +- `format` — The [format](/docs/en/interfaces/formats.md/#formats) of the file. By default `Parquet` is used. +- `structure` — Structure of the table. Format `'column1_name column1_type, column2_name column2_type, ...'`. + +Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) + +**Returned value** + +A table with the specified structure for reading data in the specified Iceberg table in S3. + +**Example** + +```sql +SELECT * FROM iceberg('http://test.s3.amazonaws.com/clickhouse-bucket/test_table', 'test', 'test') +``` + +Using named collections: + +```xml + + + + http://test.s3.amazonaws.com/clickhouse-bucket/ + test + test + auto + auto + + + +``` + +```sql +SELECT * FROM iceberg(iceberg_conf, filename = 'test_table') +DESCRIBE iceberg(iceberg_conf, filename = 'test_table') +``` + +**See Also** + +- [Iceberg engine](/docs/en/engines/table-engines/integrations/iceberg.md) From ef15d6489565a8486815cfd43500ebf6fa0729ed Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:11:23 +0100 Subject: [PATCH 115/445] Update docs/en/engines/table-engines/integrations/deltalake.md Co-authored-by: flynn --- docs/en/engines/table-engines/integrations/deltalake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/engines/table-engines/integrations/deltalake.md b/docs/en/engines/table-engines/integrations/deltalake.md index 64ef7ec4dfc..a2816c7ff57 100644 --- a/docs/en/engines/table-engines/integrations/deltalake.md +++ b/docs/en/engines/table-engines/integrations/deltalake.md @@ -44,7 +44,7 @@ Using named collections: ``` ```sql -CREATE TABLE iceberg_table ENGINE=DeltaLake(deltalake_conf, filename = 'test_table') +CREATE TABLE deltalake ENGINE=DeltaLake(deltalake_conf, filename = 'test_table') ``` ## See also From c242fe3e5e80c2bcc3259c47b8e896be3aa13952 Mon Sep 17 00:00:00 2001 From: Kseniia Sumarokova <54203879+kssenii@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:11:42 +0100 Subject: [PATCH 116/445] Update docs/en/engines/table-engines/integrations/hudi.md Co-authored-by: flynn --- docs/en/engines/table-engines/integrations/hudi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/engines/table-engines/integrations/hudi.md b/docs/en/engines/table-engines/integrations/hudi.md index eb916b17cf9..6ff998d86d9 100644 --- a/docs/en/engines/table-engines/integrations/hudi.md +++ b/docs/en/engines/table-engines/integrations/hudi.md @@ -44,7 +44,7 @@ Using named collections: ``` ```sql -CREATE TABLE iceberg_table ENGINE=Hudi(hudi_conf, filename = 'test_table') +CREATE TABLE hudi_table ENGINE=Hudi(hudi_conf, filename = 'test_table') ``` ## See also From 98c10ff6e5751404c48106293befb212b126e7d1 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 22 Feb 2023 12:16:09 +0100 Subject: [PATCH 117/445] Update docs/en/operations/system-tables/processors_profile_log.md Co-authored-by: Nikita Taranov --- docs/en/operations/system-tables/processors_profile_log.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/operations/system-tables/processors_profile_log.md b/docs/en/operations/system-tables/processors_profile_log.md index 269385deab6..cc917138741 100644 --- a/docs/en/operations/system-tables/processors_profile_log.md +++ b/docs/en/operations/system-tables/processors_profile_log.md @@ -33,6 +33,7 @@ SELECT sleep(1) │ (ReadFromStorage) │ │ SourceFromSingleChunk 0 → 1 │ └─────────────────────────────────┘ + SELECT sleep(1) SETTINGS log_processors_profiles = 1 Query id: feb5ed16-1c24-4227-aa54-78c02b3b27d4 From ab94d6dc1831de374f1530c9c5e78bcc06227133 Mon Sep 17 00:00:00 2001 From: Nikolai Kochetov Date: Wed, 22 Feb 2023 12:16:19 +0100 Subject: [PATCH 118/445] Update docs/en/operations/system-tables/processors_profile_log.md Co-authored-by: Nikita Taranov --- docs/en/operations/system-tables/processors_profile_log.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/operations/system-tables/processors_profile_log.md b/docs/en/operations/system-tables/processors_profile_log.md index cc917138741..a2e7a9ebabd 100644 --- a/docs/en/operations/system-tables/processors_profile_log.md +++ b/docs/en/operations/system-tables/processors_profile_log.md @@ -41,6 +41,7 @@ Query id: feb5ed16-1c24-4227-aa54-78c02b3b27d4 │ 0 │ └──────────┘ 1 rows in set. Elapsed: 1.018 sec. + SELECT name, elapsed_us, From ceff5f41d14653a3c95208bcef963a18902db64d Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 12:23:14 +0100 Subject: [PATCH 119/445] Fix tests --- tests/config/users.d/access_management.xml | 1 + .../configs/users.d/users.xml | 1 + .../test_create_query_constraints/configs/users.xml | 1 + .../test_global_overcommit_tracker/configs/users.xml | 1 + .../test_grant_and_revoke/configs/users.d/users.xml | 1 + .../test_overcommit_tracker/configs/users.d/users.xml | 1 + .../configs/users.d/users.xml | 1 + tests/queries/0_stateless/01271_show_privileges.reference | 1 + .../0_stateless/02117_show_create_table_system.reference | 6 +++--- 9 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/config/users.d/access_management.xml b/tests/config/users.d/access_management.xml index 8f4d82805be..f7963cdb7f2 100644 --- a/tests/config/users.d/access_management.xml +++ b/tests/config/users.d/access_management.xml @@ -3,6 +3,7 @@ 1 1 + 1 diff --git a/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml b/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml +++ b/tests/integration/test_access_control_on_cluster/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_create_query_constraints/configs/users.xml b/tests/integration/test_create_query_constraints/configs/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_create_query_constraints/configs/users.xml +++ b/tests/integration/test_create_query_constraints/configs/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_global_overcommit_tracker/configs/users.xml b/tests/integration/test_global_overcommit_tracker/configs/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_global_overcommit_tracker/configs/users.xml +++ b/tests/integration/test_global_overcommit_tracker/configs/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_grant_and_revoke/configs/users.d/users.xml b/tests/integration/test_grant_and_revoke/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_grant_and_revoke/configs/users.d/users.xml +++ b/tests/integration/test_grant_and_revoke/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_overcommit_tracker/configs/users.d/users.xml b/tests/integration/test_overcommit_tracker/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_overcommit_tracker/configs/users.d/users.xml +++ b/tests/integration/test_overcommit_tracker/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml b/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml index fb5e2028d6e..8556e73c82f 100644 --- a/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml +++ b/tests/integration/test_settings_constraints_distributed/configs/users.d/users.xml @@ -5,6 +5,7 @@ default default 1 + 1 diff --git a/tests/queries/0_stateless/01271_show_privileges.reference b/tests/queries/0_stateless/01271_show_privileges.reference index 58b1cab6e20..c061eb95a65 100644 --- a/tests/queries/0_stateless/01271_show_privileges.reference +++ b/tests/queries/0_stateless/01271_show_privileges.reference @@ -90,6 +90,7 @@ SHOW QUOTAS ['SHOW CREATE QUOTA'] GLOBAL SHOW ACCESS SHOW SETTINGS PROFILES ['SHOW PROFILES','SHOW CREATE SETTINGS PROFILE','SHOW CREATE PROFILE'] GLOBAL SHOW ACCESS SHOW ACCESS [] \N ACCESS MANAGEMENT SHOW NAMED COLLECTIONS ['SHOW NAMED COLLECTIONS'] GLOBAL ACCESS MANAGEMENT +SHOW NAMED COLLECTIONS SECRETS ['SHOW NAMED COLLECTIONS SECRETS'] GLOBAL ACCESS MANAGEMENT ACCESS MANAGEMENT [] \N ALL SYSTEM SHUTDOWN ['SYSTEM KILL','SHUTDOWN'] GLOBAL SYSTEM SYSTEM DROP DNS CACHE ['SYSTEM DROP DNS','DROP DNS CACHE','DROP DNS'] GLOBAL SYSTEM DROP CACHE diff --git a/tests/queries/0_stateless/02117_show_create_table_system.reference b/tests/queries/0_stateless/02117_show_create_table_system.reference index aabe05ea5e2..fe93418aa6d 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -288,7 +288,7 @@ CREATE TABLE system.grants ( `user_name` Nullable(String), `role_name` Nullable(String), - `access_type` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'ACCESS MANAGEMENT' = 92, 'SYSTEM SHUTDOWN' = 93, 'SYSTEM DROP DNS CACHE' = 94, 'SYSTEM DROP MARK CACHE' = 95, 'SYSTEM DROP UNCOMPRESSED CACHE' = 96, 'SYSTEM DROP MMAP CACHE' = 97, 'SYSTEM DROP QUERY CACHE' = 98, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 99, 'SYSTEM DROP FILESYSTEM CACHE' = 100, 'SYSTEM DROP SCHEMA CACHE' = 101, 'SYSTEM DROP S3 CLIENT CACHE' = 102, 'SYSTEM DROP CACHE' = 103, 'SYSTEM RELOAD CONFIG' = 104, 'SYSTEM RELOAD USERS' = 105, 'SYSTEM RELOAD SYMBOLS' = 106, 'SYSTEM RELOAD DICTIONARY' = 107, 'SYSTEM RELOAD MODEL' = 108, 'SYSTEM RELOAD FUNCTION' = 109, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 110, 'SYSTEM RELOAD' = 111, 'SYSTEM RESTART DISK' = 112, 'SYSTEM MERGES' = 113, 'SYSTEM TTL MERGES' = 114, 'SYSTEM FETCHES' = 115, 'SYSTEM MOVES' = 116, 'SYSTEM DISTRIBUTED SENDS' = 117, 'SYSTEM REPLICATED SENDS' = 118, 'SYSTEM SENDS' = 119, 'SYSTEM REPLICATION QUEUES' = 120, 'SYSTEM DROP REPLICA' = 121, 'SYSTEM SYNC REPLICA' = 122, 'SYSTEM RESTART REPLICA' = 123, 'SYSTEM RESTORE REPLICA' = 124, 'SYSTEM WAIT LOADING PARTS' = 125, 'SYSTEM SYNC DATABASE REPLICA' = 126, 'SYSTEM SYNC TRANSACTION LOG' = 127, 'SYSTEM SYNC FILE CACHE' = 128, 'SYSTEM FLUSH DISTRIBUTED' = 129, 'SYSTEM FLUSH LOGS' = 130, 'SYSTEM FLUSH' = 131, 'SYSTEM THREAD FUZZER' = 132, 'SYSTEM UNFREEZE' = 133, 'SYSTEM' = 134, 'dictGet' = 135, 'addressToLine' = 136, 'addressToLineWithInlines' = 137, 'addressToSymbol' = 138, 'demangle' = 139, 'INTROSPECTION' = 140, 'FILE' = 141, 'URL' = 142, 'REMOTE' = 143, 'MONGO' = 144, 'MEILISEARCH' = 145, 'MYSQL' = 146, 'POSTGRES' = 147, 'SQLITE' = 148, 'ODBC' = 149, 'JDBC' = 150, 'HDFS' = 151, 'S3' = 152, 'HIVE' = 153, 'SOURCES' = 154, 'CLUSTER' = 155, 'ALL' = 156, 'NONE' = 157), + `access_type` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'SHOW NAMED COLLECTIONS SECRETS' = 92, 'ACCESS MANAGEMENT' = 93, 'SYSTEM SHUTDOWN' = 94, 'SYSTEM DROP DNS CACHE' = 95, 'SYSTEM DROP MARK CACHE' = 96, 'SYSTEM DROP UNCOMPRESSED CACHE' = 97, 'SYSTEM DROP MMAP CACHE' = 98, 'SYSTEM DROP QUERY CACHE' = 99, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 100, 'SYSTEM DROP FILESYSTEM CACHE' = 101, 'SYSTEM DROP SCHEMA CACHE' = 102, 'SYSTEM DROP S3 CLIENT CACHE' = 103, 'SYSTEM DROP CACHE' = 104, 'SYSTEM RELOAD CONFIG' = 105, 'SYSTEM RELOAD USERS' = 106, 'SYSTEM RELOAD SYMBOLS' = 107, 'SYSTEM RELOAD DICTIONARY' = 108, 'SYSTEM RELOAD MODEL' = 109, 'SYSTEM RELOAD FUNCTION' = 110, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 111, 'SYSTEM RELOAD' = 112, 'SYSTEM RESTART DISK' = 113, 'SYSTEM MERGES' = 114, 'SYSTEM TTL MERGES' = 115, 'SYSTEM FETCHES' = 116, 'SYSTEM MOVES' = 117, 'SYSTEM DISTRIBUTED SENDS' = 118, 'SYSTEM REPLICATED SENDS' = 119, 'SYSTEM SENDS' = 120, 'SYSTEM REPLICATION QUEUES' = 121, 'SYSTEM DROP REPLICA' = 122, 'SYSTEM SYNC REPLICA' = 123, 'SYSTEM RESTART REPLICA' = 124, 'SYSTEM RESTORE REPLICA' = 125, 'SYSTEM WAIT LOADING PARTS' = 126, 'SYSTEM SYNC DATABASE REPLICA' = 127, 'SYSTEM SYNC TRANSACTION LOG' = 128, 'SYSTEM SYNC FILE CACHE' = 129, 'SYSTEM FLUSH DISTRIBUTED' = 130, 'SYSTEM FLUSH LOGS' = 131, 'SYSTEM FLUSH' = 132, 'SYSTEM THREAD FUZZER' = 133, 'SYSTEM UNFREEZE' = 134, 'SYSTEM' = 135, 'dictGet' = 136, 'addressToLine' = 137, 'addressToLineWithInlines' = 138, 'addressToSymbol' = 139, 'demangle' = 140, 'INTROSPECTION' = 141, 'FILE' = 142, 'URL' = 143, 'REMOTE' = 144, 'MONGO' = 145, 'MEILISEARCH' = 146, 'MYSQL' = 147, 'POSTGRES' = 148, 'SQLITE' = 149, 'ODBC' = 150, 'JDBC' = 151, 'HDFS' = 152, 'S3' = 153, 'HIVE' = 154, 'SOURCES' = 155, 'CLUSTER' = 156, 'ALL' = 157, 'NONE' = 158), `database` Nullable(String), `table` Nullable(String), `column` Nullable(String), @@ -569,10 +569,10 @@ ENGINE = SystemPartsColumns COMMENT 'SYSTEM TABLE is built on the fly.' CREATE TABLE system.privileges ( - `privilege` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'ACCESS MANAGEMENT' = 92, 'SYSTEM SHUTDOWN' = 93, 'SYSTEM DROP DNS CACHE' = 94, 'SYSTEM DROP MARK CACHE' = 95, 'SYSTEM DROP UNCOMPRESSED CACHE' = 96, 'SYSTEM DROP MMAP CACHE' = 97, 'SYSTEM DROP QUERY CACHE' = 98, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 99, 'SYSTEM DROP FILESYSTEM CACHE' = 100, 'SYSTEM DROP SCHEMA CACHE' = 101, 'SYSTEM DROP S3 CLIENT CACHE' = 102, 'SYSTEM DROP CACHE' = 103, 'SYSTEM RELOAD CONFIG' = 104, 'SYSTEM RELOAD USERS' = 105, 'SYSTEM RELOAD SYMBOLS' = 106, 'SYSTEM RELOAD DICTIONARY' = 107, 'SYSTEM RELOAD MODEL' = 108, 'SYSTEM RELOAD FUNCTION' = 109, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 110, 'SYSTEM RELOAD' = 111, 'SYSTEM RESTART DISK' = 112, 'SYSTEM MERGES' = 113, 'SYSTEM TTL MERGES' = 114, 'SYSTEM FETCHES' = 115, 'SYSTEM MOVES' = 116, 'SYSTEM DISTRIBUTED SENDS' = 117, 'SYSTEM REPLICATED SENDS' = 118, 'SYSTEM SENDS' = 119, 'SYSTEM REPLICATION QUEUES' = 120, 'SYSTEM DROP REPLICA' = 121, 'SYSTEM SYNC REPLICA' = 122, 'SYSTEM RESTART REPLICA' = 123, 'SYSTEM RESTORE REPLICA' = 124, 'SYSTEM WAIT LOADING PARTS' = 125, 'SYSTEM SYNC DATABASE REPLICA' = 126, 'SYSTEM SYNC TRANSACTION LOG' = 127, 'SYSTEM SYNC FILE CACHE' = 128, 'SYSTEM FLUSH DISTRIBUTED' = 129, 'SYSTEM FLUSH LOGS' = 130, 'SYSTEM FLUSH' = 131, 'SYSTEM THREAD FUZZER' = 132, 'SYSTEM UNFREEZE' = 133, 'SYSTEM' = 134, 'dictGet' = 135, 'addressToLine' = 136, 'addressToLineWithInlines' = 137, 'addressToSymbol' = 138, 'demangle' = 139, 'INTROSPECTION' = 140, 'FILE' = 141, 'URL' = 142, 'REMOTE' = 143, 'MONGO' = 144, 'MEILISEARCH' = 145, 'MYSQL' = 146, 'POSTGRES' = 147, 'SQLITE' = 148, 'ODBC' = 149, 'JDBC' = 150, 'HDFS' = 151, 'S3' = 152, 'HIVE' = 153, 'SOURCES' = 154, 'CLUSTER' = 155, 'ALL' = 156, 'NONE' = 157), + `privilege` Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'SHOW NAMED COLLECTIONS SECRETS' = 92, 'ACCESS MANAGEMENT' = 93, 'SYSTEM SHUTDOWN' = 94, 'SYSTEM DROP DNS CACHE' = 95, 'SYSTEM DROP MARK CACHE' = 96, 'SYSTEM DROP UNCOMPRESSED CACHE' = 97, 'SYSTEM DROP MMAP CACHE' = 98, 'SYSTEM DROP QUERY CACHE' = 99, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 100, 'SYSTEM DROP FILESYSTEM CACHE' = 101, 'SYSTEM DROP SCHEMA CACHE' = 102, 'SYSTEM DROP S3 CLIENT CACHE' = 103, 'SYSTEM DROP CACHE' = 104, 'SYSTEM RELOAD CONFIG' = 105, 'SYSTEM RELOAD USERS' = 106, 'SYSTEM RELOAD SYMBOLS' = 107, 'SYSTEM RELOAD DICTIONARY' = 108, 'SYSTEM RELOAD MODEL' = 109, 'SYSTEM RELOAD FUNCTION' = 110, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 111, 'SYSTEM RELOAD' = 112, 'SYSTEM RESTART DISK' = 113, 'SYSTEM MERGES' = 114, 'SYSTEM TTL MERGES' = 115, 'SYSTEM FETCHES' = 116, 'SYSTEM MOVES' = 117, 'SYSTEM DISTRIBUTED SENDS' = 118, 'SYSTEM REPLICATED SENDS' = 119, 'SYSTEM SENDS' = 120, 'SYSTEM REPLICATION QUEUES' = 121, 'SYSTEM DROP REPLICA' = 122, 'SYSTEM SYNC REPLICA' = 123, 'SYSTEM RESTART REPLICA' = 124, 'SYSTEM RESTORE REPLICA' = 125, 'SYSTEM WAIT LOADING PARTS' = 126, 'SYSTEM SYNC DATABASE REPLICA' = 127, 'SYSTEM SYNC TRANSACTION LOG' = 128, 'SYSTEM SYNC FILE CACHE' = 129, 'SYSTEM FLUSH DISTRIBUTED' = 130, 'SYSTEM FLUSH LOGS' = 131, 'SYSTEM FLUSH' = 132, 'SYSTEM THREAD FUZZER' = 133, 'SYSTEM UNFREEZE' = 134, 'SYSTEM' = 135, 'dictGet' = 136, 'addressToLine' = 137, 'addressToLineWithInlines' = 138, 'addressToSymbol' = 139, 'demangle' = 140, 'INTROSPECTION' = 141, 'FILE' = 142, 'URL' = 143, 'REMOTE' = 144, 'MONGO' = 145, 'MEILISEARCH' = 146, 'MYSQL' = 147, 'POSTGRES' = 148, 'SQLITE' = 149, 'ODBC' = 150, 'JDBC' = 151, 'HDFS' = 152, 'S3' = 153, 'HIVE' = 154, 'SOURCES' = 155, 'CLUSTER' = 156, 'ALL' = 157, 'NONE' = 158), `aliases` Array(String), `level` Nullable(Enum8('GLOBAL' = 0, 'DATABASE' = 1, 'TABLE' = 2, 'DICTIONARY' = 3, 'VIEW' = 4, 'COLUMN' = 5)), - `parent_group` Nullable(Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'ACCESS MANAGEMENT' = 92, 'SYSTEM SHUTDOWN' = 93, 'SYSTEM DROP DNS CACHE' = 94, 'SYSTEM DROP MARK CACHE' = 95, 'SYSTEM DROP UNCOMPRESSED CACHE' = 96, 'SYSTEM DROP MMAP CACHE' = 97, 'SYSTEM DROP QUERY CACHE' = 98, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 99, 'SYSTEM DROP FILESYSTEM CACHE' = 100, 'SYSTEM DROP SCHEMA CACHE' = 101, 'SYSTEM DROP S3 CLIENT CACHE' = 102, 'SYSTEM DROP CACHE' = 103, 'SYSTEM RELOAD CONFIG' = 104, 'SYSTEM RELOAD USERS' = 105, 'SYSTEM RELOAD SYMBOLS' = 106, 'SYSTEM RELOAD DICTIONARY' = 107, 'SYSTEM RELOAD MODEL' = 108, 'SYSTEM RELOAD FUNCTION' = 109, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 110, 'SYSTEM RELOAD' = 111, 'SYSTEM RESTART DISK' = 112, 'SYSTEM MERGES' = 113, 'SYSTEM TTL MERGES' = 114, 'SYSTEM FETCHES' = 115, 'SYSTEM MOVES' = 116, 'SYSTEM DISTRIBUTED SENDS' = 117, 'SYSTEM REPLICATED SENDS' = 118, 'SYSTEM SENDS' = 119, 'SYSTEM REPLICATION QUEUES' = 120, 'SYSTEM DROP REPLICA' = 121, 'SYSTEM SYNC REPLICA' = 122, 'SYSTEM RESTART REPLICA' = 123, 'SYSTEM RESTORE REPLICA' = 124, 'SYSTEM WAIT LOADING PARTS' = 125, 'SYSTEM SYNC DATABASE REPLICA' = 126, 'SYSTEM SYNC TRANSACTION LOG' = 127, 'SYSTEM SYNC FILE CACHE' = 128, 'SYSTEM FLUSH DISTRIBUTED' = 129, 'SYSTEM FLUSH LOGS' = 130, 'SYSTEM FLUSH' = 131, 'SYSTEM THREAD FUZZER' = 132, 'SYSTEM UNFREEZE' = 133, 'SYSTEM' = 134, 'dictGet' = 135, 'addressToLine' = 136, 'addressToLineWithInlines' = 137, 'addressToSymbol' = 138, 'demangle' = 139, 'INTROSPECTION' = 140, 'FILE' = 141, 'URL' = 142, 'REMOTE' = 143, 'MONGO' = 144, 'MEILISEARCH' = 145, 'MYSQL' = 146, 'POSTGRES' = 147, 'SQLITE' = 148, 'ODBC' = 149, 'JDBC' = 150, 'HDFS' = 151, 'S3' = 152, 'HIVE' = 153, 'SOURCES' = 154, 'CLUSTER' = 155, 'ALL' = 156, 'NONE' = 157)) + `parent_group` Nullable(Enum16('SHOW DATABASES' = 0, 'SHOW TABLES' = 1, 'SHOW COLUMNS' = 2, 'SHOW DICTIONARIES' = 3, 'SHOW' = 4, 'SHOW FILESYSTEM CACHES' = 5, 'SELECT' = 6, 'INSERT' = 7, 'ALTER UPDATE' = 8, 'ALTER DELETE' = 9, 'ALTER ADD COLUMN' = 10, 'ALTER MODIFY COLUMN' = 11, 'ALTER DROP COLUMN' = 12, 'ALTER COMMENT COLUMN' = 13, 'ALTER CLEAR COLUMN' = 14, 'ALTER RENAME COLUMN' = 15, 'ALTER MATERIALIZE COLUMN' = 16, 'ALTER COLUMN' = 17, 'ALTER MODIFY COMMENT' = 18, 'ALTER ORDER BY' = 19, 'ALTER SAMPLE BY' = 20, 'ALTER ADD INDEX' = 21, 'ALTER DROP INDEX' = 22, 'ALTER MATERIALIZE INDEX' = 23, 'ALTER CLEAR INDEX' = 24, 'ALTER INDEX' = 25, 'ALTER ADD PROJECTION' = 26, 'ALTER DROP PROJECTION' = 27, 'ALTER MATERIALIZE PROJECTION' = 28, 'ALTER CLEAR PROJECTION' = 29, 'ALTER PROJECTION' = 30, 'ALTER ADD CONSTRAINT' = 31, 'ALTER DROP CONSTRAINT' = 32, 'ALTER CONSTRAINT' = 33, 'ALTER TTL' = 34, 'ALTER MATERIALIZE TTL' = 35, 'ALTER SETTINGS' = 36, 'ALTER MOVE PARTITION' = 37, 'ALTER FETCH PARTITION' = 38, 'ALTER FREEZE PARTITION' = 39, 'ALTER DATABASE SETTINGS' = 40, 'ALTER NAMED COLLECTION' = 41, 'ALTER TABLE' = 42, 'ALTER DATABASE' = 43, 'ALTER VIEW REFRESH' = 44, 'ALTER VIEW MODIFY QUERY' = 45, 'ALTER VIEW' = 46, 'ALTER' = 47, 'CREATE DATABASE' = 48, 'CREATE TABLE' = 49, 'CREATE VIEW' = 50, 'CREATE DICTIONARY' = 51, 'CREATE TEMPORARY TABLE' = 52, 'CREATE FUNCTION' = 53, 'CREATE NAMED COLLECTION' = 54, 'CREATE' = 55, 'DROP DATABASE' = 56, 'DROP TABLE' = 57, 'DROP VIEW' = 58, 'DROP DICTIONARY' = 59, 'DROP FUNCTION' = 60, 'DROP NAMED COLLECTION' = 61, 'DROP' = 62, 'TRUNCATE' = 63, 'OPTIMIZE' = 64, 'BACKUP' = 65, 'KILL QUERY' = 66, 'KILL TRANSACTION' = 67, 'MOVE PARTITION BETWEEN SHARDS' = 68, 'CREATE USER' = 69, 'ALTER USER' = 70, 'DROP USER' = 71, 'CREATE ROLE' = 72, 'ALTER ROLE' = 73, 'DROP ROLE' = 74, 'ROLE ADMIN' = 75, 'CREATE ROW POLICY' = 76, 'ALTER ROW POLICY' = 77, 'DROP ROW POLICY' = 78, 'CREATE QUOTA' = 79, 'ALTER QUOTA' = 80, 'DROP QUOTA' = 81, 'CREATE SETTINGS PROFILE' = 82, 'ALTER SETTINGS PROFILE' = 83, 'DROP SETTINGS PROFILE' = 84, 'SHOW USERS' = 85, 'SHOW ROLES' = 86, 'SHOW ROW POLICIES' = 87, 'SHOW QUOTAS' = 88, 'SHOW SETTINGS PROFILES' = 89, 'SHOW ACCESS' = 90, 'SHOW NAMED COLLECTIONS' = 91, 'SHOW NAMED COLLECTIONS SECRETS' = 92, 'ACCESS MANAGEMENT' = 93, 'SYSTEM SHUTDOWN' = 94, 'SYSTEM DROP DNS CACHE' = 95, 'SYSTEM DROP MARK CACHE' = 96, 'SYSTEM DROP UNCOMPRESSED CACHE' = 97, 'SYSTEM DROP MMAP CACHE' = 98, 'SYSTEM DROP QUERY CACHE' = 99, 'SYSTEM DROP COMPILED EXPRESSION CACHE' = 100, 'SYSTEM DROP FILESYSTEM CACHE' = 101, 'SYSTEM DROP SCHEMA CACHE' = 102, 'SYSTEM DROP S3 CLIENT CACHE' = 103, 'SYSTEM DROP CACHE' = 104, 'SYSTEM RELOAD CONFIG' = 105, 'SYSTEM RELOAD USERS' = 106, 'SYSTEM RELOAD SYMBOLS' = 107, 'SYSTEM RELOAD DICTIONARY' = 108, 'SYSTEM RELOAD MODEL' = 109, 'SYSTEM RELOAD FUNCTION' = 110, 'SYSTEM RELOAD EMBEDDED DICTIONARIES' = 111, 'SYSTEM RELOAD' = 112, 'SYSTEM RESTART DISK' = 113, 'SYSTEM MERGES' = 114, 'SYSTEM TTL MERGES' = 115, 'SYSTEM FETCHES' = 116, 'SYSTEM MOVES' = 117, 'SYSTEM DISTRIBUTED SENDS' = 118, 'SYSTEM REPLICATED SENDS' = 119, 'SYSTEM SENDS' = 120, 'SYSTEM REPLICATION QUEUES' = 121, 'SYSTEM DROP REPLICA' = 122, 'SYSTEM SYNC REPLICA' = 123, 'SYSTEM RESTART REPLICA' = 124, 'SYSTEM RESTORE REPLICA' = 125, 'SYSTEM WAIT LOADING PARTS' = 126, 'SYSTEM SYNC DATABASE REPLICA' = 127, 'SYSTEM SYNC TRANSACTION LOG' = 128, 'SYSTEM SYNC FILE CACHE' = 129, 'SYSTEM FLUSH DISTRIBUTED' = 130, 'SYSTEM FLUSH LOGS' = 131, 'SYSTEM FLUSH' = 132, 'SYSTEM THREAD FUZZER' = 133, 'SYSTEM UNFREEZE' = 134, 'SYSTEM' = 135, 'dictGet' = 136, 'addressToLine' = 137, 'addressToLineWithInlines' = 138, 'addressToSymbol' = 139, 'demangle' = 140, 'INTROSPECTION' = 141, 'FILE' = 142, 'URL' = 143, 'REMOTE' = 144, 'MONGO' = 145, 'MEILISEARCH' = 146, 'MYSQL' = 147, 'POSTGRES' = 148, 'SQLITE' = 149, 'ODBC' = 150, 'JDBC' = 151, 'HDFS' = 152, 'S3' = 153, 'HIVE' = 154, 'SOURCES' = 155, 'CLUSTER' = 156, 'ALL' = 157, 'NONE' = 158)) ) ENGINE = SystemPrivileges COMMENT 'SYSTEM TABLE is built on the fly.' From ec8b6c5590ff64b6ab1045a385b61ab04736861b Mon Sep 17 00:00:00 2001 From: lzydmxy <13126752315@163.com> Date: Wed, 22 Feb 2023 19:57:56 +0800 Subject: [PATCH 120/445] add __init__.py for integration test test_move_partition_to_disk_on_cluster --- .../test_move_partition_to_disk_on_cluster/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/integration/test_move_partition_to_disk_on_cluster/__init__.py diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/__init__.py b/tests/integration/test_move_partition_to_disk_on_cluster/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From fba2ec30a2a3d117e95f591fbfc635887e27ed6a Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 22 Feb 2023 13:53:43 +0100 Subject: [PATCH 121/445] fix style check --- .../test.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/tests/integration/test_move_partition_to_disk_on_cluster/test.py b/tests/integration/test_move_partition_to_disk_on_cluster/test.py index fe8606bd549..90753fc8ce3 100644 --- a/tests/integration/test_move_partition_to_disk_on_cluster/test.py +++ b/tests/integration/test_move_partition_to_disk_on_cluster/test.py @@ -43,10 +43,10 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: node.query( sql="CREATE TABLE test_local_table" - "(x UInt64) " - "ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_local_table', '{replica}') " - "ORDER BY tuple()" - "SETTINGS storage_policy = 'jbod_with_external';", + "(x UInt64) " + "ENGINE=ReplicatedMergeTree('/clickhouse/tables/test_local_table', '{replica}') " + "ORDER BY tuple()" + "SETTINGS storage_policy = 'jbod_with_external';", ) node1.query("INSERT INTO test_local_table VALUES (0)") @@ -61,10 +61,10 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: assert ( - node.query( - "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" - ) - == "('all','jbod1')" + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" ) node1.query( @@ -73,10 +73,10 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: assert ( - node.query( - "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" - ) - == "('all','external')" + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','external')" ) node1.query( @@ -85,10 +85,8 @@ def test_move_partition_to_disk_on_cluster(start_cluster): for node in [node1, node2]: assert ( - node.query( - "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" - ) - == "('all','jbod1')" + node.query( + "SELECT partition_id, disk_name FROM system.parts WHERE table = 'test_local_table' FORMAT Values" + ) + == "('all','jbod1')" ) - - From e8094c9707d46ef950232e475f671627b809a46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Wed, 22 Feb 2023 14:20:48 +0100 Subject: [PATCH 122/445] Add test for #46724 --- ...2667_order_by_aggregation_result.reference | 3 +++ .../02667_order_by_aggregation_result.sql | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.reference b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference index 642fc2ed645..a89e39d87b3 100644 --- a/tests/queries/0_stateless/02667_order_by_aggregation_result.reference +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.reference @@ -2,3 +2,6 @@ 0 1 █████████████████████████████████████████████████▉ 1 2 0.5 1 0.1 1.1 +00000000-0000-0000-0000-000000000000 b 1 +417ddc5d-e556-4d27-95dd-a34d84e46a50 c 1 +notEmpty a 1 diff --git a/tests/queries/0_stateless/02667_order_by_aggregation_result.sql b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql index 277430888d7..3fef0374d83 100644 --- a/tests/queries/0_stateless/02667_order_by_aggregation_result.sql +++ b/tests/queries/0_stateless/02667_order_by_aggregation_result.sql @@ -34,3 +34,27 @@ SELECT final_col + 1 AS final_col2 FROM ttttttt GROUP BY col3; + +-- https://github.com/ClickHouse/ClickHouse/issues/46724 + +CREATE TABLE table1 +( + id String, + device UUID +) +ENGINE = MergeTree() ORDER BY tuple(); + +INSERT INTO table1 VALUES ('notEmpty', '417ddc5d-e556-4d27-95dd-a34d84e46a50'); +INSERT INTO table1 VALUES ('', '417ddc5d-e556-4d27-95dd-a34d84e46a50'); +INSERT INTO table1 VALUES ('', '00000000-0000-0000-0000-000000000000'); + +SELECT + if(empty(id), toString(device), id) AS device, + multiIf( + notEmpty(id),'a', + device == '00000000-0000-0000-0000-000000000000', 'b', + 'c' ) AS device_id_type, + count() +FROM table1 +GROUP BY device, device_id_type +ORDER BY device; From bac464f89b3fdfe612e721a2fc976146e17dd696 Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 13:57:30 +0100 Subject: [PATCH 123/445] Fix --- docs/en/engines/table-engines/integrations/deltalake.md | 2 +- docs/en/engines/table-engines/integrations/hudi.md | 2 +- docs/en/engines/table-engines/integrations/iceberg.md | 2 +- docs/en/sql-reference/table-functions/iceberg.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/engines/table-engines/integrations/deltalake.md b/docs/en/engines/table-engines/integrations/deltalake.md index a2816c7ff57..99183ac7308 100644 --- a/docs/en/engines/table-engines/integrations/deltalake.md +++ b/docs/en/engines/table-engines/integrations/deltalake.md @@ -21,7 +21,7 @@ CREATE TABLE deltalake - `url` — Bucket url with path to the existing Delta Lake table. - `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../../operations/named-collections.md) **Example** diff --git a/docs/en/engines/table-engines/integrations/hudi.md b/docs/en/engines/table-engines/integrations/hudi.md index 6ff998d86d9..a14134ecdfa 100644 --- a/docs/en/engines/table-engines/integrations/hudi.md +++ b/docs/en/engines/table-engines/integrations/hudi.md @@ -21,7 +21,7 @@ CREATE TABLE hudi_table - `url` — Bucket url with the path to an existing Hudi table. - `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../../operations/named-collections.md) **Example** diff --git a/docs/en/engines/table-engines/integrations/iceberg.md b/docs/en/engines/table-engines/integrations/iceberg.md index 33ec5f877bf..4322fc6b773 100644 --- a/docs/en/engines/table-engines/integrations/iceberg.md +++ b/docs/en/engines/table-engines/integrations/iceberg.md @@ -21,7 +21,7 @@ CREATE TABLE iceberg_table - `url` — url with the path to an existing Iceberg table. - `aws_access_key_id`, `aws_secret_access_key` - Long-term credentials for the [AWS](https://aws.amazon.com/) account user. You can use these to authenticate your requests. Parameter is optional. If credentials are not specified, they are used from the configuration file. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../../operations/named-collections.md) **Example** diff --git a/docs/en/sql-reference/table-functions/iceberg.md b/docs/en/sql-reference/table-functions/iceberg.md index 036c1379847..fda4d274005 100644 --- a/docs/en/sql-reference/table-functions/iceberg.md +++ b/docs/en/sql-reference/table-functions/iceberg.md @@ -20,7 +20,7 @@ iceberg(url [,aws_access_key_id, aws_secret_access_key] [,format] [,structure]) - `format` — The [format](/docs/en/interfaces/formats.md/#formats) of the file. By default `Parquet` is used. - `structure` — Structure of the table. Format `'column1_name column1_type, column2_name column2_type, ...'`. -Engine parameters can be specified using [Named Collections](../operations/settings/named-collections.md) +Engine parameters can be specified using [Named Collections](../../operations/named-collections.md) **Returned value** From c4761d6cc688733124081b87a5fefffd1d7541ce Mon Sep 17 00:00:00 2001 From: kssenii Date: Wed, 22 Feb 2023 12:14:59 +0100 Subject: [PATCH 124/445] Fix checks --- src/Disks/getOrCreateDiskFromAST.cpp | 10 +--------- src/Disks/getOrCreateDiskFromAST.h | 5 ----- src/Parsers/FieldFromAST.cpp | 1 + src/Parsers/isDiskFunction.cpp | 16 ++++++++++++++++ src/Parsers/isDiskFunction.h | 9 +++++++++ src/Storages/MergeTree/MergeTreeSettings.cpp | 1 + .../integration/test_disk_configuration/test.py | 2 +- 7 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 src/Parsers/isDiskFunction.cpp create mode 100644 src/Parsers/isDiskFunction.h diff --git a/src/Disks/getOrCreateDiskFromAST.cpp b/src/Disks/getOrCreateDiskFromAST.cpp index fc9cd7edbee..997bd2c853f 100644 --- a/src/Disks/getOrCreateDiskFromAST.cpp +++ b/src/Disks/getOrCreateDiskFromAST.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace DB @@ -17,15 +18,6 @@ namespace ErrorCodes extern const int BAD_ARGUMENTS; } -bool isDiskFunction(ASTPtr ast) -{ - if (!ast) - return false; - - const auto * function = ast->as(); - return function && function->name == "disk" && function->arguments->as(); -} - std::string getOrCreateDiskFromDiskAST(const ASTFunction & function, ContextPtr context) { /// We need a unique name for a created custom disk, but it needs to be the same diff --git a/src/Disks/getOrCreateDiskFromAST.h b/src/Disks/getOrCreateDiskFromAST.h index c1d4bda1a49..7c64707b0bd 100644 --- a/src/Disks/getOrCreateDiskFromAST.h +++ b/src/Disks/getOrCreateDiskFromAST.h @@ -15,9 +15,4 @@ class ASTFunction; */ std::string getOrCreateDiskFromDiskAST(const ASTFunction & function, ContextPtr context); -/* - * Is given ast has form of a disk() function. - */ -bool isDiskFunction(ASTPtr ast); - } diff --git a/src/Parsers/FieldFromAST.cpp b/src/Parsers/FieldFromAST.cpp index 5889699c081..c46a9a08e68 100644 --- a/src/Parsers/FieldFromAST.cpp +++ b/src/Parsers/FieldFromAST.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include diff --git a/src/Parsers/isDiskFunction.cpp b/src/Parsers/isDiskFunction.cpp new file mode 100644 index 00000000000..e60229cb3f7 --- /dev/null +++ b/src/Parsers/isDiskFunction.cpp @@ -0,0 +1,16 @@ +#include +#include + +namespace DB +{ + +bool isDiskFunction(ASTPtr ast) +{ + if (!ast) + return false; + + const auto * function = ast->as(); + return function && function->name == "disk" && function->arguments->as(); +} + +} diff --git a/src/Parsers/isDiskFunction.h b/src/Parsers/isDiskFunction.h new file mode 100644 index 00000000000..97b3c58fa17 --- /dev/null +++ b/src/Parsers/isDiskFunction.h @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace DB +{ + +bool isDiskFunction(ASTPtr ast); + +} diff --git a/src/Storages/MergeTree/MergeTreeSettings.cpp b/src/Storages/MergeTree/MergeTreeSettings.cpp index e5af0c772ba..e951b8f54cf 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.cpp +++ b/src/Storages/MergeTree/MergeTreeSettings.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/integration/test_disk_configuration/test.py b/tests/integration/test_disk_configuration/test.py index 60d75e4dac1..34f8bea219f 100644 --- a/tests/integration/test_disk_configuration/test.py +++ b/tests/integration/test_disk_configuration/test.py @@ -262,7 +262,7 @@ def test_merge_tree_custom_disk_setting(start_cluster): ) expected = """ - SETTINGS disk = disk(type = s3, endpoint = \\'http://minio1:9001/root/data2/\\', access_key_id = \\'minio\\', secret_access_key = \\'minio123\\'), index_granularity = 8192 + SETTINGS disk = disk(type = s3, endpoint = \\'[HIDDEN]\\', access_key_id = \\'[HIDDEN]\\', secret_access_key = \\'[HIDDEN]\\'), index_granularity = 8192 """ assert expected.strip() in node1.query(f"SHOW CREATE TABLE {TABLE_NAME}_4").strip() From ea244e539032bc0b7f0c1f1f9c081cd42b970bd4 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Wed, 22 Feb 2023 13:28:45 +0000 Subject: [PATCH 125/445] revert getViewContext --- src/Storages/StorageView.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Storages/StorageView.cpp b/src/Storages/StorageView.cpp index d3a2ec470cf..1a7050b4dff 100644 --- a/src/Storages/StorageView.cpp +++ b/src/Storages/StorageView.cpp @@ -95,8 +95,6 @@ ContextPtr getViewContext(ContextPtr context) view_settings.max_result_rows = 0; view_settings.max_result_bytes = 0; view_settings.extremes = false; - view_settings.limit = 0; - view_settings.offset = 0; view_context->setSettings(view_settings); return view_context; } From 620071bb42df86069aaa12aeca2c432f01fedfc3 Mon Sep 17 00:00:00 2001 From: Yakov Olkhovskiy Date: Wed, 22 Feb 2023 13:33:40 +0000 Subject: [PATCH 126/445] fix --- src/Planner/Planner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Planner/Planner.cpp b/src/Planner/Planner.cpp index 307e8d73b29..c64dfd20f62 100644 --- a/src/Planner/Planner.cpp +++ b/src/Planner/Planner.cpp @@ -1436,7 +1436,7 @@ void Planner::buildPlanForQueryNode() */ if (query_analysis_result.limit_length && apply_limit && !limit_applied && apply_offset) addLimitStep(query_plan, query_analysis_result, planner_context, query_node); - else if (!limit_applied && apply_offset && query_analysis_result.limit_length) + else if (!limit_applied && apply_offset && query_analysis_result.limit_offset) addOffsetStep(query_plan, query_analysis_result); /// Project names is not done on shards, because initiator will not find columns in blocks From e433ecc18f896bb61193ea059ed217502d0f2101 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 22 Feb 2023 14:37:55 +0100 Subject: [PATCH 127/445] Better exception message during Tuple JSON deserialization --- src/DataTypes/Serializations/SerializationTuple.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/DataTypes/Serializations/SerializationTuple.cpp b/src/DataTypes/Serializations/SerializationTuple.cpp index ce15e099222..2b703a15a9b 100644 --- a/src/DataTypes/Serializations/SerializationTuple.cpp +++ b/src/DataTypes/Serializations/SerializationTuple.cpp @@ -231,7 +231,15 @@ void SerializationTuple::deserializeTextJSON(IColumn & column, ReadBuffer & istr seen_elements[element_pos] = 1; auto & element_column = extractElementColumn(column, element_pos); - elems[element_pos]->deserializeTextJSON(element_column, istr, settings); + try + { + elems[element_pos]->deserializeTextJSON(element_column, istr, settings); + } + catch (Exception & e) + { + e.addMessage("(while reading the value of nested key " + name + ")"); + throw; + } skipWhitespaceIfAny(istr); ++processed; From 4fd4e77737b4f1aa29898849baf289c7e05b8710 Mon Sep 17 00:00:00 2001 From: Robert Schulze Date: Wed, 22 Feb 2023 13:48:29 +0000 Subject: [PATCH 128/445] Poco: POCO_HAVE_INT64 is always defined --- .../Foundation/include/Poco/BinaryReader.h | 4 +-- .../Foundation/include/Poco/BinaryWriter.h | 4 +-- base/poco/Foundation/include/Poco/ByteOrder.h | 29 ------------------- .../Foundation/include/Poco/NumberFormatter.h | 6 ---- .../Foundation/include/Poco/NumberParser.h | 2 -- .../Foundation/include/Poco/StreamCopier.h | 6 ---- base/poco/Foundation/include/Poco/Token.h | 2 -- base/poco/Foundation/include/Poco/Types.h | 1 - base/poco/Foundation/src/BinaryReader.cpp | 4 +-- base/poco/Foundation/src/BinaryWriter.cpp | 4 +-- base/poco/Foundation/src/NumberFormatter.cpp | 2 -- base/poco/Foundation/src/NumberParser.cpp | 2 -- base/poco/Foundation/src/StreamCopier.cpp | 6 ---- base/poco/Foundation/src/Token.cpp | 2 -- base/poco/JSON/include/Poco/JSON/Handler.h | 2 -- .../JSON/include/Poco/JSON/ParseHandler.h | 4 --- .../JSON/include/Poco/JSON/PrintHandler.h | 2 -- base/poco/JSON/src/PrintHandler.cpp | 2 -- .../include/Poco/Net/HTTPFixedLengthStream.h | 4 --- base/poco/Net/include/Poco/Net/HTTPMessage.h | 2 -- base/poco/Net/src/HTTPClientSession.cpp | 8 ----- base/poco/Net/src/HTTPMessage.cpp | 2 -- base/poco/Net/src/HTTPServerRequestImpl.cpp | 4 --- base/poco/Net/src/HTTPServerResponseImpl.cpp | 8 ----- .../include/Poco/Util/AbstractConfiguration.h | 6 ---- .../Util/include/Poco/Util/WinRegistryKey.h | 2 -- base/poco/Util/src/AbstractConfiguration.cpp | 4 --- 27 files changed, 4 insertions(+), 120 deletions(-) diff --git a/base/poco/Foundation/include/Poco/BinaryReader.h b/base/poco/Foundation/include/Poco/BinaryReader.h index 280724a8a47..4042b507a2f 100644 --- a/base/poco/Foundation/include/Poco/BinaryReader.h +++ b/base/poco/Foundation/include/Poco/BinaryReader.h @@ -76,7 +76,7 @@ public: BinaryReader & operator>>(float & value); BinaryReader & operator>>(double & value); -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryReader & operator>>(Int64 & value); BinaryReader & operator>>(UInt64 & value); #endif @@ -106,12 +106,10 @@ public: /// See BinaryWriter::write7BitEncoded() for a description /// of the compression algorithm. -#if defined(POCO_HAVE_INT64) void read7BitEncoded(UInt64 & value); /// Reads a 64-bit unsigned integer in compressed format. /// See BinaryWriter::write7BitEncoded() for a description /// of the compression algorithm. -#endif void readRaw(std::streamsize length, std::string & value); /// Reads length bytes of raw data into value. diff --git a/base/poco/Foundation/include/Poco/BinaryWriter.h b/base/poco/Foundation/include/Poco/BinaryWriter.h index 30a353a8ff7..aa280d4ccab 100644 --- a/base/poco/Foundation/include/Poco/BinaryWriter.h +++ b/base/poco/Foundation/include/Poco/BinaryWriter.h @@ -81,7 +81,7 @@ public: BinaryWriter & operator<<(float value); BinaryWriter & operator<<(double value); -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryWriter & operator<<(Int64 value); BinaryWriter & operator<<(UInt64 value); #endif @@ -114,7 +114,6 @@ public: /// written out. value is then shifted by seven bits and the next byte is written. /// This process is repeated until the entire integer has been written. -#if defined(POCO_HAVE_INT64) void write7BitEncoded(UInt64 value); /// Writes a 64-bit unsigned integer in a compressed format. /// The value written out seven bits at a time, starting @@ -125,7 +124,6 @@ public: /// If value will not fit in seven bits, the high bit is set on the first byte and /// written out. value is then shifted by seven bits and the next byte is written. /// This process is repeated until the entire integer has been written. -#endif void writeRaw(const std::string & rawData); /// Writes the string as-is to the stream. diff --git a/base/poco/Foundation/include/Poco/ByteOrder.h b/base/poco/Foundation/include/Poco/ByteOrder.h index 4f2644ddf4e..09f673c2718 100644 --- a/base/poco/Foundation/include/Poco/ByteOrder.h +++ b/base/poco/Foundation/include/Poco/ByteOrder.h @@ -34,64 +34,50 @@ public: static UInt16 flipBytes(UInt16 value); static Int32 flipBytes(Int32 value); static UInt32 flipBytes(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 flipBytes(Int64 value); static UInt64 flipBytes(UInt64 value); -#endif static Int16 toBigEndian(Int16 value); static UInt16 toBigEndian(UInt16 value); static Int32 toBigEndian(Int32 value); static UInt32 toBigEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 toBigEndian(Int64 value); static UInt64 toBigEndian(UInt64 value); -#endif static Int16 fromBigEndian(Int16 value); static UInt16 fromBigEndian(UInt16 value); static Int32 fromBigEndian(Int32 value); static UInt32 fromBigEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 fromBigEndian(Int64 value); static UInt64 fromBigEndian(UInt64 value); -#endif static Int16 toLittleEndian(Int16 value); static UInt16 toLittleEndian(UInt16 value); static Int32 toLittleEndian(Int32 value); static UInt32 toLittleEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 toLittleEndian(Int64 value); static UInt64 toLittleEndian(UInt64 value); -#endif static Int16 fromLittleEndian(Int16 value); static UInt16 fromLittleEndian(UInt16 value); static Int32 fromLittleEndian(Int32 value); static UInt32 fromLittleEndian(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 fromLittleEndian(Int64 value); static UInt64 fromLittleEndian(UInt64 value); -#endif static Int16 toNetwork(Int16 value); static UInt16 toNetwork(UInt16 value); static Int32 toNetwork(Int32 value); static UInt32 toNetwork(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 toNetwork(Int64 value); static UInt64 toNetwork(UInt64 value); -#endif static Int16 fromNetwork(Int16 value); static UInt16 fromNetwork(UInt16 value); static Int32 fromNetwork(Int32 value); static UInt32 fromNetwork(UInt32 value); -#if defined(POCO_HAVE_INT64) static Int64 fromNetwork(Int64 value); static UInt64 fromNetwork(UInt64 value); -#endif }; @@ -143,7 +129,6 @@ inline Int32 ByteOrder::flipBytes(Int32 value) } -#if defined(POCO_HAVE_INT64) inline UInt64 ByteOrder::flipBytes(UInt64 value) { # if defined(POCO_HAVE_MSC_BYTESWAP) @@ -162,7 +147,6 @@ inline Int64 ByteOrder::flipBytes(Int64 value) { return Int64(flipBytes(UInt64(value))); } -#endif // POCO_HAVE_INT64 // @@ -180,7 +164,6 @@ inline Int64 ByteOrder::flipBytes(Int64 value) } -#if defined(POCO_HAVE_INT64) # define POCO_IMPLEMENT_BYTEORDER_NOOP(op) \ POCO_IMPLEMENT_BYTEORDER_NOOP_(op, Int16) \ POCO_IMPLEMENT_BYTEORDER_NOOP_(op, UInt16) \ @@ -195,18 +178,6 @@ inline Int64 ByteOrder::flipBytes(Int64 value) POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt32) \ POCO_IMPLEMENT_BYTEORDER_FLIP_(op, Int64) \ POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt64) -#else -# define POCO_IMPLEMENT_BYTEORDER_NOOP(op) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, Int16) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, UInt16) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, Int32) \ - POCO_IMPLEMENT_BYTEORDER_NOOP_(op, UInt32) -# define POCO_IMPLEMENT_BYTEORDER_FLIP(op) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, Int16) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt16) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, Int32) \ - POCO_IMPLEMENT_BYTEORDER_FLIP_(op, UInt32) -#endif #if defined(POCO_ARCH_BIG_ENDIAN) diff --git a/base/poco/Foundation/include/Poco/NumberFormatter.h b/base/poco/Foundation/include/Poco/NumberFormatter.h index e246ca16ec3..a320b576083 100644 --- a/base/poco/Foundation/include/Poco/NumberFormatter.h +++ b/base/poco/Foundation/include/Poco/NumberFormatter.h @@ -151,7 +151,6 @@ public: /// If prefix is true, "0x" prefix is prepended to the /// resulting string. -#ifdef POCO_HAVE_INT64 # ifdef POCO_LONG_IS_64_BIT @@ -255,7 +254,6 @@ public: # endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 static std::string format(float value); /// Formats a float value in decimal floating-point notation, @@ -380,7 +378,6 @@ public: /// right justified and zero-padded in a field having at least the /// specified width. -#ifdef POCO_HAVE_INT64 # ifdef POCO_LONG_IS_64_BIT @@ -472,7 +469,6 @@ public: # endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 static void append(std::string & str, float value); /// Formats a float value in decimal floating-point notation, @@ -673,7 +669,6 @@ inline std::string NumberFormatter::formatHex(unsigned long value, int width, bo } -#ifdef POCO_HAVE_INT64 # ifdef POCO_LONG_IS_64_BIT @@ -843,7 +838,6 @@ inline std::string NumberFormatter::formatHex(UInt64 value, int width, bool pref # endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 inline std::string NumberFormatter::format(float value) diff --git a/base/poco/Foundation/include/Poco/NumberParser.h b/base/poco/Foundation/include/Poco/NumberParser.h index de813e37dae..32f8c0dc989 100644 --- a/base/poco/Foundation/include/Poco/NumberParser.h +++ b/base/poco/Foundation/include/Poco/NumberParser.h @@ -80,7 +80,6 @@ public: /// Returns true if a valid integer has been found, false otherwise. /// If parsing was not successful, value is undefined. -#if defined(POCO_HAVE_INT64) static Int64 parse64(const std::string & s, char thousandSeparator = ','); /// Parses a 64-bit integer value in decimal notation from the given string. @@ -118,7 +117,6 @@ public: /// Returns true if a valid integer has been found, false otherwise. /// If parsing was not successful, value is undefined. -#endif // defined(POCO_HAVE_INT64) static double parseFloat(const std::string & s, char decimalSeparator = '.', char thousandSeparator = ','); /// Parses a double value in decimal floating point notation diff --git a/base/poco/Foundation/include/Poco/StreamCopier.h b/base/poco/Foundation/include/Poco/StreamCopier.h index 72b19306388..c24e73d88dd 100644 --- a/base/poco/Foundation/include/Poco/StreamCopier.h +++ b/base/poco/Foundation/include/Poco/StreamCopier.h @@ -38,7 +38,6 @@ public: /// /// Returns the number of bytes copied. -#if defined(POCO_HAVE_INT64) static Poco::UInt64 copyStream64(std::istream & istr, std::ostream & ostr, std::size_t bufferSize = 8192); /// Writes all bytes readable from istr to ostr, using an internal buffer. /// @@ -46,14 +45,12 @@ public: /// /// Note: the only difference to copyStream() is that a 64-bit unsigned /// integer is used to count the number of bytes copied. -#endif static std::streamsize copyStreamUnbuffered(std::istream & istr, std::ostream & ostr); /// Writes all bytes readable from istr to ostr. /// /// Returns the number of bytes copied. -#if defined(POCO_HAVE_INT64) static Poco::UInt64 copyStreamUnbuffered64(std::istream & istr, std::ostream & ostr); /// Writes all bytes readable from istr to ostr. /// @@ -61,14 +58,12 @@ public: /// /// Note: the only difference to copyStreamUnbuffered() is that a 64-bit unsigned /// integer is used to count the number of bytes copied. -#endif static std::streamsize copyToString(std::istream & istr, std::string & str, std::size_t bufferSize = 8192); /// Appends all bytes readable from istr to the given string, using an internal buffer. /// /// Returns the number of bytes copied. -#if defined(POCO_HAVE_INT64) static Poco::UInt64 copyToString64(std::istream & istr, std::string & str, std::size_t bufferSize = 8192); /// Appends all bytes readable from istr to the given string, using an internal buffer. /// @@ -76,7 +71,6 @@ public: /// /// Note: the only difference to copyToString() is that a 64-bit unsigned /// integer is used to count the number of bytes copied. -#endif }; diff --git a/base/poco/Foundation/include/Poco/Token.h b/base/poco/Foundation/include/Poco/Token.h index 2d62ed87de6..1aec9e620fe 100644 --- a/base/poco/Foundation/include/Poco/Token.h +++ b/base/poco/Foundation/include/Poco/Token.h @@ -84,13 +84,11 @@ public: virtual std::string asString() const; /// Returns a string representation of the token. -#if defined(POCO_HAVE_INT64) virtual Int64 asInteger64() const; /// Returns a 64-bit integer representation of the token. virtual UInt64 asUnsignedInteger64() const; /// Returns an unsigned 64-bit integer representation of the token. -#endif virtual int asInteger() const; /// Returns an integer representation of the token. diff --git a/base/poco/Foundation/include/Poco/Types.h b/base/poco/Foundation/include/Poco/Types.h index 156b3584d15..d10047344f6 100644 --- a/base/poco/Foundation/include/Poco/Types.h +++ b/base/poco/Foundation/include/Poco/Types.h @@ -46,7 +46,6 @@ typedef unsigned long UInt64; typedef signed long long Int64; typedef unsigned long long UInt64; # endif -# define POCO_HAVE_INT64 1 #endif diff --git a/base/poco/Foundation/src/BinaryReader.cpp b/base/poco/Foundation/src/BinaryReader.cpp index fb57371fbc3..f2961e03966 100644 --- a/base/poco/Foundation/src/BinaryReader.cpp +++ b/base/poco/Foundation/src/BinaryReader.cpp @@ -170,7 +170,7 @@ BinaryReader& BinaryReader::operator >> (double& value) } -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryReader& BinaryReader::operator >> (Int64& value) @@ -233,7 +233,6 @@ void BinaryReader::read7BitEncoded(UInt32& value) } -#if defined(POCO_HAVE_INT64) void BinaryReader::read7BitEncoded(UInt64& value) @@ -254,7 +253,6 @@ void BinaryReader::read7BitEncoded(UInt64& value) } -#endif void BinaryReader::readRaw(std::streamsize length, std::string& value) diff --git a/base/poco/Foundation/src/BinaryWriter.cpp b/base/poco/Foundation/src/BinaryWriter.cpp index 62e1adfe373..6db5ab7cb90 100644 --- a/base/poco/Foundation/src/BinaryWriter.cpp +++ b/base/poco/Foundation/src/BinaryWriter.cpp @@ -212,7 +212,7 @@ BinaryWriter& BinaryWriter::operator << (double value) } -#if defined(POCO_HAVE_INT64) && !defined(POCO_LONG_IS_64_BIT) +#if !defined(POCO_LONG_IS_64_BIT) BinaryWriter& BinaryWriter::operator << (Int64 value) @@ -303,7 +303,6 @@ void BinaryWriter::write7BitEncoded(UInt32 value) } -#if defined(POCO_HAVE_INT64) void BinaryWriter::write7BitEncoded(UInt64 value) @@ -319,7 +318,6 @@ void BinaryWriter::write7BitEncoded(UInt64 value) } -#endif void BinaryWriter::writeRaw(const std::string& rawData) diff --git a/base/poco/Foundation/src/NumberFormatter.cpp b/base/poco/Foundation/src/NumberFormatter.cpp index 5c8126e9b0a..0a9334059a9 100644 --- a/base/poco/Foundation/src/NumberFormatter.cpp +++ b/base/poco/Foundation/src/NumberFormatter.cpp @@ -234,7 +234,6 @@ void NumberFormatter::appendHex(std::string& str, unsigned long value, int width } -#ifdef POCO_HAVE_INT64 #ifdef POCO_LONG_IS_64_BIT @@ -424,7 +423,6 @@ void NumberFormatter::appendHex(std::string& str, UInt64 value, int width) #endif // ifdef POCO_LONG_IS_64_BIT -#endif // ifdef POCO_HAVE_INT64 void NumberFormatter::append(std::string& str, float value) diff --git a/base/poco/Foundation/src/NumberParser.cpp b/base/poco/Foundation/src/NumberParser.cpp index 56eeb167595..4081f3b2663 100644 --- a/base/poco/Foundation/src/NumberParser.cpp +++ b/base/poco/Foundation/src/NumberParser.cpp @@ -104,7 +104,6 @@ bool NumberParser::tryParseOct(const std::string& s, unsigned& value) } -#if defined(POCO_HAVE_INT64) Int64 NumberParser::parse64(const std::string& s, char thSep) @@ -173,7 +172,6 @@ bool NumberParser::tryParseOct64(const std::string& s, UInt64& value) } -#endif // defined(POCO_HAVE_INT64) double NumberParser::parseFloat(const std::string& s, char decSep, char thSep) diff --git a/base/poco/Foundation/src/StreamCopier.cpp b/base/poco/Foundation/src/StreamCopier.cpp index 6f34cc233a2..508d1e7b2ae 100644 --- a/base/poco/Foundation/src/StreamCopier.cpp +++ b/base/poco/Foundation/src/StreamCopier.cpp @@ -42,7 +42,6 @@ std::streamsize StreamCopier::copyStream(std::istream& istr, std::ostream& ostr, } -#if defined(POCO_HAVE_INT64) Poco::UInt64 StreamCopier::copyStream64(std::istream& istr, std::ostream& ostr, std::size_t bufferSize) { poco_assert (bufferSize > 0); @@ -64,7 +63,6 @@ Poco::UInt64 StreamCopier::copyStream64(std::istream& istr, std::ostream& ostr, } return len; } -#endif std::streamsize StreamCopier::copyToString(std::istream& istr, std::string& str, std::size_t bufferSize) @@ -90,7 +88,6 @@ std::streamsize StreamCopier::copyToString(std::istream& istr, std::string& str, } -#if defined(POCO_HAVE_INT64) Poco::UInt64 StreamCopier::copyToString64(std::istream& istr, std::string& str, std::size_t bufferSize) { poco_assert (bufferSize > 0); @@ -112,7 +109,6 @@ Poco::UInt64 StreamCopier::copyToString64(std::istream& istr, std::string& str, } return len; } -#endif std::streamsize StreamCopier::copyStreamUnbuffered(std::istream& istr, std::ostream& ostr) @@ -130,7 +126,6 @@ std::streamsize StreamCopier::copyStreamUnbuffered(std::istream& istr, std::ostr } -#if defined(POCO_HAVE_INT64) Poco::UInt64 StreamCopier::copyStreamUnbuffered64(std::istream& istr, std::ostream& ostr) { char c = 0; @@ -144,7 +139,6 @@ Poco::UInt64 StreamCopier::copyStreamUnbuffered64(std::istream& istr, std::ostre } return len; } -#endif } // namespace Poco diff --git a/base/poco/Foundation/src/Token.cpp b/base/poco/Foundation/src/Token.cpp index 98e8bb25e93..4e81c6ef885 100644 --- a/base/poco/Foundation/src/Token.cpp +++ b/base/poco/Foundation/src/Token.cpp @@ -54,7 +54,6 @@ std::string Token::asString() const } -#if defined(POCO_HAVE_INT64) Int64 Token::asInteger64() const { return NumberParser::parse64(_value); @@ -65,7 +64,6 @@ UInt64 Token::asUnsignedInteger64() const { return NumberParser::parseUnsigned64(_value); } -#endif int Token::asInteger() const diff --git a/base/poco/JSON/include/Poco/JSON/Handler.h b/base/poco/JSON/include/Poco/JSON/Handler.h index f9114a59221..c412a05003f 100644 --- a/base/poco/JSON/include/Poco/JSON/Handler.h +++ b/base/poco/JSON/include/Poco/JSON/Handler.h @@ -74,14 +74,12 @@ namespace JSON /// An unsigned value is read. This will only be triggered if the /// value cannot fit into a signed int. -#if defined(POCO_HAVE_INT64) virtual void value(Int64 v) = 0; /// A 64-bit integer value is read. virtual void value(UInt64 v) = 0; /// An unsigned 64-bit integer value is read. This will only be /// triggered if the value cannot fit into a signed 64-bit integer. -#endif virtual void value(const std::string & value) = 0; /// A string value is read. diff --git a/base/poco/JSON/include/Poco/JSON/ParseHandler.h b/base/poco/JSON/include/Poco/JSON/ParseHandler.h index 4669dc8638f..1b8ac3066d2 100644 --- a/base/poco/JSON/include/Poco/JSON/ParseHandler.h +++ b/base/poco/JSON/include/Poco/JSON/ParseHandler.h @@ -73,14 +73,12 @@ namespace JSON /// An unsigned value is read. This will only be triggered if the /// value cannot fit into a signed int. -#if defined(POCO_HAVE_INT64) virtual void value(Int64 v); /// A 64-bit integer value is read virtual void value(UInt64 v); /// An unsigned 64-bit integer value is read. This will only be /// triggered if the value cannot fit into a signed 64-bit integer. -#endif virtual void value(const std::string & s); /// A string value is read. @@ -126,7 +124,6 @@ namespace JSON } -#if defined(POCO_HAVE_INT64) inline void ParseHandler::value(Int64 v) { setValue(v); @@ -137,7 +134,6 @@ namespace JSON { setValue(v); } -#endif inline void ParseHandler::value(const std::string & s) diff --git a/base/poco/JSON/include/Poco/JSON/PrintHandler.h b/base/poco/JSON/include/Poco/JSON/PrintHandler.h index 34a991653ba..390f4d8bba9 100644 --- a/base/poco/JSON/include/Poco/JSON/PrintHandler.h +++ b/base/poco/JSON/include/Poco/JSON/PrintHandler.h @@ -81,13 +81,11 @@ namespace JSON /// An unsigned value is read. This will only be triggered if the /// value cannot fit into a signed int. -#if defined(POCO_HAVE_INT64) void value(Int64 v); /// A 64-bit integer value is read; it will be written to the output. void value(UInt64 v); /// An unsigned 64-bit integer value is read; it will be written to the output. -#endif void value(const std::string & value); /// A string value is read; it will be formatted and written to the output. diff --git a/base/poco/JSON/src/PrintHandler.cpp b/base/poco/JSON/src/PrintHandler.cpp index bf735d0869c..ea81cbdd1c0 100644 --- a/base/poco/JSON/src/PrintHandler.cpp +++ b/base/poco/JSON/src/PrintHandler.cpp @@ -154,7 +154,6 @@ void PrintHandler::value(unsigned v) } -#if defined(POCO_HAVE_INT64) void PrintHandler::value(Int64 v) { arrayValue(); @@ -169,7 +168,6 @@ void PrintHandler::value(UInt64 v) _out << v; _objStart = false; } -#endif void PrintHandler::value(const std::string& value) diff --git a/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h b/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h index dcdd1cfcaf8..4de211fdb92 100644 --- a/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h +++ b/base/poco/Net/include/Poco/Net/HTTPFixedLengthStream.h @@ -43,11 +43,7 @@ namespace Net public: typedef HTTPBasicStreamBuf::openmode openmode; -#if defined(POCO_HAVE_INT64) typedef Poco::Int64 ContentLength; -#else - typedef std::streamsize ContentLength; -#endif HTTPFixedLengthStreamBuf(HTTPSession & session, ContentLength length, openmode mode); ~HTTPFixedLengthStreamBuf(); diff --git a/base/poco/Net/include/Poco/Net/HTTPMessage.h b/base/poco/Net/include/Poco/Net/HTTPMessage.h index 5c54bf7306b..0bef50803a8 100644 --- a/base/poco/Net/include/Poco/Net/HTTPMessage.h +++ b/base/poco/Net/include/Poco/Net/HTTPMessage.h @@ -56,7 +56,6 @@ namespace Net /// which may be UNKNOWN_CONTENT_LENGTH if /// no Content-Length header is present. -#if defined(POCO_HAVE_INT64) void setContentLength64(Poco::Int64 length); /// Sets the Content-Length header. /// @@ -73,7 +72,6 @@ namespace Net /// /// In contrast to getContentLength(), this method /// always returns a 64-bit integer for content length. -#endif // defined(POCO_HAVE_INT64) bool hasContentLength() const; /// Returns true iff a Content-Length header is present. diff --git a/base/poco/Net/src/HTTPClientSession.cpp b/base/poco/Net/src/HTTPClientSession.cpp index 323e9526df5..c5697b556d1 100644 --- a/base/poco/Net/src/HTTPClientSession.cpp +++ b/base/poco/Net/src/HTTPClientSession.cpp @@ -264,11 +264,7 @@ std::ostream& HTTPClientSession::sendRequest(HTTPRequest& request) { Poco::CountingOutputStream cs; request.write(cs); -#if POCO_HAVE_INT64 _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength64() + cs.chars()); -#else - _pRequestStream = new HTTPFixedLengthOutputStream(*this, request.getContentLength() + cs.chars()); -#endif request.write(*_pRequestStream); } else if ((method != HTTPRequest::HTTP_PUT && method != HTTPRequest::HTTP_POST && method != HTTPRequest::HTTP_PATCH) || request.has(HTTPRequest::UPGRADE)) @@ -334,11 +330,7 @@ std::istream& HTTPClientSession::receiveResponse(HTTPResponse& response) else if (response.getChunkedTransferEncoding()) _pResponseStream = new HTTPChunkedInputStream(*this); else if (response.hasContentLength()) -#if defined(POCO_HAVE_INT64) _pResponseStream = new HTTPFixedLengthInputStream(*this, response.getContentLength64()); -#else - _pResponseStream = new HTTPFixedLengthInputStream(*this, response.getContentLength()); -#endif else _pResponseStream = new HTTPInputStream(*this); diff --git a/base/poco/Net/src/HTTPMessage.cpp b/base/poco/Net/src/HTTPMessage.cpp index debda04c3b3..0cd234ee9cb 100644 --- a/base/poco/Net/src/HTTPMessage.cpp +++ b/base/poco/Net/src/HTTPMessage.cpp @@ -89,7 +89,6 @@ std::streamsize HTTPMessage::getContentLength() const } -#if defined(POCO_HAVE_INT64) void HTTPMessage::setContentLength64(Poco::Int64 length) { if (length != UNKNOWN_CONTENT_LENGTH) @@ -108,7 +107,6 @@ Poco::Int64 HTTPMessage::getContentLength64() const } else return UNKNOWN_CONTENT_LENGTH; } -#endif // defined(POCO_HAVE_INT64) void HTTPMessage::setTransferEncoding(const std::string& transferEncoding) diff --git a/base/poco/Net/src/HTTPServerRequestImpl.cpp b/base/poco/Net/src/HTTPServerRequestImpl.cpp index d8ea7398c9b..d893e49aafb 100644 --- a/base/poco/Net/src/HTTPServerRequestImpl.cpp +++ b/base/poco/Net/src/HTTPServerRequestImpl.cpp @@ -49,11 +49,7 @@ HTTPServerRequestImpl::HTTPServerRequestImpl(HTTPServerResponseImpl& response, H if (getChunkedTransferEncoding()) _pStream = new HTTPChunkedInputStream(session); else if (hasContentLength()) -#if defined(POCO_HAVE_INT64) _pStream = new HTTPFixedLengthInputStream(session, getContentLength64()); -#else - _pStream = new HTTPFixedLengthInputStream(session, getContentLength()); -#endif else if (getMethod() == HTTPRequest::HTTP_GET || getMethod() == HTTPRequest::HTTP_HEAD || getMethod() == HTTPRequest::HTTP_DELETE) _pStream = new HTTPFixedLengthInputStream(session, 0); else diff --git a/base/poco/Net/src/HTTPServerResponseImpl.cpp b/base/poco/Net/src/HTTPServerResponseImpl.cpp index fb6783c633e..55de22c876c 100644 --- a/base/poco/Net/src/HTTPServerResponseImpl.cpp +++ b/base/poco/Net/src/HTTPServerResponseImpl.cpp @@ -92,11 +92,7 @@ std::ostream& HTTPServerResponseImpl::send() { Poco::CountingOutputStream cs; write(cs); -#if defined(POCO_HAVE_INT64) _pStream = new HTTPFixedLengthOutputStream(_session, getContentLength64() + cs.chars()); -#else - _pStream = new HTTPFixedLengthOutputStream(_session, getContentLength() + cs.chars()); -#endif write(*_pStream); } else @@ -153,11 +149,7 @@ void HTTPServerResponseImpl::sendFile(const std::string& path, const std::string Timestamp dateTime = f.getLastModified(); File::FileSize length = f.getSize(); set("Last-Modified", DateTimeFormatter::format(dateTime, DateTimeFormat::HTTP_FORMAT)); -#if defined(POCO_HAVE_INT64) setContentLength64(length); -#else - setContentLength(static_cast(length)); -#endif setContentType(mediaType); setChunkedTransferEncoding(false); diff --git a/base/poco/Util/include/Poco/Util/AbstractConfiguration.h b/base/poco/Util/include/Poco/Util/AbstractConfiguration.h index a0e5e2c50dd..926ac3ba8a9 100644 --- a/base/poco/Util/include/Poco/Util/AbstractConfiguration.h +++ b/base/poco/Util/include/Poco/Util/AbstractConfiguration.h @@ -167,7 +167,6 @@ namespace Util /// If the value contains references to other properties (${}), these /// are expanded. -#if defined(POCO_HAVE_INT64) Int64 getInt64(const std::string & key) const; /// Returns the Int64 value of the property with the given name. @@ -205,7 +204,6 @@ namespace Util /// If the value contains references to other properties (${}), these /// are expanded. -#endif // defined(POCO_HAVE_INT64) double getDouble(const std::string & key) const; /// Returns the double value of the property with the given name. @@ -255,7 +253,6 @@ namespace Util /// Sets the property with the given key to the given value. /// An already existing value for the key is overwritten. -#if defined(POCO_HAVE_INT64) virtual void setInt64(const std::string & key, Int64 value); /// Sets the property with the given key to the given value. @@ -265,7 +262,6 @@ namespace Util /// Sets the property with the given key to the given value. /// An already existing value for the key is overwritten. -#endif // defined(POCO_HAVE_INT64) virtual void setDouble(const std::string & key, double value); /// Sets the property with the given key to the given value. @@ -335,12 +331,10 @@ namespace Util static int parseInt(const std::string & value); static unsigned parseUInt(const std::string & value); -#if defined(POCO_HAVE_INT64) static Int64 parseInt64(const std::string & value); static UInt64 parseUInt64(const std::string & value); -#endif // defined(POCO_HAVE_INT64) static bool parseBool(const std::string & value); void setRawWithEvent(const std::string & key, std::string value); diff --git a/base/poco/Util/include/Poco/Util/WinRegistryKey.h b/base/poco/Util/include/Poco/Util/WinRegistryKey.h index b28f6aefb37..9aa5e35ed8a 100644 --- a/base/poco/Util/include/Poco/Util/WinRegistryKey.h +++ b/base/poco/Util/include/Poco/Util/WinRegistryKey.h @@ -123,7 +123,6 @@ namespace Util /// /// Throws a NotFoundException if the value does not exist. -#if defined(POCO_HAVE_INT64) void setInt64(const std::string & name, Poco::Int64 value); /// Sets the numeric (REG_QWORD) value with the given name. @@ -135,7 +134,6 @@ namespace Util /// /// Throws a NotFoundException if the value does not exist. -#endif // POCO_HAVE_INT64 void deleteValue(const std::string & name); /// Deletes the value with the given name. diff --git a/base/poco/Util/src/AbstractConfiguration.cpp b/base/poco/Util/src/AbstractConfiguration.cpp index 95e8da68a57..2c892decd9a 100644 --- a/base/poco/Util/src/AbstractConfiguration.cpp +++ b/base/poco/Util/src/AbstractConfiguration.cpp @@ -163,7 +163,6 @@ unsigned AbstractConfiguration::getUInt(const std::string& key, unsigned default } -#if defined(POCO_HAVE_INT64) Int64 AbstractConfiguration::getInt64(const std::string& key) const @@ -214,7 +213,6 @@ UInt64 AbstractConfiguration::getUInt64(const std::string& key, UInt64 defaultVa } -#endif // defined(POCO_HAVE_INT64) double AbstractConfiguration::getDouble(const std::string& key) const @@ -283,7 +281,6 @@ void AbstractConfiguration::setUInt(const std::string& key, unsigned int value) } -#if defined(POCO_HAVE_INT64) void AbstractConfiguration::setInt64(const std::string& key, Int64 value) @@ -302,7 +299,6 @@ void AbstractConfiguration::setUInt64(const std::string& key, UInt64 value) } -#endif // defined(POCO_HAVE_INT64) void AbstractConfiguration::setDouble(const std::string& key, double value) From 7f5fb77ed51e6dc8beb842dded898b891972e51d Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 22 Feb 2023 15:09:48 +0100 Subject: [PATCH 129/445] Increase table retries in cluster copier tests (#46590) --- programs/copier/ClusterCopier.cpp | 4 ++-- tests/integration/test_cluster_copier/test.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/programs/copier/ClusterCopier.cpp b/programs/copier/ClusterCopier.cpp index 48a3578dd7b..bc882719a08 100644 --- a/programs/copier/ClusterCopier.cpp +++ b/programs/copier/ClusterCopier.cpp @@ -908,7 +908,7 @@ bool ClusterCopier::tryProcessTable(const ConnectionTimeouts & timeouts, TaskTab /// Exit if success if (task_status != TaskStatus::Finished) { - LOG_WARNING(log, "Create destination Tale Failed "); + LOG_WARNING(log, "Create destination table failed "); return false; } @@ -1473,7 +1473,7 @@ TaskStatus ClusterCopier::processPartitionPieceTaskImpl( if (count != 0) { - LOG_INFO(log, "Partition {} piece {}is not empty. In contains {} rows.", task_partition.name, current_piece_number, count); + LOG_INFO(log, "Partition {} piece {} is not empty. In contains {} rows.", task_partition.name, current_piece_number, count); Coordination::Stat stat_shards{}; zookeeper->get(partition_piece.getPartitionPieceShardsPath(), &stat_shards); diff --git a/tests/integration/test_cluster_copier/test.py b/tests/integration/test_cluster_copier/test.py index 0aadcadc064..b261f7e3a39 100644 --- a/tests/integration/test_cluster_copier/test.py +++ b/tests/integration/test_cluster_copier/test.py @@ -565,13 +565,20 @@ def test_copy_with_recovering(started_cluster, use_sample_offset): str(COPYING_FAIL_PROBABILITY), "--experimental-use-sample-offset", "1", + "--max-table-tries", + "10", ], ) else: execute_task( started_cluster, Task1(started_cluster), - ["--copy-fault-probability", str(COPYING_FAIL_PROBABILITY)], + [ + "--copy-fault-probability", + str(COPYING_FAIL_PROBABILITY), + "--max-table-tries", + "10", + ], ) @@ -606,7 +613,12 @@ def test_copy_month_to_week_partition_with_recovering(started_cluster): execute_task( started_cluster, Task2(started_cluster, "test2"), - ["--copy-fault-probability", str(COPYING_FAIL_PROBABILITY)], + [ + "--copy-fault-probability", + str(COPYING_FAIL_PROBABILITY), + "--max-table-tries", + "10", + ], ) From 9ab4944b9ed0d0d126688d153477e5bbfd0d0fdd Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 22 Feb 2023 13:55:55 +0100 Subject: [PATCH 130/445] Handle input_format_null_as_default for nested types Signed-off-by: Azat Khuzhin --- .../Serializations/SerializationArray.cpp | 6 ++- .../Serializations/SerializationMap.cpp | 6 ++- .../Serializations/SerializationNullable.cpp | 6 +-- .../Serializations/SerializationTuple.cpp | 6 ++- ...nore_unknown_keys_in_named_tuple.reference | 8 ++-- ...json_ignore_unknown_keys_in_named_tuple.sh | 23 ++++------ ..._as_default_null_as_empty_nested.reference | 42 +++++++++++++++++++ ...t_null_as_default_null_as_empty_nested.sql | 25 +++++++++++ 8 files changed, 94 insertions(+), 28 deletions(-) create mode 100644 tests/queries/0_stateless/02573_insert_null_as_default_null_as_empty_nested.reference create mode 100644 tests/queries/0_stateless/02573_insert_null_as_default_null_as_empty_nested.sql diff --git a/src/DataTypes/Serializations/SerializationArray.cpp b/src/DataTypes/Serializations/SerializationArray.cpp index 24aa9e8320d..73b232690c7 100644 --- a/src/DataTypes/Serializations/SerializationArray.cpp +++ b/src/DataTypes/Serializations/SerializationArray.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -510,7 +511,10 @@ void SerializationArray::deserializeTextJSON(IColumn & column, ReadBuffer & istr deserializeTextImpl(column, istr, [&](IColumn & nested_column) { - nested->deserializeTextJSON(nested_column, istr, settings); + if (settings.null_as_default) + SerializationNullable::deserializeTextJSONImpl(nested_column, istr, settings, nested); + else + nested->deserializeTextJSON(nested_column, istr, settings); }, false); } diff --git a/src/DataTypes/Serializations/SerializationMap.cpp b/src/DataTypes/Serializations/SerializationMap.cpp index 98067077178..34da0f11cae 100644 --- a/src/DataTypes/Serializations/SerializationMap.cpp +++ b/src/DataTypes/Serializations/SerializationMap.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -211,7 +212,10 @@ void SerializationMap::deserializeTextJSON(IColumn & column, ReadBuffer & istr, deserializeTextImpl(column, istr, [&settings](ReadBuffer & buf, const SerializationPtr & subcolumn_serialization, IColumn & subcolumn) { - subcolumn_serialization->deserializeTextJSON(subcolumn, buf, settings); + if (settings.null_as_default) + SerializationNullable::deserializeTextJSONImpl(subcolumn, buf, settings, subcolumn_serialization); + else + subcolumn_serialization->deserializeTextJSON(subcolumn, buf, settings); }); } diff --git a/src/DataTypes/Serializations/SerializationNullable.cpp b/src/DataTypes/Serializations/SerializationNullable.cpp index 8b0bdc05d00..20188f7cec5 100644 --- a/src/DataTypes/Serializations/SerializationNullable.cpp +++ b/src/DataTypes/Serializations/SerializationNullable.cpp @@ -219,13 +219,9 @@ static ReturnType safeDeserialize( /// Deserialize value into non-nullable column. In case of NULL, insert default value and return false. template , ReturnType>* = nullptr> static ReturnType safeDeserialize( - IColumn & column, const ISerialization & nested, + IColumn & column, const ISerialization &, CheckForNull && check_for_null, DeserializeNested && deserialize_nested) { - assert(!dynamic_cast(&column)); - assert(!dynamic_cast(&nested)); - UNUSED(nested); - bool insert_default = check_for_null(); if (insert_default) column.insertDefault(); diff --git a/src/DataTypes/Serializations/SerializationTuple.cpp b/src/DataTypes/Serializations/SerializationTuple.cpp index ce15e099222..ef11b3e4660 100644 --- a/src/DataTypes/Serializations/SerializationTuple.cpp +++ b/src/DataTypes/Serializations/SerializationTuple.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -231,7 +232,10 @@ void SerializationTuple::deserializeTextJSON(IColumn & column, ReadBuffer & istr seen_elements[element_pos] = 1; auto & element_column = extractElementColumn(column, element_pos); - elems[element_pos]->deserializeTextJSON(element_column, istr, settings); + if (settings.null_as_default) + SerializationNullable::deserializeTextJSONImpl(element_column, istr, settings, elems[element_pos]); + else + elems[element_pos]->deserializeTextJSON(element_column, istr, settings); skipWhitespaceIfAny(istr); ++processed; diff --git a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference index a1b4e2b5a83..b7edddf46e0 100644 --- a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference +++ b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.reference @@ -3,11 +3,11 @@ INCORRECT_DATA NOT_FOUND_COLUMN_IN_BLOCK (1) { - "row_1": {"type":"CreateEvent","actor":{"login":"foobar"},"repo":{"name":"ClickHouse\/ClickHouse"},"created_at":"2023-01-26 10:48:02","payload":{"updated_at":"1970-01-01 00:00:00","action":"","comment":{"id":"0","path":"","position":0,"line":0,"user":{"login":""},"diff_hunk":"","original_position":0,"commit_id":"","original_commit_id":""},"review":{"body":"","author_association":"","state":""},"ref":"backport","ref_type":"branch","issue":{"number":0,"title":"","labels":[],"state":"","locked":0,"assignee":{"login":""},"assignees":[],"comment":"","closed_at":"1970-01-01 00:00:00"},"pull_request":{"merged_at":null,"merge_commit_sha":"","requested_reviewers":[],"requested_teams":[],"head":{"ref":"","sha":""},"base":{"ref":"","sha":""},"merged":0,"mergeable":0,"rebaseable":0,"mergeable_state":"","merged_by":null,"review_comments":0,"maintainer_can_modify":0,"commits":0,"additions":0,"deletions":0,"changed_files":0},"size":0,"distinct_size":0,"member":{"login":""},"release":{"tag_name":"","name":""}}} + "row_1": {"type":"CreateEvent","actor":{"login":"foobar"},"repo":{"name":"ClickHouse\/ClickHouse"},"created_at":"2023-01-26 10:48:02","payload":{"updated_at":"1970-01-01 00:00:00","action":"","comment":{"id":"0","path":"","position":0,"line":0,"user":{"login":""},"diff_hunk":"","original_position":0,"commit_id":"","original_commit_id":""},"review":{"body":"","author_association":"","state":""},"ref":"backport","ref_type":"branch","issue":{"number":0,"title":"","labels":[],"state":"","locked":0,"assignee":{"login":""},"assignees":[],"comment":"","closed_at":"1970-01-01 00:00:00"},"pull_request":{"merged_at":"1970-01-01 00:00:00","merge_commit_sha":"","requested_reviewers":[],"requested_teams":[],"head":{"ref":"","sha":""},"base":{"ref":"","sha":""},"merged":0,"mergeable":0,"rebaseable":0,"mergeable_state":"","merged_by":{"login":""},"review_comments":0,"maintainer_can_modify":0,"commits":0,"additions":0,"deletions":0,"changed_files":0},"size":0,"distinct_size":0,"member":{"login":""},"release":{"tag_name":"","name":""}}} } { - "row_1": {"labels":[],"merged_by":""}, + "row_1": {"labels":[],"merged_by":""}, "row_2": {"labels":[],"merged_by":"foobar"}, - "row_3": {"labels":[],"merged_by":""}, - "row_4": {"labels":["backport"],"merged_by":""} + "row_3": {"labels":[],"merged_by":""}, + "row_4": {"labels":["backport"],"merged_by":""} } diff --git a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh index f37a36fa192..eccac543215 100755 --- a/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh +++ b/tests/queries/0_stateless/02540_input_format_json_ignore_unknown_keys_in_named_tuple.sh @@ -60,7 +60,7 @@ gharchive_structure=( closed_at DateTime('UTC') ), pull_request Tuple( - merged_at Nullable(DateTime('UTC')), + merged_at DateTime('UTC'), merge_commit_sha String, requested_reviewers Nested( login String @@ -80,16 +80,9 @@ gharchive_structure=( mergeable UInt8, rebaseable UInt8, mergeable_state String, - merged_by Nullable(String), - /* NOTE: correct type is Tuple, however Tuple cannot be Nullable, - * so you still have to use Nullable(String) and rely on - * input_format_json_read_objects_as_strings, but see also - * https://github.com/ClickHouse/ClickHouse/issues/36464 - */ - /* merged_by Tuple( - * login String - * ), - */ + merged_by Tuple( + login String + ), review_comments UInt32, maintainer_can_modify UInt8, commits UInt32, @@ -122,12 +115,10 @@ EOL # NOTE: due to [1] we cannot use dot.dot notation, only tupleElement() # # [1]: https://github.com/ClickHouse/ClickHouse/issues/24607 -$CLICKHOUSE_LOCAL "${gharchive_settings[@]}" --structure="${gharchive_structure[*]}" -q " - WITH - tupleElement(tupleElement(payload, 'pull_request'), 'merged_by') AS merged_by_ +$CLICKHOUSE_LOCAL --allow_experimental_analyzer=1 "${gharchive_settings[@]}" --structure="${gharchive_structure[*]}" -q " SELECT - tupleElement(tupleElement(tupleElement(payload, 'issue'), 'labels'), 'name') AS labels, - if(merged_by_ IS NULL, '', JSONExtractString(merged_by_, 'login')) AS merged_by + payload.issue.labels.name AS labels, + payload.pull_request.merged_by.login AS merged_by FROM table " < Date: Wed, 22 Feb 2023 15:51:13 +0100 Subject: [PATCH 131/445] Analyzer AutoFinalOnQueryPass fix --- src/Analyzer/Passes/AutoFinalOnQueryPass.cpp | 87 ++++++++++++-------- src/Analyzer/Passes/AutoFinalOnQueryPass.h | 16 +++- src/Analyzer/TableExpressionModifiers.h | 6 ++ src/Analyzer/TableFunctionNode.h | 6 ++ src/Analyzer/TableNode.h | 6 ++ 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp b/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp index 10efebe0731..fdf818681d7 100644 --- a/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp +++ b/src/Analyzer/Passes/AutoFinalOnQueryPass.cpp @@ -1,8 +1,11 @@ #include "AutoFinalOnQueryPass.h" -#include -#include #include + +#include +#include +#include +#include #include namespace DB @@ -10,52 +13,64 @@ namespace DB namespace { - class AutoFinalOnQueryPassVisitor : public InDepthQueryTreeVisitorWithContext + +class AutoFinalOnQueryPassVisitor : public InDepthQueryTreeVisitorWithContext +{ +public: + using Base = InDepthQueryTreeVisitorWithContext; + using Base::Base; + + void visitImpl(QueryTreeNodePtr & node) { - public: - using Base = InDepthQueryTreeVisitorWithContext; - using Base::Base; + const auto & context = getContext(); + if (!context->getSettingsRef().final) + return; - void visitImpl(QueryTreeNodePtr & node) + const auto * query_node = node->as(); + if (!query_node) + return; + + auto table_expressions = extractTableExpressions(query_node->getJoinTree()); + for (auto & table_expression : table_expressions) + applyFinalIfNeeded(table_expression); + } +private: + static void applyFinalIfNeeded(QueryTreeNodePtr & node) + { + auto * table_node = node->as(); + auto * table_function_node = node->as(); + if (!table_node && !table_function_node) + return; + + const auto & storage = table_node ? table_node->getStorage() : table_function_node->getStorage(); + bool is_final_supported = storage && storage->supportsFinal() && !storage->isRemote(); + if (!is_final_supported) + return; + + TableExpressionModifiers table_expression_modifiers_with_final(true /*has_final*/, {}, {}); + + if (table_node) { - if (auto * table_node = node->as()) - { - if (autoFinalOnQuery(*table_node, table_node->getStorage(), getContext())) - { - auto modifier = TableExpressionModifiers(true, std::nullopt, std::nullopt); - table_node->setTableExpressionModifiers(modifier); - } - } + if (table_node->hasTableExpressionModifiers()) + table_node->getTableExpressionModifiers()->setHasFinal(true); + else + table_node->setTableExpressionModifiers(table_expression_modifiers_with_final); } - - private: - static bool autoFinalOnQuery(TableNode & table_node, StoragePtr storage, ContextPtr context) + else if (table_function_node) { - bool is_auto_final_setting_on = context->getSettingsRef().final; - bool is_final_supported = storage && storage->supportsFinal() && !storage->isRemote(); - bool is_query_already_final = table_node.hasTableExpressionModifiers() ? table_node.getTableExpressionModifiers().has_value() : false; - - return is_auto_final_setting_on && !is_query_already_final && is_final_supported; + if (table_function_node->hasTableExpressionModifiers()) + table_function_node->getTableExpressionModifiers()->setHasFinal(true); + else + table_function_node->setTableExpressionModifiers(table_expression_modifiers_with_final); } + } +}; - }; - -} - -String AutoFinalOnQueryPass::getName() -{ - return "AutoFinalOnQueryPass"; -} - -String AutoFinalOnQueryPass::getDescription() -{ - return "Automatically applies final modifier to queries if it is supported and if user level final setting is set."; } void AutoFinalOnQueryPass::run(QueryTreeNodePtr query_tree_node, ContextPtr context) { auto visitor = AutoFinalOnQueryPassVisitor(std::move(context)); - visitor.visit(query_tree_node); } diff --git a/src/Analyzer/Passes/AutoFinalOnQueryPass.h b/src/Analyzer/Passes/AutoFinalOnQueryPass.h index eacbe0f8235..3489597108c 100644 --- a/src/Analyzer/Passes/AutoFinalOnQueryPass.h +++ b/src/Analyzer/Passes/AutoFinalOnQueryPass.h @@ -7,13 +7,23 @@ namespace DB { - +/** Automatically applies final modifier to table expressions in queries if it is supported and if user level final setting is set. + * + * Example: SELECT id, value FROM test_table; + * Result: SELECT id, value FROM test_table FINAL; + */ class AutoFinalOnQueryPass final : public IQueryTreePass { public: - String getName() override; + String getName() override + { + return "AutoFinalOnQueryPass"; + } - String getDescription() override; + String getDescription() override + { + return "Automatically applies final modifier to table expressions in queries if it is supported and if user level final setting is set"; + } void run(QueryTreeNodePtr query_tree_node, ContextPtr context) override; }; diff --git a/src/Analyzer/TableExpressionModifiers.h b/src/Analyzer/TableExpressionModifiers.h index f61c2a61610..9b76c9bc0fd 100644 --- a/src/Analyzer/TableExpressionModifiers.h +++ b/src/Analyzer/TableExpressionModifiers.h @@ -28,6 +28,12 @@ public: return has_final; } + /// Set has final value + void setHasFinal(bool value) + { + has_final = value; + } + /// Returns true if sample size ratio is specified, false otherwise bool hasSampleSizeRatio() const { diff --git a/src/Analyzer/TableFunctionNode.h b/src/Analyzer/TableFunctionNode.h index 292ab740c5b..a88630ffd00 100644 --- a/src/Analyzer/TableFunctionNode.h +++ b/src/Analyzer/TableFunctionNode.h @@ -116,6 +116,12 @@ public: return table_expression_modifiers; } + /// Get table expression modifiers + std::optional & getTableExpressionModifiers() + { + return table_expression_modifiers; + } + /// Set table expression modifiers void setTableExpressionModifiers(TableExpressionModifiers table_expression_modifiers_value) { diff --git a/src/Analyzer/TableNode.h b/src/Analyzer/TableNode.h index 4965de535df..6d47f87c78b 100644 --- a/src/Analyzer/TableNode.h +++ b/src/Analyzer/TableNode.h @@ -68,6 +68,12 @@ public: return table_expression_modifiers; } + /// Get table expression modifiers + std::optional & getTableExpressionModifiers() + { + return table_expression_modifiers; + } + /// Set table expression modifiers void setTableExpressionModifiers(TableExpressionModifiers table_expression_modifiers_value) { From cdbff57e6c91930b7b4eb9535f6b4d17e17641be Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky <43110995+evillique@users.noreply.github.com> Date: Wed, 22 Feb 2023 15:58:06 +0100 Subject: [PATCH 132/445] Ask for password interactively --- programs/client/Client.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 3be96a4b0a0..af66a4ac61d 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -327,7 +327,29 @@ try showClientVersion(); } - connect(); + try + { + connect(); + } + catch (const Exception & e) + { + if (e.code() == DB::ErrorCodes::AUTHENTICATION_FAILED) + { + if (!config().getString("password", "").empty()) + throw; + + if (!is_interactive) + throw; + + String prompt = fmt::format("Password for user ({}): ", config().getString("user", "")); + String password; + if (auto * result = readpassphrase(prompt, buf, sizeof(buf), 0)) + password = result; + + config().setString("password", password); + connect(); + } + } /// Show warnings at the beginning of connection. if (is_interactive && !config().has("no-warnings")) From 986dd728705230e8debd5d88289193e50ff284b2 Mon Sep 17 00:00:00 2001 From: avogar Date: Wed, 22 Feb 2023 15:18:13 +0000 Subject: [PATCH 133/445] Fix possible clickhouse-local abort on JSONEachRow schema inference --- src/Processors/Formats/ISchemaReader.cpp | 4 +++- ...local_desc_abort_on_twitter_json.reference | 1 + .../02669_local_desc_abort_on_twitter_json.sh | 8 ++++++++ .../0_stateless/data_json/twitter.jsonl | Bin 0 -> 9940434 bytes 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference create mode 100755 tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh create mode 100644 tests/queries/0_stateless/data_json/twitter.jsonl diff --git a/src/Processors/Formats/ISchemaReader.cpp b/src/Processors/Formats/ISchemaReader.cpp index 48cb093f0ab..48dcdc657e6 100644 --- a/src/Processors/Formats/ISchemaReader.cpp +++ b/src/Processors/Formats/ISchemaReader.cpp @@ -223,8 +223,10 @@ NamesAndTypesList IRowWithNamesSchemaReader::readSchema() break; std::unordered_set names_set; /// We should check for duplicate column names in current row - for (auto & [name, new_type] : new_names_and_types) + for (auto & new_name_and_type : new_names_and_types) { + auto & name = new_name_and_type.name; + auto & new_type = new_name_and_type.type; if (names_set.contains(name)) throw Exception(ErrorCodes::INCORRECT_DATA, "Duplicate column name found while schema inference: \"{}\"", name); names_set.insert(name); diff --git a/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.reference @@ -0,0 +1 @@ +1 diff --git a/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh new file mode 100755 index 00000000000..e4f738f18ff --- /dev/null +++ b/tests/queries/0_stateless/02669_local_desc_abort_on_twitter_json.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +$CLICKHOUSE_LOCAL -q "desc file('$CUR_DIR/data_json/twitter.jsonl')" 2>&1 | grep -c "ONLY_NULLS_WHILE_READING_SCHEMA" + diff --git a/tests/queries/0_stateless/data_json/twitter.jsonl b/tests/queries/0_stateless/data_json/twitter.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..4c5eaabcb1b1478b4b126b2168b2353668b1fe53 GIT binary patch literal 9940434 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
EN1Dr1ePMli1QRC- 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 z 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