Merge branch 'master' into perf-test-questdb

This commit is contained in:
Alexey Milovidov 2020-07-04 23:34:51 +03:00
commit 43218567fa
537 changed files with 17721 additions and 3344 deletions

37
.github/workflows/anchore-analysis.yml vendored Normal file
View File

@ -0,0 +1,37 @@
# This workflow checks out code, performs an Anchore container image
# vulnerability and compliance scan, and integrates the results with
# GitHub Advanced Security code scanning feature. For more information on
# the Anchore scan action usage and parameters, see
# https://github.com/anchore/scan-action. For more information on
# Anchore container image scanning in general, see
# https://docs.anchore.com.
name: Docker Container Scan (clickhouse-server)
on:
pull_request:
paths: docker/server/Dockerfile
schedule:
- cron: '0 21 * * *'
jobs:
Anchore-Build-Scan:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v2
- name: Build the Docker image
run: |
cd docker/server
perl -pi -e 's|=\$version||g' Dockerfile
docker build . --file Dockerfile --tag localbuild/testimage:latest
- name: Run the local Anchore scan action itself with GitHub Advanced Security code scanning integration enabled
uses: anchore/scan-action@master
with:
image-reference: "localbuild/testimage:latest"
dockerfile-path: "docker/server/Dockerfile"
acs-report-enable: true
- name: Upload Anchore Scan Report
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: results.sarif

33
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: "CodeQL Scanning"
on:
schedule:
- cron: '0 19 * * *'
jobs:
CodeQL-Build:
runs-on: self-hosted
timeout-minutes: 1440
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 2
submodules: 'recursive'
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: cpp
- run: sudo apt-get update && sudo apt-get install -y git cmake python ninja-build gcc-9 g++-9 && mkdir build
- run: cd build && CC=gcc-9 CXX=g++-9 cmake ..
- run: cd build && ninja
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

5
.gitmodules vendored
View File

@ -1,6 +1,6 @@
[submodule "contrib/poco"]
path = contrib/poco
url = https://github.com/ClickHouse-Extras/poco
url = https://github.com/ClickHouse-Extras/poco.git
branch = clickhouse
[submodule "contrib/zstd"]
path = contrib/zstd
@ -157,6 +157,9 @@
[submodule "contrib/openldap"]
path = contrib/openldap
url = https://github.com/openldap/openldap.git
[submodule "contrib/AMQP-CPP"]
path = contrib/AMQP-CPP
url = https://github.com/CopernicaMarketingSoftware/AMQP-CPP.git
[submodule "contrib/cassandra"]
path = contrib/cassandra
url = https://github.com/ClickHouse-Extras/cpp-driver.git

View File

@ -323,6 +323,81 @@
## ClickHouse release v20.3
### ClickHouse release v20.3.12.112-lts 2020-06-25
#### Bug Fix
* Fix rare crash caused by using `Nullable` column in prewhere condition. Continuation of [#11608](https://github.com/ClickHouse/ClickHouse/issues/11608). [#11869](https://github.com/ClickHouse/ClickHouse/pull/11869) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Don't allow arrayJoin inside higher order functions. It was leading to broken protocol synchronization. This closes [#3933](https://github.com/ClickHouse/ClickHouse/issues/3933). [#11846](https://github.com/ClickHouse/ClickHouse/pull/11846) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix using too many threads for queries. [#11788](https://github.com/ClickHouse/ClickHouse/pull/11788) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fix unexpected behaviour of queries like `SELECT *, xyz.*` which were success while an error expected. [#11753](https://github.com/ClickHouse/ClickHouse/pull/11753) ([hexiaoting](https://github.com/hexiaoting)).
* Now replicated fetches will be cancelled during metadata alter. [#11744](https://github.com/ClickHouse/ClickHouse/pull/11744) ([alesapin](https://github.com/alesapin)).
* Fixed LOGICAL_ERROR caused by wrong type deduction of complex literals in Values input format. [#11732](https://github.com/ClickHouse/ClickHouse/pull/11732) ([tavplubix](https://github.com/tavplubix)).
* Fix `ORDER BY ... WITH FILL` over const columns. [#11697](https://github.com/ClickHouse/ClickHouse/pull/11697) ([Anton Popov](https://github.com/CurtizJ)).
* Pass proper timeouts when communicating with XDBC bridge. Recently timeouts were not respected when checking bridge liveness and receiving meta info. [#11690](https://github.com/ClickHouse/ClickHouse/pull/11690) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix error which leads to an incorrect state of `system.mutations`. It may show that whole mutation is already done but the server still has `MUTATE_PART` tasks in the replication queue and tries to execute them. This fixes [#11611](https://github.com/ClickHouse/ClickHouse/issues/11611). [#11681](https://github.com/ClickHouse/ClickHouse/pull/11681) ([alesapin](https://github.com/alesapin)).
* Add support for regular expressions with case-insensitive flags. This fixes [#11101](https://github.com/ClickHouse/ClickHouse/issues/11101) and fixes [#11506](https://github.com/ClickHouse/ClickHouse/issues/11506). [#11649](https://github.com/ClickHouse/ClickHouse/pull/11649) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Remove trivial count query optimization if row-level security is set. In previous versions the user get total count of records in a table instead filtered. This fixes [#11352](https://github.com/ClickHouse/ClickHouse/issues/11352). [#11644](https://github.com/ClickHouse/ClickHouse/pull/11644) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix bloom filters for String (data skipping indices). [#11638](https://github.com/ClickHouse/ClickHouse/pull/11638) ([Azat Khuzhin](https://github.com/azat)).
* Fix rare crash caused by using `Nullable` column in prewhere condition. (Probably it is connected with [#11572](https://github.com/ClickHouse/ClickHouse/issues/11572) somehow). [#11608](https://github.com/ClickHouse/ClickHouse/pull/11608) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fix error `Block structure mismatch` for queries with sampling reading from `Buffer` table. [#11602](https://github.com/ClickHouse/ClickHouse/pull/11602) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fix wrong exit code of the clickhouse-client, when exception.code() % 256 = 0. [#11601](https://github.com/ClickHouse/ClickHouse/pull/11601) ([filimonov](https://github.com/filimonov)).
* Fix trivial error in log message about "Mark cache size was lowered" at server startup. This closes [#11399](https://github.com/ClickHouse/ClickHouse/issues/11399). [#11589](https://github.com/ClickHouse/ClickHouse/pull/11589) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix error `Size of offsets doesn't match size of column` for queries with `PREWHERE column in (subquery)` and `ARRAY JOIN`. [#11580](https://github.com/ClickHouse/ClickHouse/pull/11580) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* All queries in HTTP session have had the same query_id. It is fixed. [#11578](https://github.com/ClickHouse/ClickHouse/pull/11578) ([tavplubix](https://github.com/tavplubix)).
* Now clickhouse-server docker container will prefer IPv6 checking server aliveness. [#11550](https://github.com/ClickHouse/ClickHouse/pull/11550) ([Ivan Starkov](https://github.com/istarkov)).
* Fix shard_num/replica_num for `<node>` (breaks use_compact_format_in_distributed_parts_names). [#11528](https://github.com/ClickHouse/ClickHouse/pull/11528) ([Azat Khuzhin](https://github.com/azat)).
* Fix memory leak when exception is thrown in the middle of aggregation with -State functions. This fixes [#8995](https://github.com/ClickHouse/ClickHouse/issues/8995). [#11496](https://github.com/ClickHouse/ClickHouse/pull/11496) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix wrong results of distributed queries when alias could override qualified column name. Fixes [#9672](https://github.com/ClickHouse/ClickHouse/issues/9672) [#9714](https://github.com/ClickHouse/ClickHouse/issues/9714). [#9972](https://github.com/ClickHouse/ClickHouse/pull/9972) ([Artem Zuikov](https://github.com/4ertus2)).
### ClickHouse release v20.3.11.97-lts 2020-06-10
#### New Feature
* Now ClickHouse controls timeouts of dictionary sources on its side. Two new settings added to cache dictionary configuration: `strict_max_lifetime_seconds`, which is `max_lifetime` by default and `query_wait_timeout_milliseconds`, which is one minute by default. The first settings is also useful with `allow_read_expired_keys` settings (to forbid reading very expired keys). [#10337](https://github.com/ClickHouse/ClickHouse/pull/10337) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)).
#### Bug Fix
* Fix the error `Data compressed with different methods` that can happen if `min_bytes_to_use_direct_io` is enabled and PREWHERE is active and using SAMPLE or high number of threads. This fixes [#11539](https://github.com/ClickHouse/ClickHouse/issues/11539). [#11540](https://github.com/ClickHouse/ClickHouse/pull/11540) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix return compressed size for codecs. [#11448](https://github.com/ClickHouse/ClickHouse/pull/11448) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fix server crash when a column has compression codec with non-literal arguments. Fixes [#11365](https://github.com/ClickHouse/ClickHouse/issues/11365). [#11431](https://github.com/ClickHouse/ClickHouse/pull/11431) ([alesapin](https://github.com/alesapin)).
* Fix pointInPolygon with nan as point. Fixes https://github.com/ClickHouse/ClickHouse/issues/11375. [#11421](https://github.com/ClickHouse/ClickHouse/pull/11421) ([Alexey Ilyukhov](https://github.com/livace)).
* Fix crash in JOIN over LowCarinality(T) and Nullable(T). [#11380](https://github.com/ClickHouse/ClickHouse/issues/11380). [#11414](https://github.com/ClickHouse/ClickHouse/pull/11414) ([Artem Zuikov](https://github.com/4ertus2)).
* Fix error code for wrong `USING` key. [#11373](https://github.com/ClickHouse/ClickHouse/issues/11373). [#11404](https://github.com/ClickHouse/ClickHouse/pull/11404) ([Artem Zuikov](https://github.com/4ertus2)).
* Fixed geohashesInBox with arguments outside of latitude/longitude range. [#11403](https://github.com/ClickHouse/ClickHouse/pull/11403) ([Vasily Nemkov](https://github.com/Enmk)).
* Better errors for `joinGet()` functions. [#11389](https://github.com/ClickHouse/ClickHouse/pull/11389) ([Artem Zuikov](https://github.com/4ertus2)).
* Fix possible `Pipeline stuck` error for queries with external sort and limit. Fixes [#11359](https://github.com/ClickHouse/ClickHouse/issues/11359). [#11366](https://github.com/ClickHouse/ClickHouse/pull/11366) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Remove redundant lock during parts send in ReplicatedMergeTree. [#11354](https://github.com/ClickHouse/ClickHouse/pull/11354) ([alesapin](https://github.com/alesapin)).
* Fix support for `\G` (vertical output) in clickhouse-client in multiline mode. This closes [#9933](https://github.com/ClickHouse/ClickHouse/issues/9933). [#11350](https://github.com/ClickHouse/ClickHouse/pull/11350) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix crash in direct selects from StorageJoin (without JOIN) and wrong nullability. [#11340](https://github.com/ClickHouse/ClickHouse/pull/11340) ([Artem Zuikov](https://github.com/4ertus2)).
* Fix crash in `quantilesExactWeightedArray`. [#11337](https://github.com/ClickHouse/ClickHouse/pull/11337) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Now merges stopped before change metadata in `ALTER` queries. [#11335](https://github.com/ClickHouse/ClickHouse/pull/11335) ([alesapin](https://github.com/alesapin)).
* Make writing to `MATERIALIZED VIEW` with setting `parallel_view_processing = 1` parallel again. Fixes [#10241](https://github.com/ClickHouse/ClickHouse/issues/10241). [#11330](https://github.com/ClickHouse/ClickHouse/pull/11330) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fix visitParamExtractRaw when extracted JSON has strings with unbalanced { or [. [#11318](https://github.com/ClickHouse/ClickHouse/pull/11318) ([Ewout](https://github.com/devwout)).
* Fix very rare race condition in ThreadPool. [#11314](https://github.com/ClickHouse/ClickHouse/pull/11314) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix potential uninitialized memory in conversion. Example: `SELECT toIntervalSecond(now64())`. [#11311](https://github.com/ClickHouse/ClickHouse/pull/11311) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix the issue when index analysis cannot work if a table has Array column in primary key and if a query is filtering by this column with `empty` or `notEmpty` functions. This fixes [#11286](https://github.com/ClickHouse/ClickHouse/issues/11286). [#11303](https://github.com/ClickHouse/ClickHouse/pull/11303) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix bug when query speed estimation can be incorrect and the limit of `min_execution_speed` may not work or work incorrectly if the query is throttled by `max_network_bandwidth`, `max_execution_speed` or `priority` settings. Change the default value of `timeout_before_checking_execution_speed` to non-zero, because otherwise the settings `min_execution_speed` and `max_execution_speed` have no effect. This fixes [#11297](https://github.com/ClickHouse/ClickHouse/issues/11297). This fixes [#5732](https://github.com/ClickHouse/ClickHouse/issues/5732). This fixes [#6228](https://github.com/ClickHouse/ClickHouse/issues/6228). Usability improvement: avoid concatenation of exception message with progress bar in `clickhouse-client`. [#11296](https://github.com/ClickHouse/ClickHouse/pull/11296) ([alexey-milovidov](https://github.com/alexey-milovidov)).
* Fix crash while reading malformed data in Protobuf format. This fixes https://github.com/ClickHouse/ClickHouse/issues/5957, fixes https://github.com/ClickHouse/ClickHouse/issues/11203. [#11258](https://github.com/ClickHouse/ClickHouse/pull/11258) ([Vitaly Baranov](https://github.com/vitlibar)).
* Fixed a bug when cache-dictionary could return default value instead of normal (when there are only expired keys). This affects only string fields. [#11233](https://github.com/ClickHouse/ClickHouse/pull/11233) ([Nikita Mikhaylov](https://github.com/nikitamikhaylov)).
* Fix error `Block structure mismatch in QueryPipeline` while reading from `VIEW` with constants in inner query. Fixes [#11181](https://github.com/ClickHouse/ClickHouse/issues/11181). [#11205](https://github.com/ClickHouse/ClickHouse/pull/11205) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fix possible exception `Invalid status for associated output`. [#11200](https://github.com/ClickHouse/ClickHouse/pull/11200) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fix possible error `Cannot capture column` for higher-order functions with `Array(Array(LowCardinality))` captured argument. [#11185](https://github.com/ClickHouse/ClickHouse/pull/11185) ([Nikolai Kochetov](https://github.com/KochetovNicolai)).
* Fixed S3 globbing which could fail in case of more than 1000 keys and some backends. [#11179](https://github.com/ClickHouse/ClickHouse/pull/11179) ([Vladimir Chebotarev](https://github.com/excitoon)).
* If data skipping index is dependent on columns that are going to be modified during background merge (for SummingMergeTree, AggregatingMergeTree as well as for TTL GROUP BY), it was calculated incorrectly. This issue is fixed by moving index calculation after merge so the index is calculated on merged data. [#11162](https://github.com/ClickHouse/ClickHouse/pull/11162) ([Azat Khuzhin](https://github.com/azat)).
* Fix excessive reserving of threads for simple queries (optimization for reducing the number of threads, which was partly broken after changes in pipeline). [#11114](https://github.com/ClickHouse/ClickHouse/pull/11114) ([Azat Khuzhin](https://github.com/azat)).
* Fix predicates optimization for distributed queries (`enable_optimize_predicate_expression=1`) for queries with `HAVING` section (i.e. when filtering on the server initiator is required), by preserving the order of expressions (and this is enough to fix), and also force aggregator use column names over indexes. Fixes: [#10613](https://github.com/ClickHouse/ClickHouse/issues/10613), [#11413](https://github.com/ClickHouse/ClickHouse/issues/11413). [#10621](https://github.com/ClickHouse/ClickHouse/pull/10621) ([Azat Khuzhin](https://github.com/azat)).
* Introduce commit retry logic to decrease the possibility of getting duplicates from Kafka in rare cases when offset commit was failed. [#9884](https://github.com/ClickHouse/ClickHouse/pull/9884) ([filimonov](https://github.com/filimonov)).
#### Performance Improvement
* Get dictionary and check access rights only once per each call of any function reading external dictionaries. [#10928](https://github.com/ClickHouse/ClickHouse/pull/10928) ([Vitaly Baranov](https://github.com/vitlibar)).
#### Build/Testing/Packaging Improvement
* Fix several flaky integration tests. [#11355](https://github.com/ClickHouse/ClickHouse/pull/11355) ([alesapin](https://github.com/alesapin)).
### ClickHouse release v20.3.10.75-lts 2020-05-23
#### Bug Fix

View File

@ -342,6 +342,7 @@ include (cmake/find/sparsehash.cmake)
include (cmake/find/re2.cmake)
include (cmake/find/libgsasl.cmake)
include (cmake/find/rdkafka.cmake)
include (cmake/find/amqpcpp.cmake)
include (cmake/find/capnp.cmake)
include (cmake/find/llvm.cmake)
include (cmake/find/opencl.cmake)

View File

@ -13,3 +13,7 @@ ClickHouse is an open-source column-oriented database management system that all
* [Yandex.Messenger channel](https://yandex.ru/chat/#/join/20e380d9-c7be-4123-ab06-e95fb946975e) shares announcements and useful links in Russian.
* [Contacts](https://clickhouse.tech/#contacts) can help to get your questions answered if there are any.
* You can also [fill this form](https://clickhouse.tech/#meet) to meet Yandex ClickHouse team in person.
## Upcoming Events
* [ClickHouse at Yandex Cloud Webinar (in Russian)](https://cloud.yandex.ru/events/144) on July 7, 2020.

View File

@ -48,7 +48,7 @@ protected:
};
const String history_file_path;
static constexpr char word_break_characters[] = " \t\n\r\"\\'`@$><=;|&{(.";
static constexpr char word_break_characters[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?_";
String input;

View File

@ -628,7 +628,7 @@ void BaseDaemon::initialize(Application & self)
/// Create pid file.
if (config().has("pid"))
pid.emplace(config().getString("pid"));
pid.emplace(config().getString("pid"), DB::StatusFile::write_pid);
/// Change path for logging.
if (!log_path.empty())
@ -812,63 +812,6 @@ void BaseDaemon::defineOptions(Poco::Util::OptionSet & new_options)
Poco::Util::ServerApplication::defineOptions(new_options);
}
bool isPidRunning(pid_t pid)
{
return getpgid(pid) >= 0;
}
BaseDaemon::PID::PID(const std::string & file_)
{
file = Poco::Path(file_).absolute().toString();
Poco::File poco_file(file);
if (poco_file.exists())
{
pid_t pid_read = 0;
{
std::ifstream in(file);
if (in.good())
{
in >> pid_read;
if (pid_read && isPidRunning(pid_read))
throw Poco::Exception("Pid file exists and program running with pid = " + std::to_string(pid_read) + ", should not start daemon.");
}
}
std::cerr << "Old pid file exists (with pid = " << pid_read << "), removing." << std::endl;
poco_file.remove();
}
int fd = open(file.c_str(),
O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (-1 == fd)
{
if (EEXIST == errno)
throw Poco::Exception("Pid file exists, should not start daemon.");
throw Poco::CreateFileException("Cannot create pid file.");
}
SCOPE_EXIT({ close(fd); });
std::stringstream s;
s << getpid();
if (static_cast<ssize_t>(s.str().size()) != write(fd, s.str().c_str(), s.str().size()))
throw Poco::Exception("Cannot write to pid file.");
}
BaseDaemon::PID::~PID()
{
try
{
Poco::File(file).remove();
}
catch (...)
{
DB::tryLogCurrentException(__PRETTY_FUNCTION__);
}
}
void BaseDaemon::handleSignal(int signal_id)
{
if (signal_id == SIGINT ||

View File

@ -22,6 +22,7 @@
#include <common/getThreadId.h>
#include <daemon/GraphiteWriter.h>
#include <Common/Config/ConfigProcessor.h>
#include <Common/StatusFile.h>
#include <loggers/Loggers.h>
@ -163,16 +164,7 @@ protected:
std::unique_ptr<Poco::TaskManager> task_manager;
/// RAII wrapper for pid file.
struct PID
{
std::string file;
PID(const std::string & file_);
~PID();
};
std::optional<PID> pid;
std::optional<DB::StatusFile> pid;
std::atomic_bool is_cancelled{false};

20
cmake/find/amqpcpp.cmake Normal file
View File

@ -0,0 +1,20 @@
SET(ENABLE_AMQPCPP 1)
if (NOT EXISTS "${ClickHouse_SOURCE_DIR}/contrib/AMQP-CPP/CMakeLists.txt")
message (WARNING "submodule contrib/AMQP-CPP is missing. to fix try run: \n git submodule update --init --recursive")
set (ENABLE_AMQPCPP 0)
endif ()
if (ENABLE_AMQPCPP)
set (USE_AMQPCPP 1)
set (AMQPCPP_LIBRARY AMQP-CPP)
set (AMQPCPP_INCLUDE_DIR "${ClickHouse_SOURCE_DIR}/contrib/AMQP-CPP/include")
list (APPEND AMQPCPP_INCLUDE_DIR
"${ClickHouse_SOURCE_DIR}/contrib/AMQP-CPP/include"
"${ClickHouse_SOURCE_DIR}/contrib/AMQP-CPP")
endif()
message (STATUS "Using AMQP-CPP=${USE_AMQPCPP}: ${AMQPCPP_INCLUDE_DIR} : ${AMQPCPP_LIBRARY}")

1
contrib/AMQP-CPP vendored Submodule

@ -0,0 +1 @@
Subproject commit 1c08399ab0ab9e4042ef8e2bbe9e208e5dcbc13b

View File

@ -301,6 +301,10 @@ if (USE_FASTOPS)
add_subdirectory (fastops-cmake)
endif()
if (USE_AMQPCPP)
add_subdirectory (amqpcpp-cmake)
endif()
if (USE_CASSANDRA)
add_subdirectory (libuv)
add_subdirectory (cassandra)

View File

@ -0,0 +1,44 @@
set (LIBRARY_DIR ${ClickHouse_SOURCE_DIR}/contrib/AMQP-CPP)
set (SRCS
${LIBRARY_DIR}/src/array.cpp
${LIBRARY_DIR}/src/channel.cpp
${LIBRARY_DIR}/src/channelimpl.cpp
${LIBRARY_DIR}/src/connectionimpl.cpp
${LIBRARY_DIR}/src/deferredcancel.cpp
${LIBRARY_DIR}/src/deferredconfirm.cpp
${LIBRARY_DIR}/src/deferredconsumer.cpp
${LIBRARY_DIR}/src/deferredextreceiver.cpp
${LIBRARY_DIR}/src/deferredget.cpp
${LIBRARY_DIR}/src/deferredpublisher.cpp
${LIBRARY_DIR}/src/deferredreceiver.cpp
${LIBRARY_DIR}/src/field.cpp
${LIBRARY_DIR}/src/flags.cpp
${LIBRARY_DIR}/src/linux_tcp/openssl.cpp
${LIBRARY_DIR}/src/linux_tcp/tcpconnection.cpp
${LIBRARY_DIR}/src/receivedframe.cpp
${LIBRARY_DIR}/src/table.cpp
${LIBRARY_DIR}/src/watchable.cpp
)
add_library(amqp-cpp ${SRCS})
target_compile_options (amqp-cpp
PUBLIC
-Wno-old-style-cast
-Wno-inconsistent-missing-destructor-override
-Wno-deprecated
-Wno-unused-parameter
-Wno-shadow
-Wno-tautological-type-limit-compare
-Wno-extra-semi
# NOTE: disable all warnings at last because the warning:
# "conversion function converting 'XXX' to itself will never be used"
# doesn't have it's own diagnostic flag yet.
-w
)
target_include_directories (amqp-cpp PUBLIC ${LIBRARY_DIR}/include)
target_link_libraries (amqp-cpp PUBLIC ssl)

2
contrib/avro vendored

@ -1 +1 @@
Subproject commit 6cfcf6c24293af100d523b89b61d1ab216fa4735
Subproject commit 92caca2d42fc9a97e34e95f963593539d32ed331

View File

@ -22,9 +22,14 @@ if (ENABLE_JEMALLOC)
#
# By enabling percpu_arena number of arenas limited to number of CPUs and hence
# this problem should go away.
set (JEMALLOC_CONFIG_MALLOC_CONF "percpu_arena:percpu,oversize_threshold:0")
#
# muzzy_decay_ms -- use MADV_FREE when available on newer Linuxes, to
# avoid spurious latencies and additional work associated with
# MADV_DONTNEED. See
# https://github.com/ClickHouse/ClickHouse/issues/11121 for motivation.
set (JEMALLOC_CONFIG_MALLOC_CONF "percpu_arena:percpu,oversize_threshold:0,muzzy_decay_ms:10000")
else()
set (JEMALLOC_CONFIG_MALLOC_CONF "oversize_threshold:0")
set (JEMALLOC_CONFIG_MALLOC_CONF "oversize_threshold:0,muzzy_decay_ms:10000")
endif()
# CACHE variable is empty, to allow changing defaults without necessity
# to purge cache

2
contrib/poco vendored

@ -1 +1 @@
Subproject commit be2ab90ba5dccd46919a116e3fe4fa77bb85063b
Subproject commit 74c93443342f6028fa6402057684733b316aa737

2
contrib/replxx vendored

@ -1 +1 @@
Subproject commit 2d37daaad24be71e76514a36b0a47120be2f9086
Subproject commit 94b1f568d16183214d26c7c0e9ce69a4ce407f65

2
docker/bare/Dockerfile Normal file
View File

@ -0,0 +1,2 @@
FROM scratch
ADD root /

37
docker/bare/README.md Normal file
View File

@ -0,0 +1,37 @@
## The bare minimum ClickHouse Docker image.
It is intented as a showcase to check the amount of implicit dependencies of ClickHouse from the OS in addition to the OS kernel.
Example usage:
```
./prepare
docker build --tag clickhouse-bare .
```
Run clickhouse-local:
```
docker run -it --rm --network host clickhouse-bare /clickhouse local --query "SELECT 1"
```
Run clickhouse-client in interactive mode:
```
docker run -it --rm --network host clickhouse-bare /clickhouse client
```
Run clickhouse-server:
```
docker run -it --rm --network host clickhouse-bare /clickhouse server
```
It can be also run in chroot instead of Docker (first edit the `prepare` script to enable `proc`):
```
sudo chroot . /clickhouse server
```
## What does it miss?
- creation of `clickhouse` user to run the server;
- VOLUME for server;
- most of the details, see other docker images for comparison.

24
docker/bare/prepare Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
set -e
SRC_DIR=../..
BUILD_DIR=${SRC_DIR}/build
# BTW, .so files are acceptable from any Linux distribution for the last 12 years (at least).
# See https://presentations.clickhouse.tech/cpp_russia_2020/ for the details.
mkdir root
pushd root
mkdir lib lib64 etc tmp root
cp ${BUILD_DIR}/programs/clickhouse .
cp ${SRC_DIR}/programs/server/{config,users}.xml .
cp /lib/x86_64-linux-gnu/{libc.so.6,libdl.so.2,libm.so.6,libpthread.so.0,librt.so.1,libnss_dns.so.2,libresolv.so.2} lib
cp /lib64/ld-linux-x86-64.so.2 lib64
cp /etc/resolv.conf ./etc
strip clickhouse
# This is needed for chroot but not needed for Docker:
# mkdir proc
# sudo mount --bind /proc proc

View File

@ -6,6 +6,7 @@ ARG version=20.6.1.*
RUN apt-get update \
&& apt-get install --yes --no-install-recommends \
apt-transport-https \
ca-certificates \
dirmngr \
gnupg \
&& mkdir -p /etc/apt/sources.list.d \

View File

@ -1,19 +1,83 @@
{
"docker/packager/deb": "yandex/clickhouse-deb-builder",
"docker/packager/binary": "yandex/clickhouse-binary-builder",
"docker/test/coverage": "yandex/clickhouse-coverage",
"docker/test/compatibility/centos": "yandex/clickhouse-test-old-centos",
"docker/test/compatibility/ubuntu": "yandex/clickhouse-test-old-ubuntu",
"docker/test/integration/base": "yandex/clickhouse-integration-test",
"docker/test/performance-comparison": "yandex/clickhouse-performance-comparison",
"docker/test/stateful": "yandex/clickhouse-stateful-test",
"docker/test/stateful_with_coverage": "yandex/clickhouse-stateful-test-with-coverage",
"docker/test/stateless": "yandex/clickhouse-stateless-test",
"docker/test/stateless_pytest": "yandex/clickhouse-stateless-pytest",
"docker/test/stateless_with_coverage": "yandex/clickhouse-stateless-test-with-coverage",
"docker/test/unit": "yandex/clickhouse-unit-test",
"docker/test/stress": "yandex/clickhouse-stress-test",
"docker/test/split_build_smoke_test": "yandex/clickhouse-split-build-smoke-test",
"docker/test/codebrowser": "yandex/clickhouse-codebrowser",
"docker/test/integration/runner": "yandex/clickhouse-integration-tests-runner"
"docker/packager/deb": {
"name": "yandex/clickhouse-deb-builder",
"dependent": [
"docker/test/stateless",
"docker/test/stateless_with_coverage",
"docker/test/stateless_pytest",
"docker/test/coverage"
]
},
"docker/packager/binary": {
"name": "yandex/clickhouse-binary-builder",
"dependent": [
"docker/test/split_build_smoke_test",
"docker/test/pvs"
]
},
"docker/test/coverage": {
"name": "yandex/clickhouse-coverage",
"dependent": []
},
"docker/test/compatibility/centos": {
"name": "yandex/clickhouse-test-old-centos",
"dependent": []
},
"docker/test/compatibility/ubuntu": {
"name": "yandex/clickhouse-test-old-ubuntu",
"dependent": []
},
"docker/test/integration/base": {
"name": "yandex/clickhouse-integration-test",
"dependent": []
},
"docker/test/performance-comparison": {
"name": "yandex/clickhouse-performance-comparison",
"dependent": []
},
"docker/test/stateful": {
"name": "yandex/clickhouse-stateful-test",
"dependent": [
"docker/test/stress"
]
},
"docker/test/stateful_with_coverage": {
"name": "yandex/clickhouse-stateful-test-with-coverage",
"dependent": []
},
"docker/test/stateless": {
"name": "yandex/clickhouse-stateless-test",
"dependent": [
"docker/test/stateful",
"docker/test/stateful_with_coverage"
]
},
"docker/test/stateless_pytest": {
"name": "yandex/clickhouse-stateless-pytest",
"dependent": []
},
"docker/test/stateless_with_coverage": {
"name": "yandex/clickhouse-stateless-test-with-coverage",
"dependent": []
},
"docker/test/unit": {
"name": "yandex/clickhouse-unit-test",
"dependent": []
},
"docker/test/stress": {
"name": "yandex/clickhouse-stress-test",
"dependent": []
},
"docker/test/split_build_smoke_test": {
"name": "yandex/clickhouse-split-build-smoke-test",
"dependent": []
},
"docker/test/codebrowser": {
"name": "yandex/clickhouse-codebrowser",
"dependent": []
},
"docker/test/integration/runner": {
"name": "yandex/clickhouse-integration-tests-runner",
"dependent": []
}
}

View File

@ -5,7 +5,7 @@ RUN apt-get --allow-unauthenticated update -y && apt-get install --yes wget gnup
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
RUN echo "deb [trusted=yes] http://apt.llvm.org/eoan/ llvm-toolchain-eoan-10 main" >> /etc/apt/sources.list
# initial packages
RUN apt-get --allow-unauthenticated update -y \
&& env DEBIAN_FRONTEND=noninteractive \
apt-get --allow-unauthenticated install --yes --no-install-recommends \

View File

@ -31,7 +31,7 @@ def pull_image(image_name):
def build_image(image_name, filepath):
subprocess.check_call("docker build --network=host -t {} -f {} .".format(image_name, filepath), shell=True)
def run_docker_image_with_env(image_name, output, env_variables, ch_root, ccache_dir):
def run_docker_image_with_env(image_name, output, env_variables, ch_root, ccache_dir, docker_image_version):
env_part = " -e ".join(env_variables)
if env_part:
env_part = " -e " + env_part
@ -46,7 +46,7 @@ def run_docker_image_with_env(image_name, output, env_variables, ch_root, ccache
ch_root=ch_root,
ccache_dir=ccache_dir,
env=env_part,
img_name=image_name,
img_name=image_name + ":" + docker_image_version,
interactive=interactive
)
@ -189,6 +189,7 @@ if __name__ == "__main__":
parser.add_argument("--alien-pkgs", nargs='+', default=[])
parser.add_argument("--with-coverage", action="store_true")
parser.add_argument("--with-binaries", choices=("programs", "tests", ""), default="")
parser.add_argument("--docker-image-version", default="latest")
args = parser.parse_args()
if not os.path.isabs(args.output_dir):
@ -212,12 +213,14 @@ if __name__ == "__main__":
logging.info("Should place {} to output".format(args.with_binaries))
dockerfile = os.path.join(ch_root, "docker/packager", image_type, "Dockerfile")
image_with_version = image_name + ":" + args.docker_image_version
if image_type != "freebsd" and not check_image_exists_locally(image_name) or args.force_build_image:
if not pull_image(image_name) or args.force_build_image:
build_image(image_name, dockerfile)
if not pull_image(image_with_version) or args.force_build_image:
build_image(image_with_version, dockerfile)
env_prepared = parse_env_variables(
args.build_type, args.compiler, args.sanitizer, args.package_type, image_type,
args.cache, args.distcc_hosts, args.unbundled, args.split_binary, args.clang_tidy,
args.version, args.author, args.official, args.alien_pkgs, args.with_coverage, args.with_binaries)
run_docker_image_with_env(image_name, args.output_dir, env_prepared, ch_root, args.ccache_dir)
run_docker_image_with_env(image_name, args.output_dir, env_prepared, ch_root, args.ccache_dir, args.docker_image_version)
logging.info("Output placed into {}".format(args.output_dir))

View File

@ -7,6 +7,7 @@ ARG gosu_ver=1.10
RUN apt-get update \
&& apt-get install --yes --no-install-recommends \
apt-transport-https \
ca-certificates \
dirmngr \
gnupg \
&& mkdir -p /etc/apt/sources.list.d \
@ -19,7 +20,6 @@ RUN apt-get update \
clickhouse-client=$version \
clickhouse-server=$version \
locales \
ca-certificates \
wget \
&& rm -rf \
/var/lib/apt/lists/* \

View File

@ -0,0 +1,12 @@
version: '2.3'
services:
rabbitmq1:
image: rabbitmq:3-management
hostname: rabbitmq1
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: "root"
RABBITMQ_DEFAULT_PASS: "clickhouse"

View File

@ -12,6 +12,8 @@ RUN apt-get update \
g++ \
gdb \
git \
gnuplot \
imagemagick \
libc6-dbg \
moreutils \
ncdu \

View File

@ -36,18 +36,14 @@ function configure
while killall clickhouse-server; do echo . ; sleep 1 ; done
echo all killed
# Remove logs etc, because they will be updated, and sharing them between
# servers with hardlink might cause unpredictable behavior.
rm db0/data/system/* -rf ||:
rm db0/metadata/system/* -rf ||:
# Make copies of the original db for both servers. Use hardlinks instead
# of copying. Be careful to remove preprocessed configs and system tables,or
# it can lead to weird effects.
# of copying to save space. Before that, remove preprocessed configs and
# system tables, because sharing them between servers with hardlinks may
# lead to weird effects.
rm -r left/db ||:
rm -r right/db ||:
rm -r db0/preprocessed_configs ||:
rm -r db/{data,metadata}/system ||:
rm -r db0/{data,metadata}/system ||:
cp -al db0/ left/db/
cp -al db0/ right/db/
}
@ -143,22 +139,29 @@ function run_tests
touch "$x"
done
# Randomize test order.
test_files=$(for f in $test_files; do echo "$f"; done | sort -R)
# Run the tests.
test_name="<none>"
for test in $test_files
do
# Check that both servers are alive, to fail faster if they die.
# Check that both servers are alive, and restart them if they die.
clickhouse-client --port 9001 --query "select 1 format Null" \
|| { echo $test_name >> left-server-died.log ; restart ; continue ; }
|| { echo $test_name >> left-server-died.log ; restart ; }
clickhouse-client --port 9002 --query "select 1 format Null" \
|| { echo $test_name >> right-server-died.log ; restart ; continue ; }
|| { echo $test_name >> right-server-died.log ; restart ; }
test_name=$(basename "$test" ".xml")
echo test "$test_name"
TIMEFORMAT=$(printf "$test_name\t%%3R\t%%3U\t%%3S\n")
# the grep is to filter out set -x output and keep only time output
{ time "$script_dir/perf.py" --host localhost localhost --port 9001 9002 -- "$test" > "$test_name-raw.tsv" 2> "$test_name-err.log" ; } 2>&1 >/dev/null | grep -v ^+ >> "wall-clock-times.tsv" || continue
{ \
time "$script_dir/perf.py" --host localhost localhost --port 9001 9002 \
-- "$test" > "$test_name-raw.tsv" 2> "$test_name-err.log" ; \
} 2>&1 >/dev/null | grep -v ^+ >> "wall-clock-times.tsv" \
|| echo "Test $test_name failed with error code $?" >> "$test_name-err.log"
done
unset TIMEFORMAT
@ -217,10 +220,13 @@ function get_profiles
# Collect the profiles
clickhouse-client --port 9001 --query "set query_profiler_cpu_time_period_ns = 0"
clickhouse-client --port 9001 --query "set query_profiler_real_time_period_ns = 0"
clickhouse-client --port 9001 --query "set query_profiler_cpu_time_period_ns = 0"
clickhouse-client --port 9001 --query "set query_profiler_real_time_period_ns = 0"
clickhouse-client --port 9001 --query "system flush logs"
clickhouse-client --port 9002 --query "system flush logs"
clickhouse-client --port 9001 --query "system flush logs" &
clickhouse-client --port 9002 --query "set query_profiler_cpu_time_period_ns = 0"
clickhouse-client --port 9002 --query "set query_profiler_real_time_period_ns = 0"
clickhouse-client --port 9002 --query "system flush logs" &
wait
clickhouse-client --port 9001 --query "select * from system.query_log where type = 2 format TSVWithNamesAndTypes" > left-query-log.tsv ||: &
clickhouse-client --port 9001 --query "select * from system.query_thread_log format TSVWithNamesAndTypes" > left-query-thread-log.tsv ||: &
@ -272,10 +278,11 @@ for test_file in $(find . -maxdepth 1 -name "*-raw.tsv" -print)
do
test_name=$(basename "$test_file" "-raw.tsv")
sed -n "s/^query\t/$test_name\t/p" < "$test_file" >> "analyze/query-runs.tsv"
sed -n "s/^client-time/$test_name/p" < "$test_file" >> "analyze/client-times.tsv"
sed -n "s/^report-threshold/$test_name/p" < "$test_file" >> "analyze/report-thresholds.tsv"
sed -n "s/^skipped/$test_name/p" < "$test_file" >> "analyze/skipped-tests.tsv"
sed -n "s/^display-name/$test_name/p" < "$test_file" >> "analyze/query-display-names.tsv"
sed -n "s/^client-time\t/$test_name\t/p" < "$test_file" >> "analyze/client-times.tsv"
sed -n "s/^report-threshold\t/$test_name\t/p" < "$test_file" >> "analyze/report-thresholds.tsv"
sed -n "s/^skipped\t/$test_name\t/p" < "$test_file" >> "analyze/skipped-tests.tsv"
sed -n "s/^display-name\t/$test_name\t/p" < "$test_file" >> "analyze/query-display-names.tsv"
sed -n "s/^partial\t/$test_name\t/p" < "$test_file" >> "analyze/partial-queries.tsv"
done
unset IFS
@ -284,6 +291,18 @@ clickhouse-local --query "
create view query_runs as select * from file('analyze/query-runs.tsv', TSV,
'test text, query_index int, query_id text, version UInt8, time float');
create view partial_queries as select test, query_index
from file('analyze/partial-queries.tsv', TSV,
'test text, query_index int, servers Array(int)');
create table partial_query_times engine File(TSVWithNamesAndTypes,
'analyze/partial-query-times.tsv')
as select test, query_index, stddevPop(time) time_stddev, median(time) time_median
from query_runs
where (test, query_index) in partial_queries
group by test, query_index
;
create view left_query_log as select *
from file('left-query-log.tsv', TSVWithNamesAndTypes,
'$(cat "left-query-log.tsv.columns")');
@ -292,25 +311,54 @@ create view right_query_log as select *
from file('right-query-log.tsv', TSVWithNamesAndTypes,
'$(cat "right-query-log.tsv.columns")');
create table query_metrics engine File(TSV, -- do not add header -- will parse with grep
'analyze/query-run-metrics.tsv')
as select
test, query_index, 0 run, version,
[
-- server-reported time
query_duration_ms / toFloat64(1000)
, toFloat64(memory_usage)
-- client-reported time
, query_runs.time
] metrics
from (
select query_duration_ms, memory_usage, query_id, 0 version from left_query_log
create view query_logs as
select *, 0 version from left_query_log
union all
select query_duration_ms, memory_usage, query_id, 1 version from right_query_log
) query_logs
select *, 1 version from right_query_log
;
create table query_run_metrics_full engine File(TSV, 'analyze/query-run-metrics-full.tsv')
as
with (
-- sumMapState with the list of all keys with '-0.' values. Negative zero is because
-- sumMap removes keys with positive zeros.
with (select groupUniqArrayArray(ProfileEvents.Names) from query_logs) as all_names
select arrayReduce('sumMapState', [(all_names, arrayMap(x->-0., all_names))])
) as all_metrics
select test, query_index, version, query_id,
(finalizeAggregation(
arrayReduce('sumMapMergeState',
[
all_metrics,
arrayReduce('sumMapState',
[(ProfileEvents.Names,
arrayMap(x->toFloat64(x), ProfileEvents.Values))]
),
arrayReduce('sumMapState', [(
['client_time', 'server_time'],
arrayMap(x->if(x != 0., x, -0.), [
toFloat64(query_runs.time),
toFloat64(query_duration_ms / 1000.)]))])
]
)) as metrics_tuple).1 metric_names,
metrics_tuple.2 metric_values
from query_logs
right join query_runs
using (query_id, version)
order by test, query_index
on query_logs.query_id = query_runs.query_id
and query_logs.version = query_runs.version
where (test, query_index) not in partial_queries
;
create table query_run_metrics engine File(
TSV, -- do not add header -- will parse with grep
'analyze/query-run-metrics.tsv')
as select test, query_index, 0 run, version, metric_values
from query_run_metrics_full
order by test, query_index, run, version
;
create table query_run_metric_names engine File(TSV, 'analyze/query-run-metric-names.tsv')
as select metric_names from query_run_metrics_full limit 1
;
"
@ -320,7 +368,7 @@ create table query_metrics engine File(TSV, -- do not add header -- will parse w
# query. We also don't have lateral joins. So I just put all runs of each
# query into a separate file, and then compute randomization distribution
# for each file. I do this in parallel using GNU parallel.
query_index=1
( set +x # do not bloat the log
IFS=$'\n'
for prefix in $(cut -f1,2 "analyze/query-run-metrics.tsv" | sort | uniq)
do
@ -337,6 +385,7 @@ do
done
wait
unset IFS
)
parallel --joblog analyze/parallel-log.txt --null < analyze/commands.txt 2>> analyze/errors.log
}
@ -360,20 +409,42 @@ create view query_display_names as select * from
'test text, query_index int, query_display_name text')
;
create table query_metric_stats engine File(TSVWithNamesAndTypes,
'report/query-metric-stats.tsv') as
create table partial_queries_report engine File(TSV, 'report/partial-queries-report.tsv')
as select floor(time_median, 3) m, floor(time_stddev / time_median, 3) v,
test, query_index, query_display_name
from file('analyze/partial-query-times.tsv', TSVWithNamesAndTypes,
'test text, query_index int, time_stddev float, time_median float') t
join query_display_names using (test, query_index)
order by test, query_index
;
-- WITH, ARRAY JOIN and CROSS JOIN do not like each other:
-- https://github.com/ClickHouse/ClickHouse/issues/11868
-- https://github.com/ClickHouse/ClickHouse/issues/11757
-- Because of this, we make a view with arrays first, and then apply all the
-- array joins.
create view query_metric_stat_arrays as
with (select * from file('analyze/query-run-metric-names.tsv',
TSV, 'n Array(String)')) as metric_name
select metric_name, left, right, diff, stat_threshold, test, query_index,
query_display_name
from file ('analyze/query-reports.tsv', TSV, 'left Array(float),
right Array(float), diff Array(float), stat_threshold Array(float),
test text, query_index int') reports
left array join ['server_time', 'memory', 'client_time'] as metric_name,
left, right, diff, stat_threshold
left join query_display_names
on reports.test = query_display_names.test
and reports.query_index = query_display_names.query_index
;
create table query_metric_stats engine File(TSVWithNamesAndTypes,
'report/query-metric-stats.tsv')
as
select metric_name, left, right, diff, stat_threshold, test, query_index,
query_display_name
from query_metric_stat_arrays
left array join metric_name, left, right, diff, stat_threshold
;
-- Main statistics for queries -- query time as reported in query log.
create table queries engine File(TSVWithNamesAndTypes, 'report/queries.tsv')
as select
@ -723,9 +794,74 @@ done
wait
unset IFS
# Remember that grep sets error code when nothing is found, hence the bayan
# operator.
grep -H -m2 -i '\(Exception\|Error\):[^:]' ./*-err.log | sed 's/:/\t/' >> run-errors.tsv ||:
# Prefer to grep for clickhouse_driver exception messages, but if there are none,
# just show a couple of lines from the log.
for log in *-err.log
do
test=$(basename "$log" "-err.log")
{
grep -H -m2 -i '\(Exception\|Error\):[^:]' "$log" \
|| head -2 "$log"
} | sed "s/^/$test\t/" >> run-errors.tsv ||:
done
}
function report_metrics
{
rm -rf metrics ||:
mkdir metrics
clickhouse-local --stacktrace --verbose --query "
create view right_async_metric_log as
select * from file('right-async-metric-log.tsv', TSVWithNamesAndTypes,
'event_date Date, event_time DateTime, name String, value Float64')
;
-- Use the right log as time reference because it may have higher precision.
create table metrics engine File(TSV, 'metrics/metrics.tsv') as
with (select min(event_time) from right_async_metric_log) as min_time
select name metric, r.event_time - min_time event_time, l.value as left, r.value as right
from right_async_metric_log r
asof join file('left-async-metric-log.tsv', TSVWithNamesAndTypes,
'event_date Date, event_time DateTime, name String, value Float64') l
on l.name = r.name and r.event_time <= l.event_time
order by metric, event_time
;
-- Show metrics that have changed
create table changes engine File(TSV, 'metrics/changes.tsv') as
select metric, median(left) as left, median(right) as right,
floor((right - left) / left, 3) diff,
floor(if(left > right, left / right, right / left), 3) times_diff
from metrics
group by metric
having abs(diff) > 0.05 and isFinite(diff)
order by diff desc
;
"
IFS=$'\n'
for prefix in $(cut -f1 "metrics/metrics.tsv" | sort | uniq)
do
file="metrics/$prefix.tsv"
grep "^$prefix " "metrics/metrics.tsv" | cut -f2- > "$file"
gnuplot -e "
set datafile separator '\t';
set terminal png size 960,540;
set xtics time format '%tH:%tM';
set title '$prefix' noenhanced offset 0,-3;
set key left top;
plot
'$file' using 1:2 with lines title 'Left'
, '$file' using 1:3 with lines title 'Right'
;
" \
| convert - -filter point -resize "200%" "metrics/$prefix.png" &
done
wait
unset IFS
}
# Check that local and client are in PATH
@ -749,12 +885,20 @@ case "$stage" in
time run_benchmark 2> >(tee -a run-errors.tsv 1>&2) ||:
;&
"get_profiles")
# Getting profiles inexplicably hangs sometimes, so try to save some logs if
# this happens again. Give the servers some time to collect all info, then
# trace and kill. Start in a subshell, so that both function don't interfere
# with each other's jobs through `wait`. Also make the subshell have its own
# process group, so that we can then kill it with all its child processes.
# Somehow it doesn't kill the children by itself when dying.
# Check for huge pages.
cat /sys/kernel/mm/transparent_hugepage/enabled > thp-enabled.txt ||:
cat /proc/meminfo > meminfo.txt ||:
for pid in $(pgrep -f clickhouse-server)
do
cat "/proc/$pid/smaps" > "$pid-smaps.txt" ||:
done
# We had a bug where getting profiles froze sometimes, so try to save some
# logs if this happens again. Give the servers some time to collect all info,
# then trace and kill. Start in a subshell, so that both function don't
# interfere with each other's jobs through `wait`. Also make the subshell
# have its own process group, so that we can then kill it with all its child
# processes. Somehow it doesn't kill the children by itself when dying.
set -m
( get_profiles_watchdog ) &
watchdog_pid=$!
@ -781,7 +925,11 @@ case "$stage" in
;&
"report")
time report ||:
;&
"report_metrics")
time report_metrics ||:
;&
"report_html")
time "$script_dir/report.py" --report=all-queries > all-queries.html 2> >(tee -a report/errors.log 1>&2) ||:
time "$script_dir/report.py" > report.html
;&

View File

@ -20,4 +20,6 @@
</metric_log>
<uncompressed_cache_size>1000000000</uncompressed_cache_size>
<asynchronous_metrics_update_period_s>10</asynchronous_metrics_update_period_s>
</yandex>

View File

@ -24,14 +24,32 @@ dataset_paths["values"]="https://clickhouse-datasets.s3.yandex.net/values_with_e
function download
{
# might have the same version on left and right
if ! [ "$left_sha" = "$right_sha" ]
# Historically there were various path for the performance test package.
# Test all of them.
for path in "https://clickhouse-builds.s3.yandex.net/$left_pr/$left_sha/"{,clickhouse_build_check/}"performance/performance.tgz"
do
if curl --fail --head "$path"
then
wget -nv -nd -c "https://clickhouse-builds.s3.yandex.net/$left_pr/$left_sha/performance/performance.tgz" -O- | tar -C left --strip-components=1 -zxv &
wget -nv -nd -c "https://clickhouse-builds.s3.yandex.net/$right_pr/$right_sha/performance/performance.tgz" -O- | tar -C right --strip-components=1 -zxv &
left_path="$path"
fi
done
for path in "https://clickhouse-builds.s3.yandex.net/$right_pr/$right_sha/"{,clickhouse_build_check/}"performance/performance.tgz"
do
if curl --fail --head "$path"
then
right_path="$path"
fi
done
# might have the same version on left and right
if ! [ "$left_path" = "$right_path" ]
then
wget -nv -nd -c "$left_path" -O- | tar -C left --strip-components=1 -zxv &
wget -nv -nd -c "$right_path" -O- | tar -C right --strip-components=1 -zxv &
else
mkdir right ||:
wget -nv -nd -c "https://clickhouse-builds.s3.yandex.net/$left_pr/$left_sha/performance/performance.tgz" -O- | tar -C left --strip-components=1 -zxv && cp -a left/* right &
wget -nv -nd -c "$left_path" -O- | tar -C left --strip-components=1 -zxv && cp -a left/* right &
fi
for dataset_name in $datasets

View File

@ -50,10 +50,18 @@ function find_reference_sha
# FIXME sometimes we have testing tags on commits without published builds --
# normally these are documentation commits. Loop to skip them.
if curl --fail --head "https://clickhouse-builds.s3.yandex.net/0/$REF_SHA/performance/performance.tgz"
# Historically there were various path for the performance test package.
# Test all of them.
unset found
for path in "https://clickhouse-builds.s3.yandex.net/0/$REF_SHA/"{,clickhouse_build_check/}"performance/performance.tgz"
do
if curl --fail --head "$path"
then
found="$path"
break
fi
done
if [ -n "$found" ] ; then break; fi
start_ref="$REF_SHA~"
done
@ -133,6 +141,6 @@ dmesg -T > dmesg.log
7z a '-x!*/tmp' /output/output.7z ./*.{log,tsv,html,txt,rep,svg,columns} \
{right,left}/{performance,scripts} {{right,left}/db,db0}/preprocessed_configs \
report analyze benchmark
report analyze benchmark metrics
cp compare.log /output

View File

@ -7,6 +7,7 @@ import clickhouse_driver
import xml.etree.ElementTree as et
import argparse
import pprint
import re
import string
import time
import traceback
@ -102,10 +103,11 @@ for s in servers:
# connection loses the changes in settings.
drop_query_templates = [q.text for q in root.findall('drop_query')]
drop_queries = substitute_parameters(drop_query_templates)
for c in connections:
for conn_index, c in enumerate(connections):
for q in drop_queries:
try:
c.execute(q)
print(f'drop\t{conn_index}\t{c.last_query.elapsed}\t{tsv_escape(q)}')
except:
pass
@ -117,10 +119,12 @@ for c in connections:
# configurable). So the end result is uncertain, but hopefully we'll be able to
# run at least some queries.
settings = root.findall('settings/*')
for c in connections:
for conn_index, c in enumerate(connections):
for s in settings:
try:
c.execute("set {} = '{}'".format(s.tag, s.text))
q = f"set {s.tag} = '{s.text}'"
c.execute(q)
print(f'set\t{conn_index}\t{c.last_query.elapsed}\t{tsv_escape(q)}')
except:
print(traceback.format_exc(), file=sys.stderr)
@ -139,16 +143,28 @@ for t in tables:
# Run create queries
create_query_templates = [q.text for q in root.findall('create_query')]
create_queries = substitute_parameters(create_query_templates)
for c in connections:
# Disallow temporary tables, because the clickhouse_driver reconnects on errors,
# and temporary tables are destroyed. We want to be able to continue after some
# errors.
for q in create_queries:
if re.search('create temporary table', q, flags=re.IGNORECASE):
print(f"Temporary tables are not allowed in performance tests: '{q}'",
file = sys.stderr)
sys.exit(1)
for conn_index, c in enumerate(connections):
for q in create_queries:
c.execute(q)
print(f'create\t{conn_index}\t{c.last_query.elapsed}\t{tsv_escape(q)}')
# Run fill queries
fill_query_templates = [q.text for q in root.findall('fill_query')]
fill_queries = substitute_parameters(fill_query_templates)
for c in connections:
for conn_index, c in enumerate(connections):
for q in fill_queries:
c.execute(q)
print(f'fill\t{conn_index}\t{c.last_query.elapsed}\t{tsv_escape(q)}')
# Run test queries
for query_index, q in enumerate(test_queries):
@ -165,31 +181,47 @@ for query_index, q in enumerate(test_queries):
# Prewarm: run once on both servers. Helps to bring the data into memory,
# precompile the queries, etc.
try:
# A query might not run on the old server if it uses a function added in the
# new one. We want to run them on the new server only, so that the PR author
# can ensure that the test works properly. Remember the errors we had on
# each server.
query_error_on_connection = [None] * len(connections);
for conn_index, c in enumerate(connections):
try:
prewarm_id = f'{query_prefix}.prewarm0'
res = c.execute(q, query_id = prewarm_id)
print(f'prewarm\t{query_index}\t{prewarm_id}\t{conn_index}\t{c.last_query.elapsed}')
except KeyboardInterrupt:
raise
except:
# If prewarm fails for some query -- skip it, and try to test the others.
# This might happen if the new test introduces some function that the
# old server doesn't support. Still, report it as an error.
# FIXME the driver reconnects on error and we lose settings, so this might
# lead to further errors or unexpected behavior.
print(traceback.format_exc(), file=sys.stderr)
# FIXME the driver reconnects on error and we lose settings, so this
# might lead to further errors or unexpected behavior.
query_error_on_connection[conn_index] = traceback.format_exc();
continue
# If prewarm fails for the query on both servers -- report the error, skip
# the query and continue testing the next query.
if query_error_on_connection.count(None) == 0:
print(query_error_on_connection[0], file = sys.stderr)
continue
# If prewarm fails on one of the servers, run the query on the rest of them.
# Useful for queries that use new functions added in the new server version.
if query_error_on_connection.count(None) < len(query_error_on_connection):
no_error = [i for i, e in enumerate(query_error_on_connection) if not e]
print(f'partial\t{query_index}\t{no_error}')
# Now, perform measured runs.
# Track the time spent by the client to process this query, so that we can notice
# out the queries that take long to process on the client side, e.g. by sending
# excessive data.
# Track the time spent by the client to process this query, so that we can
# notice the queries that take long to process on the client side, e.g. by
# sending excessive data.
start_seconds = time.perf_counter()
server_seconds = 0
for run in range(0, args.runs):
run_id = f'{query_prefix}.run{run}'
for conn_index, c in enumerate(connections):
if query_error_on_connection[conn_index]:
continue
res = c.execute(q, query_id = run_id)
print(f'query\t{query_index}\t{run_id}\t{conn_index}\t{c.last_query.elapsed}')
server_seconds += c.last_query.elapsed
@ -198,8 +230,8 @@ for query_index, q in enumerate(test_queries):
print(f'client-time\t{query_index}\t{client_seconds}\t{server_seconds}')
# Run drop queries
drop_query_templates = [q.text for q in root.findall('drop_query')]
drop_queries = substitute_parameters(drop_query_templates)
for c in connections:
for conn_index, c in enumerate(connections):
for q in drop_queries:
c.execute(q)
print(f'drop\t{conn_index}\t{c.last_query.elapsed}\t{tsv_escape(q)}')

View File

@ -7,6 +7,7 @@ import csv
import itertools
import json
import os
import os.path
import pprint
import sys
import traceback
@ -23,6 +24,7 @@ faster_queries = 0
slower_queries = 0
unstable_queries = 0
very_unstable_queries = 0
unstable_partial_queries = 0
# max seconds to run one query by itself, not counting preparation
allowed_single_run_time = 2
@ -191,9 +193,34 @@ if args.report == 'main':
slow_on_client_rows = tsvRows('report/slow-on-client.tsv')
error_tests += len(slow_on_client_rows)
printSimpleTable('Slow on client',
['Client time, s', 'Server time, s', 'Ratio', 'Test', 'Query'],
['Client time,&nbsp;s', 'Server time,&nbsp;s', 'Ratio', 'Test', 'Query'],
slow_on_client_rows)
def print_partial():
rows = tsvRows('report/partial-queries-report.tsv')
if not rows:
return
global unstable_partial_queries, slow_average_tests
print(tableStart('Partial queries'))
columns = ['Median time, s', 'Relative time variance', 'Test', '#', 'Query']
print(tableHeader(columns))
attrs = ['' for c in columns]
for row in rows:
if float(row[1]) > 0.10:
attrs[1] = f'style="background: {color_bad}"'
unstable_partial_queries += 1
else:
attrs[1] = ''
if float(row[0]) > allowed_single_run_time:
attrs[0] = f'style="background: {color_bad}"'
slow_average_tests += 1
else:
attrs[0] = ''
print(tableRow(row, attrs))
print(tableEnd())
print_partial()
def print_changes():
rows = tsvRows('report/changed-perf.tsv')
if not rows:
@ -203,8 +230,8 @@ if args.report == 'main':
print(tableStart('Changes in performance'))
columns = [
'Old, s', # 0
'New, s', # 1
'Old,&nbsp;s', # 0
'New,&nbsp;s', # 1
'Relative difference (new&nbsp;&minus;&nbsp;old) / old', # 2
'p&nbsp;<&nbsp;0.001 threshold', # 3
# Failed # 4
@ -245,8 +272,8 @@ if args.report == 'main':
unstable_queries += len(unstable_rows)
columns = [
'Old, s', #0
'New, s', #1
'Old,&nbsp;s', #0
'New,&nbsp;s', #1
'Relative difference (new&nbsp;-&nbsp;old)/old', #2
'p&nbsp;<&nbsp;0.001 threshold', #3
# Failed #4
@ -288,13 +315,13 @@ if args.report == 'main':
columns = [
'Test', #0
'Wall clock time, s', #1
'Total client time, s', #2
'Wall clock time,&nbsp;s', #1
'Total client time,&nbsp;s', #2
'Total queries', #3
'Ignored short queries', #4
'Longest query<br>(sum for all runs), s', #5
'Avg wall clock time<br>(sum for all runs), s', #6
'Shortest query<br>(sum for all runs), s', #7
'Longest query<br>(sum for all runs),&nbsp;s', #5
'Avg wall clock time<br>(sum for all runs),&nbsp;s', #6
'Shortest query<br>(sum for all runs),&nbsp;s', #7
]
print(tableStart('Test times'))
@ -324,6 +351,9 @@ if args.report == 'main':
print_test_times()
def print_benchmark_results():
if not os.path.isfile('benchmark/website-left.json'):
return
json_reports = [json.load(open(f'benchmark/website-{x}.json')) for x in ['left', 'right']]
stats = [next(iter(x.values()))["statistics"] for x in json_reports]
qps = [x["QPS"] for x in stats]
@ -384,6 +414,11 @@ if args.report == 'main':
*sys.exc_info()[:2])[-1])
pass
printSimpleTable('Metric changes',
['Metric', 'Old median value', 'New median value',
'Relative difference', 'Times difference'],
tsvRows('metrics/changes.tsv'))
print_report_errors()
print("""
@ -412,6 +447,11 @@ if args.report == 'main':
status = 'failure'
message_array.append(str(slower_queries) + ' slower')
if unstable_partial_queries:
unstable_queries += unstable_partial_queries
error_tests += unstable_partial_queries
status = 'failure'
if unstable_queries:
message_array.append(str(unstable_queries) + ' unstable')
@ -450,8 +490,8 @@ elif args.report == 'all-queries':
columns = [
# Changed #0
# Unstable #1
'Old, s', #2
'New, s', #3
'Old,&nbsp;s', #2
'New,&nbsp;s', #3
'Relative difference (new&nbsp;&minus;&nbsp;old) / old', #4
'Times speedup / slowdown', #5
'p&nbsp;<&nbsp;0.001 threshold', #6

View File

@ -25,7 +25,7 @@ ENV PKG_VERSION="pvs-studio-7.08.39365.50-amd64.deb"
RUN wget "https://files.viva64.com/$PKG_VERSION"
RUN sudo dpkg -i "$PKG_VERSION"
CMD cd /repo_folder && pvs-studio-analyzer credentials $LICENCE_NAME $LICENCE_KEY -o ./licence.lic \
CMD echo "Running PVS version $PKG_VERSION" && cd /repo_folder && pvs-studio-analyzer credentials $LICENCE_NAME $LICENCE_KEY -o ./licence.lic \
&& cmake . && ninja re2_st && \
pvs-studio-analyzer analyze -o pvs-studio.log -e contrib -j 4 -l ./licence.lic; \
plog-converter -a GA:1,2 -t fullhtml -o /test_output/pvs-studio-html-report pvs-studio.log; \

View File

@ -53,4 +53,4 @@ CMD dpkg -i package_folder/clickhouse-common-static_*.deb; \
&& clickhouse-client --query "RENAME TABLE datasets.hits_v1 TO test.hits" \
&& clickhouse-client --query "RENAME TABLE datasets.visits_v1 TO test.visits" \
&& clickhouse-client --query "SHOW TABLES FROM test" \
&& clickhouse-test --testname --shard --zookeeper --no-stateless $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt
&& clickhouse-test --testname --shard --zookeeper --no-stateless --use-skip-list $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt

View File

@ -105,7 +105,7 @@ LLVM_PROFILE_FILE='client_%h_%p_%m.profraw' clickhouse-client --query "SHOW TABL
LLVM_PROFILE_FILE='client_%h_%p_%m.profraw' clickhouse-client --query "RENAME TABLE datasets.hits_v1 TO test.hits"
LLVM_PROFILE_FILE='client_%h_%p_%m.profraw' clickhouse-client --query "RENAME TABLE datasets.visits_v1 TO test.visits"
LLVM_PROFILE_FILE='client_%h_%p_%m.profraw' clickhouse-client --query "SHOW TABLES FROM test"
LLVM_PROFILE_FILE='client_%h_%p_%m.profraw' clickhouse-test --testname --shard --zookeeper --no-stateless $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt
LLVM_PROFILE_FILE='client_%h_%p_%m.profraw' clickhouse-test --testname --shard --zookeeper --no-stateless --use-skip-list $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt
kill_clickhouse

View File

@ -83,4 +83,4 @@ CMD dpkg -i package_folder/clickhouse-common-static_*.deb; \
if [[ -n "$USE_DATABASE_ATOMIC" ]] && [[ "$USE_DATABASE_ATOMIC" -eq 1 ]]; then ln -s /usr/share/clickhouse-test/config/database_atomic_usersd.xml /etc/clickhouse-server/users.d/; fi; \
ln -sf /usr/share/clickhouse-test/config/client_config.xml /etc/clickhouse-client/config.xml; \
service zookeeper start; sleep 5; \
service clickhouse-server start && sleep 5 && clickhouse-test --testname --shard --zookeeper $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt
service clickhouse-server start && sleep 5 && clickhouse-test --testname --shard --zookeeper --use-skip-list $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt

View File

@ -76,7 +76,7 @@ start_clickhouse
sleep 10
LLVM_PROFILE_FILE='client_coverage.profraw' clickhouse-test --testname --shard --zookeeper $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt
LLVM_PROFILE_FILE='client_coverage.profraw' clickhouse-test --testname --shard --zookeeper --use-skip-list $ADDITIONAL_OPTIONS $SKIP_TESTS_OPTION 2>&1 | ts '%Y-%m-%d %H:%M:%S' | tee test_output/test_result.txt
kill_clickhouse

View File

@ -17,13 +17,13 @@ def run_perf_test(cmd, xmls_path, output_folder):
def run_func_test(cmd, output_prefix, num_processes, skip_tests_option):
output_paths = [os.path.join(output_prefix, "stress_test_run_{}.txt".format(i)) for i in range(num_processes)]
f = open(output_paths[0], 'w')
main_command = "{} {}".format(cmd, skip_tests_option)
main_command = "{} --use-skip-list {}".format(cmd, skip_tests_option)
logging.info("Run func tests main cmd '%s'", main_command)
pipes = [Popen(main_command, shell=True, stdout=f, stderr=f)]
for output_path in output_paths[1:]:
time.sleep(0.5)
f = open(output_path, 'w')
full_command = "{} --order=random {}".format(cmd, skip_tests_option)
full_command = "{} --use-skip-list --order=random {}".format(cmd, skip_tests_option)
logging.info("Run func tests '%s'", full_command)
p = Popen(full_command, shell=True, stdout=f, stderr=f)
pipes.append(p)

View File

@ -5,9 +5,9 @@ toc_title: Architecture Overview
# Overview of ClickHouse Architecture {#overview-of-clickhouse-architecture}
ClickHouse is a true column-oriented DBMS. Data is stored by columns and during the execution of arrays (vectors or chunks of columns). Whenever possible, operations are dispatched on arrays, rather than on individual values. It is called “vectorized query execution,” and it helps lower the cost of actual data processing.
ClickHouse is a true column-oriented DBMS. Data is stored by columns, and during the execution of arrays (vectors or chunks of columns). Whenever possible, operations are dispatched on arrays, rather than on individual values. It is called "vectorized query execution" and it helps lower the cost of actual data processing.
> This idea is nothing new. It dates back to the `APL` programming language and its descendants: `A +`, `J`, `K`, and `Q`. Array programming is used in scientific data processing. Neither is this idea something new in relational databases: for example, it is used in the `Vectorwise` system.
> This idea is nothing new. It dates back to the `APL` (A programming language, 1957) and its descendants: `A +` (APL dialect), `J` (1990), `K` (1993), and `Q` (programming language from Kx Systems, 2003). Array programming is used in scientific data processing. Neither is this idea something new in relational databases: for example, it is used in the `VectorWise` system (also known as Actian Vector Analytic Database by Actian Corporation).
There are two different approaches for speeding up query processing: vectorized query execution and runtime code generation. The latter removes all indirection and dynamic dispatch. Neither of these approaches is strictly better than the other. Runtime code generation can be better when it fuses many operations, thus fully utilizing CPU execution units and the pipeline. Vectorized query execution can be less practical because it involves temporary vectors that must be written to the cache and read back. If the temporary data does not fit in the L2 cache, this becomes an issue. But vectorized query execution more easily utilizes the SIMD capabilities of the CPU. A [research paper](http://15721.courses.cs.cmu.edu/spring2016/papers/p5-sompolski.pdf) written by our friends shows that it is better to combine both approaches. ClickHouse uses vectorized query execution and has limited initial support for runtime code generation.
@ -19,13 +19,13 @@ Various `IColumn` implementations (`ColumnUInt8`, `ColumnString`, and so on) are
## Field {#field}
Nevertheless, it is possible to work with individual values as well. To represent an individual value, the `Field` is used. `Field` is just a discriminated union of `UInt64`, `Int64`, `Float64`, `String` and `Array`. `IColumn` has the `operator[]` method to get the n-th value as a `Field` and the `insert` method to append a `Field` to the end of a column. These methods are not very efficient, because they require dealing with temporary `Field` objects representing an individual value. There are more efficient methods, such as `insertFrom`, `insertRangeFrom`, and so on.
Nevertheless, it is possible to work with individual values as well. To represent an individual value, the `Field` is used. `Field` is just a discriminated union of `UInt64`, `Int64`, `Float64`, `String` and `Array`. `IColumn` has the `operator []` method to get the n-th value as a `Field`, and the `insert` method to append a `Field` to the end of a column. These methods are not very efficient, because they require dealing with temporary `Field` objects representing an individual value. There are more efficient methods, such as `insertFrom`, `insertRangeFrom`, and so on.
`Field` doesnt have enough information about a specific data type for a table. For example, `UInt8`, `UInt16`, `UInt32`, and `UInt64` are all represented as `UInt64` in a `Field`.
`Field` doesn't have enough information about a specific data type for a table. For example, `UInt8`, `UInt16`, `UInt32`, and `UInt64` are all represented as `UInt64` in a `Field`.
## Leaky Abstractions {#leaky-abstractions}
`IColumn` has methods for common relational transformations of data, but they dont meet all needs. For example, `ColumnUInt64` doesnt have a method to calculate the sum of two columns, and `ColumnString` doesnt have a method to run a substring search. These countless routines are implemented outside of `IColumn`.
`IColumn` has methods for common relational transformations of data, but they dont meet all needs. For example, `ColumnUInt64` doesn't have a method to calculate the sum of two columns, and `ColumnString` doesn't have a method to run a substring search. These countless routines are implemented outside of `IColumn`.
Various functions on columns can be implemented in a generic, non-efficient way using `IColumn` methods to extract `Field` values, or in a specialized way using knowledge of inner memory layout of data in a specific `IColumn` implementation. It is implemented by casting functions to a specific `IColumn` type and deal with internal representation directly. For example, `ColumnUInt64` has the `getData` method that returns a reference to an internal array, then a separate routine reads or fills that array directly. We have “leaky abstractions” to allow efficient specializations of various routines.
@ -35,7 +35,7 @@ Various functions on columns can be implemented in a generic, non-efficient way
`IDataType` and `IColumn` are only loosely related to each other. Different data types can be represented in memory by the same `IColumn` implementations. For example, `DataTypeUInt32` and `DataTypeDateTime` are both represented by `ColumnUInt32` or `ColumnConstUInt32`. In addition, the same data type can be represented by different `IColumn` implementations. For example, `DataTypeUInt8` can be represented by `ColumnUInt8` or `ColumnConstUInt8`.
`IDataType` only stores metadata. For instance, `DataTypeUInt8` doesnt store anything at all (except vptr) and `DataTypeFixedString` stores just `N` (the size of fixed-size strings).
`IDataType` only stores metadata. For instance, `DataTypeUInt8` doesn't store anything at all (except virtual pointer `vptr`) and `DataTypeFixedString` stores just `N` (the size of fixed-size strings).
`IDataType` has helper methods for various data formats. Examples are methods to serialize a value with possible quoting, to serialize a value for JSON, and to serialize a value as part of the XML format. There is no direct correspondence to data formats. For example, the different data formats `Pretty` and `TabSeparated` can use the same `serializeTextEscaped` helper method from the `IDataType` interface.
@ -120,9 +120,9 @@ There are ordinary functions and aggregate functions. For aggregate functions, s
Ordinary functions dont change the number of rows they work as if they are processing each row independently. In fact, functions are not called for individual rows, but for `Block`s of data to implement vectorized query execution.
There are some miscellaneous functions, like [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), and [runningAccumulate](../sql-reference/functions/other-functions.md#function-runningaccumulate), that exploit block processing and violate the independence of rows.
There are some miscellaneous functions, like [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), and [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulatexploit block processing and violate the independence of rows.
ClickHouse has strong typing, so theres no implicit type conversion. If a function doesnt support a specific combination of types, it throws an exception. But functions can work (be overloaded) for many different combinations of types. For example, the `plus` function (to implement the `+` operator) works for any combination of numeric types: `UInt8` + `Float32`, `UInt16` + `Int8`, and so on. Also, some variadic functions can accept any number of arguments, such as the `concat` function.
ClickHouse has strong typing, so theres no implicit type conversion. If a function doesn't support a specific combination of types, it throws an exception. But functions can work (be overloaded) for many different combinations of types. For example, the `plus` function (to implement the `+` operator) works for any combination of numeric types: `UInt8` + `Float32`, `UInt16` + `Int8`, and so on. Also, some variadic functions can accept any number of arguments, such as the `concat` function.
Implementing a function may be slightly inconvenient because a function explicitly dispatches supported data types and supported `IColumns`. For example, the `plus` function has code generated by instantiation of a C++ template for each combination of numeric types, and constant or non-constant left and right arguments.
@ -169,13 +169,13 @@ There is no global query plan for distributed query execution. Each node has its
`MergeTree` is a family of storage engines that supports indexing by primary key. The primary key can be an arbitrary tuple of columns or expressions. Data in a `MergeTree` table is stored in “parts”. Each part stores data in the primary key order, so data is ordered lexicographically by the primary key tuple. All the table columns are stored in separate `column.bin` files in these parts. The files consist of compressed blocks. Each block is usually from 64 KB to 1 MB of uncompressed data, depending on the average value size. The blocks consist of column values placed contiguously one after the other. Column values are in the same order for each column (the primary key defines the order), so when you iterate by many columns, you get values for the corresponding rows.
The primary key itself is “sparse”. It doesnt address every single row, but only some ranges of data. A separate `primary.idx` file has the value of the primary key for each N-th row, where N is called `index_granularity` (usually, N = 8192). Also, for each column, we have `column.mrk` files with “marks,” which are offsets to each N-th row in the data file. Each mark is a pair: the offset in the file to the beginning of the compressed block, and the offset in the decompressed block to the beginning of data. Usually, compressed blocks are aligned by marks, and the offset in the decompressed block is zero. Data for `primary.idx` always resides in memory, and data for `column.mrk` files is cached.
The primary key itself is “sparse”. It doesn't address every single row, but only some ranges of data. A separate `primary.idx` file has the value of the primary key for each N-th row, where N is called `index_granularity` (usually, N = 8192). Also, for each column, we have `column.mrk` files with “marks,” which are offsets to each N-th row in the data file. Each mark is a pair: the offset in the file to the beginning of the compressed block, and the offset in the decompressed block to the beginning of data. Usually, compressed blocks are aligned by marks, and the offset in the decompressed block is zero. Data for `primary.idx` always resides in memory, and data for `column.mrk` files is cached.
When we are going to read something from a part in `MergeTree`, we look at `primary.idx` data and locate ranges that could contain requested data, then look at `column.mrk` data and calculate offsets for where to start reading those ranges. Because of sparseness, excess data may be read. ClickHouse is not suitable for a high load of simple point queries, because the entire range with `index_granularity` rows must be read for each key, and the entire compressed block must be decompressed for each column. We made the index sparse because we must be able to maintain trillions of rows per single server without noticeable memory consumption for the index. Also, because the primary key is sparse, it is not unique: it cannot check the existence of the key in the table at INSERT time. You could have many rows with the same key in a table.
When you `INSERT` a bunch of data into `MergeTree`, that bunch is sorted by primary key order and forms a new part. There are background threads that periodically select some parts and merge them into a single sorted part to keep the number of parts relatively low. Thats why it is called `MergeTree`. Of course, merging leads to “write amplification”. All parts are immutable: they are only created and deleted, but not modified. When SELECT is executed, it holds a snapshot of the table (a set of parts). After merging, we also keep old parts for some time to make a recovery after failure easier, so if we see that some merged part is probably broken, we can replace it with its source parts.
`MergeTree` is not an LSM tree because it doesnt contain “memtable” and “log”: inserted data is written directly to the filesystem. This makes it suitable only to INSERT data in batches, not by individual row and not very frequently about once per second is ok, but a thousand times a second is not. We did it this way for simplicitys sake, and because we are already inserting data in batches in our applications.
`MergeTree` is not an LSM tree because it doesn't contain “memtable” and “log”: inserted data is written directly to the filesystem. This makes it suitable only to INSERT data in batches, not by individual row and not very frequently about once per second is ok, but a thousand times a second is not. We did it this way for simplicitys sake, and because we are already inserting data in batches in our applications.
> MergeTree tables can only have one (primary) index: there arent any secondary indices. It would be nice to allow multiple physical representations under one logical table, for example, to store data in more than one physical order or even to allow representations with pre-aggregated data along with original data.
@ -187,7 +187,7 @@ Replication in ClickHouse can be configured on a per-table basis. You could have
Replication is implemented in the `ReplicatedMergeTree` storage engine. The path in `ZooKeeper` is specified as a parameter for the storage engine. All tables with the same path in `ZooKeeper` become replicas of each other: they synchronize their data and maintain consistency. Replicas can be added and removed dynamically simply by creating or dropping a table.
Replication uses an asynchronous multi-master scheme. You can insert data into any replica that has a session with `ZooKeeper`, and data is replicated to all other replicas asynchronously. Because ClickHouse doesnt support UPDATEs, replication is conflict-free. As there is no quorum acknowledgment of inserts, just-inserted data might be lost if one node fails.
Replication uses an asynchronous multi-master scheme. You can insert data into any replica that has a session with `ZooKeeper`, and data is replicated to all other replicas asynchronously. Because ClickHouse doesn't support UPDATEs, replication is conflict-free. As there is no quorum acknowledgment of inserts, just-inserted data might be lost if one node fails.
Metadata for replication is stored in ZooKeeper. There is a replication log that lists what actions to do. Actions are: get part; merge parts; drop a partition, and so on. Each replica copies the replication log to its queue and then executes the actions from the queue. For example, on insertion, the “get the part” action is created in the log, and every replica downloads that part. Merges are coordinated between replicas to get byte-identical results. All parts are merged in the same way on all replicas. It is achieved by electing one replica as the leader, and that replica initiates merges and writes “merge parts” actions to the log.

View File

@ -1055,11 +1055,11 @@ Each Avro message embeds a schema id that can be resolved to the actual schema w
Schemas are cached once resolved.
Schema Registry URL is configured with [format\_avro\_schema\_registry\_url](../operations/settings/settings.md#settings-format_avro_schema_registry_url)
Schema Registry URL is configured with [format\_avro\_schema\_registry\_url](../operations/settings/settings.md#format_avro_schema_registry_url).
### Data Types Matching {#data_types-matching-1}
Same as [Avro](#data-format-avro)
Same as [Avro](#data-format-avro).
### Usage {#usage}
@ -1093,7 +1093,7 @@ SELECT * FROM topic1_stream;
```
!!! note "Warning"
Setting `format_avro_schema_registry_url` needs to be configured in `users.xml` to maintain its value after a restart.
Setting `format_avro_schema_registry_url` needs to be configured in `users.xml` to maintain its value after a restart. Also you can use the `format_avro_schema_registry_url` setting of the `Kafka` table engine.
## Parquet {#data-format-parquet}

View File

@ -95,6 +95,10 @@ Features:
[cickhouse-plantuml](https://pypi.org/project/clickhouse-plantuml/) is a script to generate [PlantUML](https://plantuml.com/) diagram of tables schemes.
### xeus-clickhouse {#xeus-clickhouse}
[xeus-clickhouse](https://github.com/wangfenjin/xeus-clickhouse) is a Jupyter kernal for ClickHouse, which supports query CH data using SQL in Jupyter.
## Commercial {#commercial}
### DataGrip {#datagrip}

View File

@ -51,6 +51,7 @@ toc_title: Adopters
| <a href="https://www.qingcloud.com/" class="favicon">QINGCLOUD</a> | Cloud services | Main product | — | — | [Slides in Chinese, October 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup19/4.%20Cloud%20%2B%20TSDB%20for%20ClickHouse%20张健%20QingCloud.pdf) |
| <a href="https://qrator.net" class="favicon">Qrator</a> | DDoS protection | Main product | — | — | [Blog Post, March 2019](https://blog.qrator.net/en/clickhouse-ddos-mitigation_37/) |
| <a href="https://www.percent.cn/" class="favicon">Percent 百分点</a> | Analytics | Main Product | — | — | [Slides in Chinese, June 2019](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup24/4.%20ClickHouse万亿数据双中心的设计与实践%20.pdf) |
| <a href="https://plausible.io/" class="favicon">Plausible</a> | Analytics | Main Product | — | — | [Blog post, June 2020](https://twitter.com/PlausibleHQ/status/1273889629087969280) |
| <a href="https://rambler.ru" class="favicon">Rambler</a> | Internet services | Analytics | — | — | [Talk in Russian, April 2018](https://medium.com/@ramblertop/разработка-api-clickhouse-для-рамблер-топ-100-f4c7e56f3141) |
| <a href="https://www.tencent.com" class="favicon">Tencent</a> | Messaging | Logging | — | — | [Talk in Chinese, November 2019](https://youtu.be/T-iVQRuw-QY?t=5050) |
| <a href="https://trafficstars.com/" class="favicon">Traffic Stars</a> | AD network | — | — | — | [Slides in Russian, May 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup15/lightning/ninja.pdf) |

View File

@ -34,7 +34,7 @@ By default, the ClickHouse server provides the `default` user account which is n
If you just started using ClickHouse, consider the following scenario:
1. [Enable](#enabling-access-control) SQL-driven access control and account management for the `default` user.
2. Log in to the `default` user account and create all the required users. Dont forget to create an administrator account (`GRANT ALL ON *.* WITH GRANT OPTION TO admin_user_account`).
2. Log in to the `default` user account and create all the required users. Dont forget to create an administrator account (`GRANT ALL ON *.* TO admin_user_account WITH GRANT OPTION`).
3. [Restrict permissions](../operations/settings/permissions-for-queries.md#permissions_for_queries) for the `default` user and disable SQL-driven access control and account management for it.
### Properties of Current Solution {#access-control-properties}

View File

@ -398,6 +398,27 @@ The cache is shared for the server and memory is allocated as needed. The cache
<mark_cache_size>5368709120</mark_cache_size>
```
## max_server_memory_usage {#max_server_memory_usage}
Limits total RAM usage by the ClickHouse server. You can specify it only for the default profile.
Possible values:
- Positive integer.
- 0 — Unlimited.
Default value: `0`.
**Additional Info**
On hosts with low RAM and swap, you possibly need setting `max_server_memory_usage_to_ram_ratio > 1`.
**See also**
- [max_memory_usage](../settings/query-complexity.md#settings_max_memory_usage)
## max\_concurrent\_queries {#max-concurrent-queries}
The maximum number of simultaneously processed requests.

View File

@ -36,7 +36,7 @@ Memory usage is not monitored for the states of certain aggregate functions.
Memory usage is not fully tracked for states of the aggregate functions `min`, `max`, `any`, `anyLast`, `argMin`, `argMax` from `String` and `Array` arguments.
Memory consumption is also restricted by the parameters `max_memory_usage_for_user` and `max_memory_usage_for_all_queries`.
Memory consumption is also restricted by the parameters `max_memory_usage_for_user` and [max_server_memory_usage](../server-configuration-parameters/settings.md#max_server_memory_usage).
## max\_memory\_usage\_for\_user {#max-memory-usage-for-user}
@ -46,18 +46,10 @@ Default values are defined in [Settings.h](https://github.com/ClickHouse/ClickHo
See also the description of [max\_memory\_usage](#settings_max_memory_usage).
## max\_memory\_usage\_for\_all\_queries {#max-memory-usage-for-all-queries}
The maximum amount of RAM to use for running all queries on a single server.
Default values are defined in [Settings.h](https://github.com/ClickHouse/ClickHouse/blob/master/src/Core/Settings.h#L289). By default, the amount is not restricted (`max_memory_usage_for_all_queries = 0`).
See also the description of [max\_memory\_usage](#settings_max_memory_usage).
## max\_rows\_to\_read {#max-rows-to-read}
The following restrictions can be checked on each block (instead of on each row). That is, the restrictions can be broken a little.
When running a query in multiple threads, the following restrictions apply to each thread separately.
A maximum number of rows that can be read from a table when running a query.

View File

@ -727,6 +727,17 @@ The INSERT query also contains data for INSERT that is processed by a separate s
Default value: 256 KiB.
## max\_parser\_depth {#max_parser_depth}
Limits maximum recursion depth in the recursive descent parser. Allows to control stack size.
Possible values:
- Positive integer.
- 0 — Recursion depth is unlimited.
Default value: 1000.
## interactive\_delay {#interactive-delay}
The interval in microseconds for checking whether request execution has been cancelled and sending the progress.
@ -1368,13 +1379,11 @@ Possible values: 32 (32 bytes) - 1073741824 (1 GiB)
Default value: 32768 (32 KiB)
## format\_avro\_schema\_registry\_url {#settings-format_avro_schema_registry_url}
## format\_avro\_schema\_registry\_url {#format_avro_schema_registry_url}
Sets Confluent Schema Registry URL to use with [AvroConfluent](../../interfaces/formats.md#data-format-avro-confluent) format
Sets [Confluent Schema Registry](https://docs.confluent.io/current/schema-registry/index.html) URL to use with [AvroConfluent](../../interfaces/formats.md#data-format-avro-confluent) format.
Type: URL
Default value: Empty
Default value: `Empty`.
## background\_pool\_size {#background_pool_size}
@ -1418,6 +1427,23 @@ Possible values:
Default value: 16.
## always_fetch_merged_part {#always_fetch_merged_part}
Prohibits data parts merging in [Replicated*MergeTree](../../engines/table-engines/mergetree-family/replication.md)-engine tables.
When merging is prohibited, the replica never merges parts and always downloads merged parts from other replicas. If there is no required data yet, the replica waits for it. CPU and disk load on the replica server decreases, but the network load on cluster increases. This setting can be useful on servers with relatively weak CPUs or slow disks, such as servers for backups storage.
Possible values:
- 0 — `Replicated*MergeTree`-engine tables merge data parts at the replica.
- 1 — `Replicated*MergeTree`-engine tables don't merge data parts at the replica. The tables download merged data parts from other replicas.
Default value: 0.
**See Also**
- [Data Replication](../../engines/table-engines/mergetree-family/replication.md)
## background\_distributed\_schedule\_pool\_size {#background_distributed_schedule_pool_size}
Sets the number of threads performing background tasks for [distributed](../../engines/table-engines/special/distributed.md) sends. This setting is applied at ClickHouse server start and cant be changed in a user session.

View File

@ -120,6 +120,7 @@ zoo.cfg:
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
# This value is not quite motivated
initLimit=30000
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
@ -127,6 +128,9 @@ syncLimit=10
maxClientCnxns=2000
# It is the maximum value that client may request and the server will accept.
# It is Ok to have high maxSessionTimeout on server to allow clients to work with high session timeout if they want.
# But we request session timeout of 30 seconds by default (you can change it with session_timeout_ms in ClickHouse config).
maxSessionTimeout=60000000
# the directory where the snapshot is stored.
dataDir=/opt/zookeeper/{{ '{{' }} cluster['name'] {{ '}}' }}/data

View File

@ -33,7 +33,7 @@ To work with these states, use:
- [AggregatingMergeTree](../../engines/table-engines/mergetree-family/aggregatingmergetree.md) table engine.
- [finalizeAggregation](../../sql-reference/functions/other-functions.md#function-finalizeaggregation) function.
- [runningAccumulate](../../sql-reference/functions/other-functions.md#function-runningaccumulate) function.
- [runningAccumulate](../../sql-reference/functions/other-functions.md#runningaccumulate) function.
- [-Merge](#aggregate_functions_combinators-merge) combinator.
- [-MergeState](#aggregate_functions_combinators-mergestate) combinator.

View File

@ -54,10 +54,12 @@ LAYOUT(LAYOUT_TYPE(param value)) -- layout settings
- [hashed](#dicts-external_dicts_dict_layout-hashed)
- [sparse\_hashed](#dicts-external_dicts_dict_layout-sparse_hashed)
- [cache](#cache)
- [ssd\_cache](#ssd-cache)
- [direct](#direct)
- [range\_hashed](#range-hashed)
- [complex\_key\_hashed](#complex-key-hashed)
- [complex\_key\_cache](#complex-key-cache)
- [ssd\_complex\_key\_cache](#ssd-cache)
- [complex\_key\_direct](#complex-key-direct)
- [ip\_trie](#ip-trie)
@ -296,6 +298,40 @@ Set a large enough cache size. You need to experiment to select the number of ce
This type of storage is for use with composite [keys](../../../sql-reference/dictionaries/external-dictionaries/external-dicts-dict-structure.md). Similar to `cache`.
### ssd\_cache {#ssd-cache}
Similar to `cache`, but stores data on SSD and index in RAM.
``` xml
<layout>
<ssd_cache>
<!-- Size of elementary read block in bytes. Recommended to be equal to SSD's page size. -->
<block_size>4096</block_size>
<!-- Max cache file size in bytes. -->
<file_size>16777216</file_size>
<!-- Size of RAM buffer in bytes for reading elements from SSD. -->
<read_buffer_size>131072</read_buffer_size>
<!-- Size of RAM buffer in bytes for aggregating elements before flushing to SSD. -->
<write_buffer_size>1048576</write_buffer_size>
<!-- Path where cache file will be stored. -->
<path>/var/lib/clickhouse/clickhouse_dictionaries/test_dict</path>
<!-- Max number on stored keys in the cache. Rounded up to a power of two. -->
<max_stored_keys>1048576</max_stored_keys>
</ssd_cache>
</layout>
```
or
``` sql
LAYOUT(CACHE(BLOCK_SIZE 4096 FILE_SIZE 16777216 READ_BUFFER_SIZE 1048576
PATH /var/lib/clickhouse/clickhouse_dictionaries/test_dict MAX_STORED_KEYS 1048576))
```
### complex\_key\_ssd\_cache {#complex-key-ssd-cache}
This type of storage is for use with composite [keys](external-dicts-dict-structure.md). Similar to `ssd\_cache`.
### direct {#direct}
The dictionary is not stored in memory and directly goes to the source during the processing of a request.

View File

@ -875,10 +875,14 @@ arrayReduce(agg_func, arr1, arr2, ..., arrN)
**Example**
Query:
``` sql
SELECT arrayReduce('max', [1, 2, 3])
```
Result:
``` text
┌─arrayReduce('max', [1, 2, 3])─┐
│ 3 │
@ -887,10 +891,14 @@ SELECT arrayReduce('max', [1, 2, 3])
If an aggregate function takes multiple arguments, then this function must be applied to multiple arrays of the same size.
Query:
``` sql
SELECT arrayReduce('maxIf', [3, 5], [1, 0])
```
Result:
``` text
┌─arrayReduce('maxIf', [3, 5], [1, 0])─┐
│ 3 │
@ -899,10 +907,14 @@ SELECT arrayReduce('maxIf', [3, 5], [1, 0])
Example with a parametric aggregate function:
Query:
``` sql
SELECT arrayReduce('uniqUpTo(3)', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
```
Result:
``` text
┌─arrayReduce('uniqUpTo(3)', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])─┐
│ 4 │
@ -923,12 +935,18 @@ arrayReduceInRanges(agg_func, ranges, arr1, arr2, ..., arrN)
- `agg_func` — The name of an aggregate function which should be a constant [string](../../sql-reference/data-types/string.md).
- `ranges` — The ranges to aggretate which should be an [array](../../sql-reference/data-types/array.md) of [tuples](../../sql-reference/data-types/tuple.md) which containing the index and the length of each range.
- `arr` — Any number of [array](../../sql-reference/data-types/array.md) type columns as the parameters of the aggregation function.
- `arr` — Any number of [Array](../../sql-reference/data-types/array.md) type columns as the parameters of the aggregation function.
**Returned value**
- Array containing results of the aggregate function over specified ranges.
Type: [Array](../../sql-reference/data-types/array.md).
**Example**
Query:
``` sql
SELECT arrayReduceInRanges(
'sum',
@ -937,6 +955,8 @@ SELECT arrayReduceInRanges(
) AS res
```
Result:
``` text
┌─res─────────────────────────┐
│ [1234500,234000,34560,4567] │

View File

@ -783,7 +783,7 @@ Returns size on disk (without taking into account compression).
blockSerializedSize(value[, value[, ...]])
```
**Parameters:**
**Parameters**
- `value` — Any value.
@ -793,10 +793,14 @@ blockSerializedSize(value[, value[, ...]])
**Example**
Query:
``` sql
SELECT blockSerializedSize(maxState(1)) as x
```
Result:
``` text
┌─x─┐
│ 2 │
@ -1050,11 +1054,110 @@ Result:
Takes state of aggregate function. Returns result of aggregation (finalized state).
## runningAccumulate {#function-runningaccumulate}
## runningAccumulate {#runningaccumulate}
Takes the states of the aggregate function and returns a column with values, are the result of the accumulation of these states for a set of block lines, from the first to the current line.
For example, takes state of aggregate function (example runningAccumulate(uniqState(UserID))), and for each row of block, return result of aggregate function on merge of states of all previous rows and current row.
So, result of function depends on partition of data to blocks and on order of data in block.
Accumulates states of an aggregate function for each row of a data block.
!!! warning "Warning"
The state is reset for each new data block.
**Syntax**
```sql
runningAccumulate(agg_state[, grouping]);
```
**Parameters**
- `agg_state` — State of the aggregate function. [AggregateFunction](../../sql-reference/data-types/aggregatefunction.md#data-type-aggregatefunction).
- `grouping` — Grouping key. Optional. The state of the function is reset if the `grouping` value is changed. It can be any of the [supported data types](../../sql-reference/data-types/index.md) for which the equality operator is defined.
**Returned value**
- Each resulting row contains a result of the aggregate function, accumulated for all the input rows from 0 to the current position. `runningAccumulate` resets states for each new data block or when the `grouping` value changes.
Type depends on the aggregate function used.
**Examples**
Consider how you can use `runningAccumulate` to find the cumulative sum of numbers without and with grouping.
Query:
```sql
SELECT k, runningAccumulate(sum_k) AS res FROM (SELECT number as k, sumState(k) AS sum_k FROM numbers(10) GROUP BY k ORDER BY k);
```
Result:
```text
┌─k─┬─res─┐
│ 0 │ 0 │
│ 1 │ 1 │
│ 2 │ 3 │
│ 3 │ 6 │
│ 4 │ 10 │
│ 5 │ 15 │
│ 6 │ 21 │
│ 7 │ 28 │
│ 8 │ 36 │
│ 9 │ 45 │
└───┴─────┘
```
The subquery generates `sumState` for every number from `0` to `9`. `sumState` returns the state of the [sum](../aggregate-functions/reference/sum.md) function that contains the sum of a single number.
The whole query does the following:
1. For the first row, `runningAccumulate` takes `sumState(0)` and returns `0`.
2. For the second row, the function merges `sumState(0)` and `sumState(1)` resulting in `sumState(0 + 1)`, and returns `1` as a result.
3. For the third row, the function merges `sumState(0 + 1)` and `sumState(2)` resulting in `sumState(0 + 1 + 2)`, and returns `3` as a result.
4. The actions are repeated until the block ends.
The following example shows the `groupping` parameter usage:
Query:
```sql
SELECT
grouping,
item,
runningAccumulate(state, grouping) AS res
FROM
(
SELECT
toInt8(number / 4) AS grouping,
number AS item,
sumState(number) AS state
FROM numbers(15)
GROUP BY item
ORDER BY item ASC
);
```
Result:
```text
┌─grouping─┬─item─┬─res─┐
│ 0 │ 0 │ 0 │
│ 0 │ 1 │ 1 │
│ 0 │ 2 │ 3 │
│ 0 │ 3 │ 6 │
│ 1 │ 4 │ 4 │
│ 1 │ 5 │ 9 │
│ 1 │ 6 │ 15 │
│ 1 │ 7 │ 22 │
│ 2 │ 8 │ 8 │
│ 2 │ 9 │ 17 │
│ 2 │ 10 │ 27 │
│ 2 │ 11 │ 38 │
│ 3 │ 12 │ 12 │
│ 3 │ 13 │ 25 │
│ 3 │ 14 │ 39 │
└──────────┴──────┴─────┘
```
As you can see, `runningAccumulate` merges states for each group of rows separately.
## joinGet {#joinget}

View File

@ -59,7 +59,6 @@ Ver también la descripción de [Método de codificación de datos:](#settings_m
## ¿Qué puedes encontrar en Neodigit {#max-rows-to-read}
Las siguientes restricciones se pueden verificar en cada bloque (en lugar de en cada fila). Es decir, las restricciones se pueden romper un poco.
Al ejecutar una consulta en varios subprocesos, las siguientes restricciones se aplican a cada subproceso por separado.
Un número máximo de filas que se pueden leer de una tabla al ejecutar una consulta.

View File

@ -1 +0,0 @@
../../en/development/architecture.md

View File

@ -0,0 +1,197 @@
# Обзор архитектуры ClickHouse {#overview-of-clickhouse-architecture}
ClickHouse - полноценная колоночная СУБД. Данные хранятся в колонках, а в процессе обработки - в массивах (векторах или фрагментах (chunkах) колонок). По возможности операции выполняются на массивах, а не на индивидуальных значениях. Это называется “векторизованное выполнения запросов” (vectorized query execution), и помогает снизить стоимость фактической обработки данных.
> Эта идея не нова. Такой подход использовался в `APL` (A programming language, 1957) и его потомках: `A +` (диалект `APL`), `J` (1990), `K` (1993) и `Q` (язык программирования Kx Systems, 2003). Программирование на массивах (Array programming) используется в научных вычислительных системах. Эта идея не является чем-то новым и для реляционных баз данных: например, она используется в системе `VectorWise` (так же известной как Actian Vector Analytic Database от Actian Corporation).
Существует два различных подхода для увеличения скорости обработки запросов: выполнение векторизованного запроса и генерация кода во время выполнения (runtime code generation). В последнем случае код генерируется на лету для каждого типа запроса, удаляя все косвенные и динамические обращения. Ни один из этих подходов не имеет явного преимущества. Генерация кода во время выполнения выигрывает, если объединяет большое число операций, таким образом полностью используя вычислительные блоки и конвейер CPU. Выполнение векторизованного запроса может быть менее практично потому, что задействует временные векторы данных, которые должны быть записаны и прочитаны из кэша. Если временные данные не помещаются в L2 кэш, будут проблемы. С другой стороны выполнение векторизованного запроса упрощает использование SIMD инструкций CPU. [Научная работа](http://15721.courses.cs.cmu.edu/spring2016/papers/p5-sompolski.pdf) наших друзей показывает преимущества сочетания обоих подходов. ClickHouse использует выполнение векторизованного запроса и имеет ограниченную начальную поддержку генерации кода во время выполнения.
## Колонки {#columns}
Для представления столбцов в памяти (фактически, фрагментов столбцов) используется интерфейс `IColumn`. Интерфейс предоставляет вспомогательные методы для реализации различных реляционных операторов. Почти все операции иммутабельные: они не изменяют оригинальных колонок, а создают новую с измененными значениями. Например, метод `IColumn :: filter` принимает фильтр - набор байт. Он используется для реляционных операторов `WHERE` и `HAVING`. Другой пример: метод `IColumn :: permute` используется для поддержки `ORDER BY`, метод `IColumn :: cut` - `LIMIT` и т. д.
Различные реализации `IColumn` (`ColumnUInt8`, `ColumnString` и т. д.) отвечают за распределение данных колонки в памяти. Для колонок целочисленного типа это один смежный массив, такой как `std :: vector`. Для колонок типа `String` и `Array` это два вектора: один для всех элементов массивов, располагающихся смежно, второй для хранения смещения до начала каждого массива. Также существует реализация `ColumnConst`, в которой хранится только одно значение в памяти, но выглядит как колонка.
## Поля {#field}
Тем не менее, можно работать и с индивидуальными значениями. Для представления индивидуальных значений используется `Поле` (`Field`). `Field` - размеченное объединение `UInt64`, `Int64`, `Float64`, `String` и `Array`. `IColumn` имеет метод `оператор []` для получения значения по индексу n как `Field`, а также метод insert для добавления `Field` в конец колонки. Эти методы не очень эффективны, так как требуют временных объектов `Field`, представляющих индивидуальное значение. Есть более эффективные методы, такие как `insertFrom`, `insertRangeFrom` и т.д.
`Field` не несет в себе достаточно информации о конкретном типе данных в таблице. Например, `UInt8`, `UInt16`, `UInt32` и `UInt64` в `Field` представлены как `UInt64`.
## Дырявые абстракции (Leaky Abstractions) {#leaky-abstractions}
`IColumn` предоставляет методы для общих реляционных преобразований данных, но они не отвечают всем потребностям. Например, `ColumnUInt64` не имеет метода для вычисления суммы двух столбцов, а `ColumnString` не имеет метода для запуска поиска по подстроке. Эти бесчисленные процедуры реализованы вне `IColumn`.
Различные функции на колонках могут быть реализованы обобщенным, неэффективным путем, используя `IColumn` методы для извлечения значений `Field`, или специальным путем, используя знания о внутреннем распределение данных в памяти в конкретной реализации `IColumn`. Для этого функции приводятся к конкретному типу `IColumn` и работают напрямую с его внутренним представлением. Например, в `ColumnUInt64` есть метод getData, который возвращает ссылку на внутренний массив, чтение и заполнение которого, выполняется отдельной процедурой напрямую. Фактически, мы имеем "дырявую абстракции", обеспечивающие эффективные специализации различных процедур.
## Типы данных (Data Types) {#data_types}
`IDataType` отвечает за сериализацию и десериализацию - чтение и запись фрагментов колонок или индивидуальных значений в бинарном или текстовом формате.
`IDataType` точно соответствует типам данных в таблицах - `DataTypeUInt32`, `DataTypeDateTime`, `DataTypeString` и т. д.
`IDataType` и `IColumn` слабо связаны друг с другом. Различные типы данных могут быть представлены в памяти с помощью одной реализации `IColumn`. Например, и `DataTypeUInt32`, и `DataTypeDateTime` в памяти представлены как `ColumnUInt32` или `ColumnConstUInt32`. В добавок к этому, один тип данных может быть представлен различными реализациями `IColumn`. Например, `DataTypeUInt8` может быть представлен как `ColumnUInt8` и `ColumnConstUInt8`.
`IDataType` хранит только метаданные. Например, `DataTypeUInt8` не хранить ничего (кроме скрытого указателя `vptr`), а `DataTypeFixedString` хранит только `N` (фиксированный размер строки).
В `IDataType` есть вспомогательные методы для данных различного формата. Среди них методы сериализации значений, допускающих использование кавычек, сериализации значения в JSON или XML. Среди них нет прямого соответствия форматам данных. Например, различные форматы `Pretty` и `TabSeparated` могут использовать один вспомогательный метод `serializeTextEscaped` интерфейса `IDataType`.
## Блоки (Block) {#block}
`Block` это контейнер, который представляет фрагмент (chunk) таблицы в памяти. Это набор троек - `(IColumn, IDataType, имя колонки)`. В процессе выполнения запроса, данные обрабатываются `Block`ами. Если у нас есть `Block`, значит у нас есть данные (в объекте `IColumn`), информация о типе (в `IDataType`), которая говорит нам, как работать с колонкой, и имя колонки (оригинальное имя колонки таблицы или служебное имя, присвоенное для получения промежуточных результатов вычислений).
При вычислении некоторой функции на колонках в блоке мы добавляем еще одну колонку с результатами в блок, не трогая колонки аргументов функции, потому что операции иммутабельные. Позже ненужные колонки могут быть удалены из блока, но не модифицированы. Это удобно для устранения общих подвыражений.
Блоки создаются для всех обработанных фрагментов данных. Напоминаем, что одни и те же типы вычислений, имена колонок и типы переиспользуются в разных блоках и только данные колонок изменяются. Лучше разделить данные и заголовок блока потому, что в блоках маленького размера мы имеем большой оверхэд по временным строкам при копировании умных указателей (`shared_ptrs`) и имен колонок.
## Потоки блоков (Block Streams) {#block-streams}
Потоки блоков обрабатывают данные. Мы используем потоки блоков для чтения данных, трансформации или записи данных куда-либо. `IBlockInputStream` предоставляет метод `read` для получения следующего блока, пока это возможно, и метод `write`, чтобы продвигать (push) блок куда-либо.
Потоки отвечают за:
1. Чтение и запись в таблицу. Таблица лишь возвращает поток для чтения или записи блоков.
2. Реализацию форматов данных. Например, при выводе данных в терминал в формате `Pretty`, вы создаете выходной поток блоков, который форматирует поступающие в него блоки.
3. Трансформацию данных. Допустим, у вас есть `IBlockInputStream` и вы хотите создать отфильтрованный поток. Вы создаете `FilterBlockInputStream` и инициализируете его вашим потоком. Затем вы тянете (pull) блоки из `FilterBlockInputStream`, а он тянет блоки исходного потока, фильтрует их и возвращает отфильтрованные блоки вам. Таким образом построены конвейеры выполнения запросов.
Имеются и более сложные трансформации. Например, когда вы тянете блоки из `AggregatingBlockInputStream`, он считывает все данные из своего источника, агрегирует их, и возвращает поток агрегированных данных вам. Другой пример: конструктор `UnionBlockInputStream` принимает множество источников входных данных и число потоков. Такой `Stream` работает в несколько потоков и читает данные источников параллельно.
> Потоки блоков используют «втягивающий» (pull) подход к управлению потоком выполнения: когда вы вытягиваете блок из первого потока, он, следовательно, вытягивает необходимые блоки из вложенных потоков, так и работает весь конвейер выполнения. Ни «pull» ни «push» не имеют явного преимущества, потому что поток управления неявный, и это ограничивает в реализации различных функций, таких как одновременное выполнение нескольких запросов (слияние нескольких конвейеров вместе). Это ограничение можно преодолеть с помощью сопрограмм (coroutines) или просто запуском дополнительных потоков, которые ждут друг друга. У нас может быть больше возможностей, если мы сделаем поток управления явным: если мы локализуем логику для передачи данных из одной расчетной единицы в другую вне этих расчетных единиц. Читайте эту [статью](http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/) для углубленного изучения.
Следует отметить, что конвейер выполнения запроса создает временные данные на каждом шаге. Мы стараемся сохранить размер блока достаточно маленьким, чтобы временные данные помещались в кэш процессора. При таком допущении запись и чтение временных данных практически бесплатны по сравнению с другими расчетами. Мы могли бы рассмотреть альтернативу, которая заключается в том, чтобы объединить многие операции в конвеере вместе. Это может сделать конвейер как можно короче и удалить большую часть временных данных, что может быть преимуществом, но у такого подхода также есть недостатки. Например, разделенный конвейер позволяет легко реализовать кэширование промежуточных данных, использование промежуточных данных из аналогичных запросов, выполняемых одновременно, и объединение конвейеров для аналогичных запросов.
## Форматы {#formats}
Форматы данных реализуются с помощью потоков блоков. Есть форматы представления (presentational), пригодные только для вывода данных клиенту, такие как `Pretty` формат, который предоставляет только `IBlockOutputStream`. И есть форматы ввода/вывода, такие как `TabSeparated` или `JSONEachRow`.
Существуют также потоки строк: `IRowInputStream` и `IRowOutputStream`. Они позволяют вытягивать/выталкивать данные отдельными строками, а не блоками. Они нужны только для упрощения реализации ориентированных на строки форматов. Обертка `BlockInputStreamFromRowInputStream` и `BlockOutputStreamFromRowOutputStream` позволяет конвертировать потоки, ориентированные на строки, в обычные потоки, ориентированные на блоки.
## I/O {#io}
Для байт-ориентированных ввода/вывода существуют абстрактные классы `ReadBuffer` и `WriteBuffer`. Они используются вместо C++ `iostream`. Не волнуйтесь: каждый зрелый проект C++ использует что-то другое вместо `iostream` по уважительным причинам.
`ReadBuffer` и `WriteBuffer` это просто непрерывный буфер и курсор, указывающий на позицию в этом буфере. Реализации могут как владеть так и не владеть памятью буфера. Существует виртуальный метод заполнения буфера следующими данными (для `ReadBuffer`) или сброса буфера куда-нибудь (например `WriteBuffer`). Виртуальные методы редко вызываются.
Реализации `ReadBuffer`/`WriteBuffer` используются для работы с файлами и файловыми дескрипторами, а также сетевыми сокетами, для реализации сжатия (`CompressedWriteBuffer` инициализируется вместе с другим `WriteBuffer` и осуществляет сжатие данных перед записью в него), и для других целей названия `ConcatReadBuffer`, `LimitReadBuffer`, и `HashingWriteBuffer` говорят сами за себя.
Буферы чтения/записи имеют дело только с байтами. В заголовочных файлах `ReadHelpers` и `WriteHelpers` объявлены некоторые функции, чтобы помочь с форматированием ввода/вывода. Например, есть помощники для записи числа в десятичном формате.
Давайте посмотрим, что происходит, когда вы хотите вывести результат в `JSON` формате в стандартный вывод (stdout). У вас есть результирующий набор данных, готовый к извлечению из `IBlockInputStream`. Вы создаете `WriteBufferFromFileDescriptor(STDOUT_FILENO)` чтобы записать байты в stdout. Вы создаете `JSONRowOutputStream`, инициализируете с этим `WriteBuffer`'ом, чтобы записать строки `JSON` в stdout. Кроме того вы создаете `BlockOutputStreamFromRowOutputStream`, реализуя `IBlockOutputStream`. Затем вызывается `copyData` для передачи данных из `IBlockInputStream` в `IBlockOutputStream` и все работает. Внутренний `JSONRowOutputStream` будет писать в формате `JSON` различные разделители и вызвать `IDataType::serializeTextJSON` метод со ссылкой на `IColumn` и номер строки в качестве аргументов. Следовательно, `IDataType::serializeTextJSON` вызовет метод из `WriteHelpers.h`: например, `writeText` для числовых типов и `writeJSONString` для `DataTypeString`.
## Таблицы {#tables}
Интерфейс `IStorage` служит для отображения таблицы. Различные движки таблиц являются реализациями этого интерфейса. Примеры `StorageMergeTree`, `StorageMemory` и так далее. Экземпляры этих классов являются просто таблицами.
Ключевые методы `IStorage` это `read` и `write`. Есть и другие варианты - `alter`, `rename`, `drop` и так далее. Метод `read` принимает следующие аргументы: набор столбцов для чтения из таблицы, `AST` запрос и желаемое количество потоков для вывода. Он возвращает один или несколько объектов `IBlockInputStream` и информацию о стадии обработки данных, которая была завершена внутри табличного движка во время выполнения запроса.
В большинстве случаев метод read отвечает только за чтение указанных столбцов из таблицы, а не за дальнейшую обработку данных. Вся дальнейшая обработка данных осуществляется интерпретатором запросов и не входит в сферу ответственности `IStorage`.
Но есть и заметные исключения:
- AST запрос, передающийся в метод `read`, может использоваться движком таблицы для получения информации о возможности использования индекса и считывания меньшего количества данных из таблицы.
- Иногда движок таблиц может сам обрабатывать данные до определенного этапа. Например, `StorageDistributed` можно отправить запрос на удаленные серверы, попросить их обработать данные до этапа, когда данные с разных удаленных серверов могут быть объединены, и вернуть эти предварительно обработанные данные. Затем интерпретатор запросов завершает обработку данных.
Метод `read` может возвращать несколько объектов `IBlockInputStream`, позволяя осуществлять параллельную обработку данных. Эти несколько блочных входных потоков могут считываться из таблицы параллельно. Затем вы можете обернуть эти потоки различными преобразованиями (такими как вычисление выражений или фильтрация), которые могут быть вычислены независимо, и создать `UnionBlockInputStream` поверх них, чтобы читать из нескольких потоков параллельно.
Есть и другие варианты. Например, `TableFunction` возвращает временный объект `IStorage`, который можно подставить во `FROM`.
Чтобы получить быстрое представление о том, как реализовать свой движок таблиц, посмотрите на что-то простое, например `StorageMemory` или `StorageTinyLog`.
> В качестве результата выполнения метода `read`, `IStorage` возвращает `QueryProcessingStage` информацию о том, какие части запроса были обработаны внутри хранилища.
## Парсеры (Parsers) {#parsers}
Написанный от руки парсер, анализирующий запрос, работает по методу рекурсивного спуска. Например, `ParserSelectQuery` просто рекурсивно вызывает нижестоящие парсеры для различных частей запроса. Парсеры создают абстрактное синтаксическое дерево (`AST`). `AST` представлен узлами, которые являются экземплярами `IAST`.
> Генераторы парсеров не используются по историческим причинам.
## Интерпретаторы {#interpreters}
Интерпретаторы отвечают за создание конвейера выполнения запроса из `AST`. Есть простые интерпретаторы, такие как `InterpreterExistsQuery` и `InterpreterDropQuery` или более сложный `InterpreterSelectQuery`. Конвейер выполнения запроса представляет собой комбинацию входных и выходных потоков блоков. Например, результатом интерпретации `SELECT` запроса является `IBlockInputStream` для чтения результирующего набора данных; результат интерпретации `INSERT` запроса - это `IBlockOutputStream`, для записи данных, предназначенных для вставки; результат интерпретации `INSERT SELECT` запроса - это `IBlockInputStream`, который возвращает пустой результирующий набор при первом чтении, но копирует данные из `SELECT` к `INSERT`.
`InterpreterSelectQuery` использует `ExpressionAnalyzer` и `ExpressionActions` механизмы для анализа запросов и преобразований. Именно здесь выполняется большинство оптимизаций запросов на основе правил. `ExpressionAnalyzer` написан довольно грязно и должен быть переписан: различные преобразования запросов и оптимизации должны быть извлечены в отдельные классы, чтобы позволить модульные преобразования или запросы.
## Функции {#functions}
Существуют обычные функции и агрегатные функции. Агрегатные функции смотрите в следующем разделе.
Обычный функции не изменяют число строк и работают так, как если бы обрабатывали каждую строку независимо. В действительности же, функции вызываются не к отдельным строкам, а блокам данных для реализации векторизованного выполнения запросов.
Некоторые функции, такие как [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), и [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulate), эксплуатируют блочную обработку и нарушают независимость строк.
ClickHouse имеет сильную типизацию, поэтому нет никакого неявного преобразования типов. Если функция не поддерживает определенную комбинацию типов, она создает исключение. Но функции могут работать (перегружаться) для многих различных комбинаций типов. Например, функция `plus` (для реализации `+` оператор) работает для любой комбинации числовых типов: `UInt8` + `Float32`, `UInt16` + `Int8` и так далее. Кроме того, некоторые вариадические функции, такие как `concat`, могут принимать любое количество аргументов.
Реализация функции может быть немного неудобной, поскольку функция явно определяет поддерживаемые типы данных и поддерживается `IColumns`. Например, в `plus` функция имеет код, генерируемый экземпляром шаблона C++ для каждой комбинации числовых типов, а также постоянные или непостоянные левые и правые аргументы.
Это отличное место для реализации генерации кода во время выполнения, чтобы избежать раздувания кода шаблона. Кроме того, он позволяет добавлять слитые функции, такие как fused multiply-add или выполнять несколько сравнений в одной итерации цикла.
Из-за векторизованного выполнения запроса функции не закорачиваются. Например, если вы пишете `WHERE f(x) AND g(y)`, обе части вычисляются, даже для строк, когда `f(x)` равно нулю (за исключением тех случаев, когда `f(x)` является нулевым постоянным выражением). Но если избирательность условия `f(x)` высока, и расчет `f(x)` обходится гораздо дешевле, чем `g(y)`, лучше всего разделить вычисление на этапы. На первом этапе вычислить `f(x)`, отфильтровать результирующие столбцы, а затем вычислять `g(y)` только для меньших, отфильтрованных фрагментов данных.
## Агрегатные функции {#aggregate-functions}
Агрегатные функции - это функции с состоянием (stateful). Они накапливают переданные значения в некотором состоянии и позволяют получать результаты из этого состояния. Работа с ними осуществляется с помощью интерфейса `IAggregateFunction`. Состояния могут быть как простыми (состояние для `AggregateFunctionCount` это всего лишь один человек `UInt64` значение) так и довольно сложными (состояние `AggregateFunctionUniqCombined` представляет собой комбинацию линейного массива, хэш-таблицы и вероятностной структуры данных `HyperLogLog`).
Состояния распределяются в `Arena` (пул памяти) для работы с несколькими состояниями при выполнении запроса `GROUP BY` высокой кардинальности (большим числом уникальных данных). Состояния могут иметь нетривиальный конструктор и деструктор: например, сложные агрегатные состояния могут сами аллоцировать дополнительную память. Потому к созданию и уничтожению состояний, правильной передаче владения и порядку уничтожения следует уделять больше внимание.
Агрегатные состояния могут быть сериализованы и десериализованы для передачи их по сети во время выполнения распределенного запроса или для записи их на диск при дефиците оперативной памяти. Они даже могут храниться в таблице с `DataTypeAggregateFunction`, чтобы позволяет выполнять инкрементное агрегирование данных.
> Формат сериализации данных для состояний агрегатных функций в настоящее время не версионируется. Это нормально, если агрегатные состояния хранятся только временно. Но у нас есть такая возможность `AggregatingMergeTree` механизм таблиц для инкрементной агрегации, и люди уже используют его в эксплуатации. Именно по этой причине требуется помнить об обратная совместимости при изменении формата сериализации для любой агрегатной функции.
## Сервер {#server}
Сервер предоставляет несколько различных интерфейсов.
- HTTP интерфейс для любых сторонних клиентов.
- TCP интерфейс для родного ClickHouse клиента и межсерверной взаимодействия при выполнении распределенных запросов.
- Интерфейс для передачи данных при репликации.
Внутри простой многопоточный сервер без корутин (coroutines), файберов (fibers) и т.д. Поскольку сервер не предназначен для обработки большого количества простых запросов, а ориентирован на обработку сложных запросов относительно низкой интенсивности, каждый из потоков может обрабатывать огромное количество аналитических запросов.
Сервер инициализирует класс `Context`, где хранит необходимое для выполнения запроса окружение: список доступных баз данных, пользователей и прав доступа, настройки, кластеры, список процессов, журнал запросов и т.д. Это окружение используется интерпретаторами.
Мы поддерживаем полную обратную и прямую совместимость для TCP интерфейса: старые клиенты могут общаться с новыми серверами, а новые клиенты могут общаться со старыми серверами. Но мы не хотим поддерживать его вечно и прекращаем поддержку старых версий примерно через год.
!!! note "Note"
Для всех сторонних приложений мы рекомендуем использовать HTTP интерфейс, потому что он прост и удобен в использовании. TCP интерфейс тесно связан с внутренними структурами данных: он использует внутренний формат для передачи блоков данных и использует специальное кадрирование для сжатых данных. Мы не выпустили библиотеку C для этого протокола, потому что потребовала бы линковки большей части кодовой базы ClickHouse, что непрактично.
## Выполнение распределенных запросов (Distributed Query Execution) {#distributed-query-execution}
Сервера в кластере в основном независимы. Вы можете создать `Распределенную` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные - она только предоставляет возможность "просмотра" всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети.
Ситуация усложняется, при использовании подзапросы в случае IN или JOIN, когда каждый из них использует таблицу `Distributed`. Есть разные стратегии для выполнения таких запросов.
Глобального плана выполнения распределенных запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов GROUP BY высокой кардинальности или запросов с большим числом временных данных в JOIN: в таких случаях нам необходимо перераспределить («reshuffle») данные между серверами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим.
## Merge Tree {#merge-tree}
`MergeTree` - это семейство движков хранения, поддерживающих индексацию по первичному ключу. Первичный ключ может быть произвольным набором (кортежем) столбцов или выражений. Данные в таблице `MergeTree` хранятся "частями" (“parts”). Каждая часть хранит данные отсортированные по первичному ключу (данные упорядочены лексикографически). Все столбцы таблицы хранятся в отдельных файлах `column.bin` в этих частях. Файлы состоят из сжатых блоков. Каждый блок обычно содержит от 64 КБ до 1 МБ несжатых данных, в зависимости от среднего значения размера данных. Блоки состоят из значений столбцов, расположенных последовательно один за другим. Значения столбцов находятся в одинаковом порядке для каждого столбца (порядок определяется первичным ключом), поэтому, когда вы выполняете итерацию по многим столбцам, вы получаете значения для соответствующих строк.
Сам первичный ключ является "разреженным" ("sparse"). Он не относится к каждой отдельной строке, а только к некоторым диапазонам данных. Отдельный файл «primary.idx» имеет значение первичного ключа для каждой N-й строки, где N называется гранулярностью индекса ("index_granularity", обычно N = 8192). Также для каждого столбца у нас есть файлы `column.mrk` с "метками" ("marks"), которые обозначают смещение для каждой N-й строки в файле данных. Каждая метка представляет собой пару: смещение начала сжатого блока от начала файла и смещение к началу данных в распакованном блоке. Обычно сжатые блоки выравниваются по меткам, а смещение в распакованном блоке равно нулю. Данные для `primary.idx` всегда находятся в памяти, а данные для файлов `column.mrk` кэшируются.
Когда мы собираемся читать что-то из части данных `MergeTree`, мы смотрим содержимое `primary.idx` и определяем диапазоны, которые могут содержать запрошенные данные, затем просматриваем содержимое `column.mrk` и вычисляем смещение, чтобы начать чтение этих диапазонов. Из-за разреженности могут быть прочитаны лишние данные. ClickHouse не подходит для простых точечных запросов высокой интенсивности, потому что весь диапазон строк размером `index_granularity` должен быть прочитан для каждого ключа, а сжатый блок должен быть полностью распакован для каждого столбца. Мы сделали индекс разреженным, потому что мы должны иметь возможность поддерживать триллионы строк на один сервер без существенных расходов памяти на индексацию. Кроме того, поскольку первичный ключ является разреженным, он не уникален: он не может проверить наличие ключа в таблице во время INSERT. Вы можете иметь множество строк с одним и тем же ключом в таблице.
При выполнении `INSERT` для группы данных в `MergeTree`, элементы группы сортируются по первичному ключу и образует новую “часть”. Фоновые потоки периодически выбирают некоторые части и объединяют их в одну отсортированную часть, чтобы сохранить относительно небольшое количество частей. Вот почему он называется `MergeTree`. Конечно, объединение приводит к повышению интенсивности записи. Все части иммутабельные: они только создаются и удаляются, но не изменяются. Когда выполняется `SELECT`, он содержит снимок таблицы (набор частей). После объединения старые части также сохраняются в течение некоторого времени, чтобы упростить восстановление после сбоя, поэтому, если мы видим, что какая-то объединенная часть, вероятно, повреждена, мы можем заменить ее исходными частями.
`MergeTree` не является деревом LSM (Log-structured merge-tree — журнально-структурированное дерево со слиянием), потому что оно не содержит «memtable» и «log»: вставленные данные записываются непосредственно в файловую систему. Это делает его пригодным только для вставки данных в пакетах, а не по отдельным строкам и не очень часто - примерно раз в секунду это нормально, а тысячу раз в секунду - нет. Мы сделали это для простоты и потому, что мы уже вставляем данные в пакеты в наших приложениях.
> Таблицы `MergeTree` могут иметь только один (первичный) индекс: вторичных индексов нет. Было бы неплохо разрешить несколько физических представлениям в одной логической таблице, например, хранить данные в более чем одном физическом порядке или даже разрешить представления с предварительно агрегированными данными вместе с исходными данными.
Существуют движки `MergeTree`, которые выполняют дополнительную работу во время фоновых слияний. Примерами являются `CollapsingMergeTree` и `AggregatingMergeTree`. Это можно рассматривать как специальную поддержку обновления. Помните, что это не настоящие обновления, поскольку пользователи обычно не контролируют время выполнения фоновых слияний, а данные в таблице `MergeTree` почти всегда хранятся в нескольких частях, а не в полностью объединенной форме.
## Репликация {#replication}
Репликация в ClickHouse может быть настроена для каждой таблицы отдельно. Вы можете иметь несколько реплицированных и несколько не реплицированных таблиц на одном сервере. Вы также можете реплицировать таблицы по-разному, например, одну с двухфакторной репликацией и другую с трехфакторной.
Репликация реализована в движке таблицы `ReplicatedMergeTree`. Путь в `ZooKeeper` указывается в качестве параметра движка. Все таблицы с одинаковым путем в `ZooKeeper` становятся репликами друг друга: они синхронизируют свои данные и поддерживают согласованность. Реплики можно добавлять и удалять динамически, просто создавая или удаляя таблицу.
Репликация использует асинхронную multi-master схему. Вы можете вставить данные в любую реплику, которая имеет открытую сессию в `ZooKeeper`, и данные реплицируются на все другие реплики асинхронно. Поскольку ClickHouse не поддерживает UPDATE, репликация исключает конфликты (conflict-free replication). Поскольку подтверждение вставок кворумом не реализовано, только что вставленные данные могут быть потеряны в случае сбоя одного узла.
Метаданные для репликации хранятся в `ZooKeeper`. Существует журнал репликации, в котором перечислены действия, которые необходимо выполнить. Среди этих действий: получить часть (get the part); объединить части (merge parts); удалить партицию (drop a partition) и так далее. Каждая реплика копирует журнал репликации в свою очередь, а затем выполняет действия из очереди. Например, при вставке в журнале создается действие «получить часть» (get the part), и каждая реплика загружает эту часть. Слияния координируются между репликами, чтобы получить идентичные до байта результаты. Все части объединяются одинаково на всех репликах. Это достигается путем выбора одной реплики в качестве лидера, и эта реплика инициирует слияния и записывает действия «слияния частей» в журнал.
Репликация является физической: между узлами передаются только сжатые части, а не запросы. Слияния обрабатываются на каждой реплике независимо, в большинстве случаев, чтобы снизить затраты на сеть, во избежание усиления роли сети. Крупные объединенные части отправляются по сети только в случае значительной задержки репликации.
Кроме того, каждая реплика сохраняет свое состояние в `ZooKeeper` в виде набора частей и его контрольных сумм. Когда состояние в локальной файловой системе расходится с эталонным состоянием в `ZooKeeper`, реплика восстанавливает свою согласованность путем загрузки отсутствующих и поврежденных частей из других реплик. Когда в локальной файловой системе есть неожиданные или испорченные данные, ClickHouse не удаляет их, а перемещает в отдельный каталог и забывает об этом.
!!! note "Note"
Кластер ClickHouse состоит из независимых шардов, а каждый шард состоит из реплик. Кластер **не является эластичным** (not elastic), поэтому после добавления нового шарда данные не будут автоматически распределены между ними. Вместо этого нужно изменить настройки, чтобы выровнять нагрузку на кластер. Эта реализация дает вам больший контроль, и вполне приемлема для относительно небольших кластеров, таких как десятки узлов. Но для кластеров с сотнями узлов, которые мы используем в эксплуатации, такой подход становится существенным недостатком. Движки таблиц, которые охватывают весь кластер с динамически реплицируемыми областями, которые могут быть автоматически разделены и сбалансированы между кластерами, еще предстоит реализовать.
{## [Original article](https://clickhouse.tech/docs/ru/development/architecture/) ##}

View File

@ -53,7 +53,7 @@
- [Distributed](special/distributed.md#distributed)
- [MaterializedView](special/materializedview.md#materializedview)
- [Dictionary](special/dictionary.md#dictionary)
- [Merge](special/merge.md#merge
- [Merge](special/merge.md#merge)
- [File](special/file.md#file)
- [Null](special/null.md#null)
- [Set](special/set.md#set)

View File

@ -1,12 +1,28 @@
# Distributed {#distributed}
**Движок Distributed не хранит данные самостоятельно**, а позволяет обрабатывать запросы распределённо, на нескольких серверах.
Чтение автоматически распараллеливается. При чтении будут использованы индексы таблиц на удалённых серверах, если есть.
Движок Distributed принимает параметры: имя кластера в конфигурационном файле сервера, имя удалённой базы данных, имя удалённой таблицы, а также (не обязательно) ключ шардирования.
**Движок Distributed не хранит данные самостоятельно**, а позволяет обрабатывать запросы распределённо, на нескольких серверах. Чтение автоматически распараллеливается. При чтении будут использованы индексы таблиц на удалённых серверах, если есть.
Движок Distributed принимает параметры:
- имя кластера в конфигурационном файле сервера
- имя удалённой базы данных
- имя удалённой таблицы
- (не обязательно) ключ шардирования.
- (не обязательно) имя политики, оно будет использоваться для хранения временных файлов для асинхронной отправки
Смотрите также:
- настройка `insert_distributed_sync`
- [MergeTree](../mergetree-family/mergetree.md#table_engine-mergetree-multiple-volumes) для примера
Пример:
``` sql
Distributed(logs, default, hits[, sharding_key])
Distributed(logs, default, hits[, sharding_key[, policy_name]])
```
данные будут читаться со всех серверов кластера logs, из таблицы default.hits, расположенной на каждом сервере кластера.

View File

@ -945,6 +945,55 @@ message MessageType {
ClickHouse пишет и читает сообщения `Protocol Buffers` в формате `length-delimited`. Это означает, что перед каждым сообщением пишется его длина
в формате [varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). См. также [как читать и записывать сообщения Protocol Buffers в формате length-delimited в различных языках программирования](https://cwiki.apache.org/confluence/display/GEODE/Delimiting+Protobuf+Messages).
## Avro {#data-format-avro}
## AvroConfluent {#data-format-avro-confluent}
Для формата `AvroConfluent` ClickHouse поддерживает декодирование сообщений `Avro` с одним объектом. Такие сообщения используются с [Kafka] (http://kafka.apache.org/) и реестром схем [Confluent](https://docs.confluent.io/current/schema-registry/index.html).
Каждое сообщение `Avro` содержит идентификатор схемы, который может быть разрешен для фактической схемы с помощью реестра схем.
Схемы кэшируются после разрешения.
URL-адрес реестра схем настраивается с помощью [format\_avro\_schema\_registry\_url](../operations/settings/settings.md#format_avro_schema_registry_url).
### Соответствие типов данных {#sootvetstvie-tipov-dannykh-0}
Такое же, как в [Avro](#data-format-avro).
### Использование {#ispolzovanie}
Чтобы быстро проверить разрешение схемы, используйте [kafkacat](https://github.com/edenhill/kafkacat) с языком запросов [clickhouse-local](../operations/utilities/clickhouse-local.md):
``` bash
$ kafkacat -b kafka-broker -C -t topic1 -o beginning -f '%s' -c 3 | clickhouse-local --input-format AvroConfluent --format_avro_schema_registry_url 'http://schema-registry' -S "field1 Int64, field2 String" -q 'select * from table'
1 a
2 b
3 c
```
Чтобы использовать `AvroConfluent` с [Kafka](../engines/table-engines/integrations/kafka.md):
``` sql
CREATE TABLE topic1_stream
(
field1 String,
field2 String
)
ENGINE = Kafka()
SETTINGS
kafka_broker_list = 'kafka-broker',
kafka_topic_list = 'topic1',
kafka_group_name = 'group1',
kafka_format = 'AvroConfluent';
SET format_avro_schema_registry_url = 'http://schema-registry';
SELECT * FROM topic1_stream;
```
!!! note "Внимание"
`format_avro_schema_registry_url` необходимо настроить в `users.xml`, чтобы сохранить значение после перезапуска. Также можно использовать настройку `format_avro_schema_registry_url` табличного движка `Kafka`.
## Parquet {#data-format-parquet}
[Apache Parquet](http://parquet.apache.org/) — формат поколоночного хранения данных, который распространён в экосистеме Hadoop. Для формата `Parquet` ClickHouse поддерживает операции чтения и записи.

View File

@ -31,7 +31,7 @@ ClickHouse поддерживает управление доступом на
Если вы начали пользоваться ClickHouse недавно, попробуйте следующий сценарий:
1. [Включите](#enabling-access-control) SQL-ориентированное управление доступом для пользователя `default`.
2. Войдите под пользователем `default` и создайте всех необходимых пользователей. Не забудьте создать аккаунт администратора (`GRANT ALL ON *.* WITH GRANT OPTION TO admin_user_account`).
2. Войдите под пользователем `default` и создайте всех необходимых пользователей. Не забудьте создать аккаунт администратора (`GRANT ALL ON *.* TO admin_user_account WITH GRANT OPTION`).
3. [Ограничьте разрешения](settings/permissions-for-queries.md#permissions_for_queries) для пользователя `default` и отключите для него SQL-ориентированное управление доступом.
### Особенности реализации {#access-control-properties}

View File

@ -372,6 +372,25 @@ ClickHouse проверит условия `min_part_size` и `min_part_size_rat
<max_concurrent_queries>100</max_concurrent_queries>
```
## max_server_memory_usage {#max_server_memory_usage}
Ограничивает объём оперативной памяти, используемой сервером ClickHouse. Настройка может быть задана только для профиля `default`.
Возможные значения:
- Положительное целое число.
- 0 — объём используемой памяти не ограничен.
Значение по умолчанию: `0`.
**Дополнительная информация**
На серверах с небольшим объёмом RAM и файла подкачки может потребоваться настройка `max_server_memory_usage_to_ram_ratio > 1`.
**См. также**
- [max_memory_usage](../settings/query-complexity.md#settings_max_memory_usage)
## max\_connections {#max-connections}
Максимальное количество входящих соединений.

View File

@ -1,4 +1,4 @@
# Ограничения на сложность запроса {#ogranicheniia-na-slozhnost-zaprosa}
# Ограничения на сложность запроса {#restrictions-on-query-complexity}
Ограничения на сложность запроса - часть настроек.
Используются, чтобы обеспечить более безопасное исполнение запросов из пользовательского интерфейса.
@ -32,7 +32,7 @@
Потребление памяти не полностью учитывается для состояний агрегатных функций `min`, `max`, `any`, `anyLast`, `argMin`, `argMax` от аргументов `String` и `Array`.
Потребление памяти ограничивается также параметрами `max_memory_usage_for_user` и `max_memory_usage_for_all_queries`.
Потребление памяти ограничивается также параметрами `max_memory_usage_for_user` и [max_server_memory_usage](../server-configuration-parameters/settings.md#max_server_memory_usage).
## max\_memory\_usage\_for\_user {#max-memory-usage-for-user}
@ -42,18 +42,9 @@
Смотрите также описание настройки [max\_memory\_usage](#settings_max_memory_usage).
## max\_memory\_usage\_for\_all\_queries {#max-memory-usage-for-all-queries}
Максимальный возможный объём оперативной памяти для всех запросов на одном сервере.
Значения по умолчанию определены в файле [Settings.h](https://github.com/ClickHouse/ClickHouse/blob/master/src/Core/Settings.h#L289). По умолчанию размер не ограничен (`max_memory_usage_for_all_queries = 0`).
Смотрите также описание настройки [max\_memory\_usage](#settings_max_memory_usage).
## max\_rows\_to\_read {#max-rows-to-read}
Следующие ограничения могут проверяться на каждый блок (а не на каждую строку). То есть, ограничения могут быть немного нарушены.
При выполнении запроса в несколько потоков, следующие ограничения действуют в каждом потоке по отдельности.
Максимальное количество строчек, которое можно прочитать из таблицы при выполнении запроса.

View File

@ -644,6 +644,17 @@ log_query_threads=1
Значение по умолчанию: 256 Кб.
## max\_parser\_depth {#max_parser_depth}
Ограничивает максимальную глубину рекурсии в парсере рекурсивного спуска. Позволяет контролировать размер стека.
Возможные значения:
- Положительное целое число.
- 0 — Глубина рекурсии не ограничена.
Значение по умолчанию: 1000.
## interactive\_delay {#interactive-delay}
Интервал в микросекундах для проверки, не запрошена ли остановка выполнения запроса, и отправки прогресса.
@ -1205,6 +1216,23 @@ Default value: 0.
Значение по умолчанию: 16.
## always_fetch_merged_part {#always_fetch_merged_part}
Запрещает слияние данных для таблиц семейства [Replicated*MergeTree](../../engines/table-engines/mergetree-family/replication.md).
Если слияние запрещено, реплика никогда не выполняет слияние отдельных кусков данных, а всегда загружает объединённые данные из других реплик. Если объединённых данных пока нет, реплика ждет их появления. Нагрузка на процессор и диски на реплике уменьшается, но нагрузка на сеть в кластере возрастает. Настройка может быть полезна на репликах с относительно слабыми процессорами или медленными дисками, например, на репликах для хранения архивных данных.
Возможные значения:
- 0 — таблицы семейства `Replicated*MergeTree` выполняют слияние данных на реплике.
- 1 — таблицы семейства `Replicated*MergeTree` не выполняют слияние данных на реплике, а загружают объединённые данные из других реплик.
Значение по умолчанию: 0.
**См. также:**
- [Репликация данных](../../engines/table-engines/mergetree-family/replication.md)
## transform_null_in {#transform_null_in}
Разрешает сравнивать значения [NULL](../../sql-reference/syntax.md#null-literal) в операторе [IN](../../sql-reference/operators/in.md).
@ -1313,6 +1341,12 @@ SELECT idx, i FROM null_in WHERE i IN (1, NULL) SETTINGS transform_null_in = 1;
Значение по умолчанию: 16.
## format\_avro\_schema\_registry\_url {#format_avro_schema_registry_url}
Задает URL реестра схем [Confluent](https://docs.confluent.io/current/schema-registry/index.html) для использования с форматом [AvroConfluent](../../interfaces/formats.md#data-format-avro-confluent).
Значение по умолчанию: `Пустая строка`.
## min_insert_block_size_rows_for_materialized_views {#min-insert-block-size-rows-for-materialized-views}
Устанавливает минимальное количество строк в блоке, который может быть вставлен в таблицу запросом `INSERT`. Блоки меньшего размера склеиваются в блоки большего размера. Настройка применяется только для блоков, вставляемых в [материализованное представление](../../sql-reference/statements/create.md#create-view). Настройка позволяет избежать избыточного потребления памяти.

View File

@ -1122,6 +1122,18 @@ WHERE name in ('Kafka', 'MergeTree', 'ReplicatedCollapsingMergeTree')
- `sorting_key` (String) — ключ сортировки таблицы.
- `primary_key` (String) - первичный ключ таблицы.
- `sampling_key` (String) — ключ сэмплирования таблицы.
- `storage_policy` (String) - политика хранения данных:
- [MergeTree](../engines/table-engines/mergetree-family/mergetree.md#table_engine-mergetree-multiple-volumes)
- [Distributed](../engines/table-engines/special/distributed.md#distributed)
- `total_rows` (Nullable(UInt64)) - Общее количество строк, если есть возможность быстро определить точное количество строк в таблице, в противном случае `Null` (включая базовую таблицу `Buffer`).
- `total_bytes` (Nullable(UInt64)) - Общее количество байт, если можно быстро определить точное количество байт для таблицы на накопителе, в противном случае `Null` (**не включает** в себя никакого базового хранилища).
- Если таблица хранит данные на диске, возвращает используемое пространство на диске (т. е. сжатое).
- Если таблица хранит данные в памяти, возвращает приблизительное количество используемых байт в памяти.
Таблица `system.tables` используется при выполнении запроса `SHOW TABLES`.

View File

@ -29,7 +29,7 @@
- Движок таблиц [AggregatingMergeTree](../../engines/table-engines/mergetree-family/aggregatingmergetree.md).
- Функция [finalizeAggregation](../../sql-reference/aggregate-functions/combinators.md#function-finalizeaggregation).
- Функция [runningAccumulate](../../sql-reference/aggregate-functions/combinators.md#function-runningaccumulate).
- Функция [runningAccumulate](../../sql-reference/aggregate-functions/combinators.md#runningaccumulate).
- Комбинатор [-Merge](#aggregate_functions_combinators-merge).
- Комбинатор [-MergeState](#aggregate_functions_combinators-mergestate).

View File

@ -798,16 +798,33 @@ SELECT
└──────────────┴───────────┘
```
## arrayReduce(agg\_func, arr1, …) {#array-functions-arrayreduce}
## arrayReduce (#arrayreduce}
Применяет агрегатную функцию к элементам массива и возвращает ее результат. Имя агрегирующей функции передается как строка в одинарных кавычках `'max'`, `'sum'`. При использовании параметрических агрегатных функций, параметр указывается после имени функции в круглых скобках `'uniqUpTo(6)'`.
Пример:
**Синтаксис**
```sql
arrayReduce(agg_func, arr1, arr2, ..., arrN)
```
**Параметры**
- `agg_func` — Имя агрегатной функции, которая должна быть константой [string](../../sql-reference/data-types/string.md).
- `arr` — Любое количество столбцов типа [array](../../sql-reference/data-types/array.md) в качестве параметров агрегатной функции.
**Возвращаемое значение**
**Пример**
Запрос:
```sql
SELECT arrayReduce('max', [1, 2, 3])
```
Ответ:
```text
┌─arrayReduce('max', [1, 2, 3])─┐
│ 3 │
@ -816,12 +833,16 @@ SELECT arrayReduce('max', [1, 2, 3])
Если агрегатная функция имеет несколько аргументов, то эту функцию можно применять к нескольким массивам одинакового размера.
Пример:
**Пример**
Запрос:
```sql
SELECT arrayReduce('maxIf', [3, 5], [1, 0])
```
Ответ:
```text
┌─arrayReduce('maxIf', [3, 5], [1, 0])─┐
│ 3 │
@ -830,16 +851,62 @@ SELECT arrayReduce('maxIf', [3, 5], [1, 0])
Пример с параметрической агрегатной функцией:
Запрос:
```sql
SELECT arrayReduce('uniqUpTo(3)', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
```
Ответ:
```text
┌─arrayReduce('uniqUpTo(3)', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])─┐
│ 4 │
└─────────────────────────────────────────────────────────────┘
```
## arrayReduceInRanges {#arrayreduceinranges}
Применяет агрегатную функцию к элементам массива в заданных диапазонах и возвращает массив, содержащий результат, соответствующий каждому диапазону. Функция вернет тот же результат, что и несколько `arrayReduce(agg_func, arraySlice(arr1, index, length), ...)`.
**Синтаксис**
```sql
arrayReduceInRanges(agg_func, ranges, arr1, arr2, ..., arrN)
```
**Параметры**
- `agg_func` — Имя агрегатной функции, которая должна быть [строковой](../../sql-reference/data-types/string.md) константой.
- `ranges` — Диапазоны для агрегирования, которые должны быть [массивом](../../sql-reference/data-types/array.md) of [кортежей](../../sql-reference/data-types/tuple.md) который содержит индекс и длину каждого диапазона.
- `arr` — Любое количество столбцов типа [Array](../../sql-reference/data-types/array.md) в качестве параметров агрегатной функции.
**Возвращаемое значение**
- Массив, содержащий результаты агрегатной функции для указанных диапазонов.
Тип: [Array](../../sql-reference/data-types/array.md).
**Пример**
Запрос:
```sql
SELECT arrayReduceInRanges(
'sum',
[(1, 5), (2, 3), (3, 4), (4, 4)],
[1000000, 200000, 30000, 4000, 500, 60, 7]
) AS res
```
Ответ:
```text
┌─res─────────────────────────┐
│ [1234500,234000,34560,4567] │
└─────────────────────────────┘
```
## arrayReverse(arr) {#arrayreverse}
Возвращает массив того же размера, что и исходный массив, содержащий элементы в обратном порядке.
@ -881,7 +948,7 @@ flatten(array_of_arrays)
**Параметры**
- `array_of_arrays` — [Массивов](../../sql-reference/functions/array-functions.md) массивов. Например, `[[1,2,3], [4,5]]`.
- `array_of_arrays` — [Массив](../../sql-reference/functions/array-functions.md) массивов. Например, `[[1,2,3], [4,5]]`.
**Примеры**

View File

@ -1,4 +1,4 @@
# Прочие функции {#prochie-funktsii}
# Прочие функции {#other-functions}
## hostName() {#hostname}
@ -757,6 +757,38 @@ SELECT getSizeOfEnumType( CAST('a' AS Enum8('a' = 1, 'b' = 2) ) ) AS x
└───┘
```
## blockSerializedSize {#blockserializedsize}
Возвращает размер на диске (без учета сжатия).
``` sql
blockSerializedSize(value[, value[, ...]])
```
**Параметры**
- `value` — Значение произвольного типа.
**Возвращаемые значения**
- Количество байтов, которые будут записаны на диск для блока значений (без сжатия).
**Пример**
Запрос:
``` sql
SELECT blockSerializedSize(maxState(1)) as x
```
Ответ:
``` text
┌─x─┐
│ 2 │
└───┘
```
## toColumnTypeName {#tocolumntypename}
Возвращает имя класса, которым представлен тип данных столбца в оперативной памяти.
@ -1004,9 +1036,110 @@ SELECT formatReadableSize(filesystemCapacity()) AS "Capacity", toTypeName(filesy
Принимает состояние агрегатной функции. Возвращает результат агрегирования.
## runningAccumulate {#function-runningaccumulate}
## runningAccumulate {#runningaccumulate}
Принимает на вход состояния агрегатной функции и возвращает столбец со значениями, которые представляют собой результат мёржа этих состояний для выборки строк из блока от первой до текущей строки. Например, принимает состояние агрегатной функции (например, `runningAccumulate(uniqState(UserID))`), и для каждой строки блока возвращает результат агрегатной функции после мёржа состояний функции для всех предыдущих строк и текущей. Таким образом, результат зависит от разбиения данных по блокам и от порядка данных в блоке.
Накапливает состояния агрегатной функции для каждой строки блока данных.
!!! warning "Warning"
Функция обнуляет состояние для каждого нового блока.
**Синтаксис**
```sql
runningAccumulate(agg_state[, grouping]);
```
**Параметры**
- `agg_state` — Состояние агрегатной функции. [AggregateFunction](../../sql-reference/data-types/aggregatefunction.md#data-type-aggregatefunction).
- `grouping` — Ключ группировки. Опциональный параметр. Состояние функции обнуляется, если значение `grouping` меняется. Параметр может быть любого [поддерживаемого типа данных](../../sql-reference/data-types/index.md), для которого определен оператор равенства.
**Возвращаемое значение**
- Каждая результирующая строка содержит результат агрегатной функции, накопленный для всех входных строк от 0 до текущей позиции. `runningAccumulate` обнуляет состояния для каждого нового блока данных или при изменении значения `grouping`.
Тип зависит от используемой агрегатной функции.
**Примеры**
Рассмотрим примеры использования `runningAccumulate` для нахождения кумулятивной суммы чисел без и с группировкой.
Запрос:
```sql
SELECT k, runningAccumulate(sum_k) AS res FROM (SELECT number as k, sumState(k) AS sum_k FROM numbers(10) GROUP BY k ORDER BY k);
```
Результат:
```text
┌─k─┬─res─┐
│ 0 │ 0 │
│ 1 │ 1 │
│ 2 │ 3 │
│ 3 │ 6 │
│ 4 │ 10 │
│ 5 │ 15 │
│ 6 │ 21 │
│ 7 │ 28 │
│ 8 │ 36 │
│ 9 │ 45 │
└───┴─────┘
```
Подзапрос формирует `sumState` для каждого числа от `0` до `9`. `sumState` возвращает состояние функции [sum](../../sql-reference/aggregate-functions/reference.md#agg_function-sum), содержащее сумму одного числа.
Весь запрос делает следующее:
1. Для первой строки `runningAccumulate` берет `sumState(0)` и возвращает `0`.
2. Для второй строки функция объединяет `sumState (0)` и `sumState (1)`, что приводит к `sumState (0 + 1)`, и возвращает в результате `1`.
3. Для третьей строки функция объединяет `sumState (0 + 1)` и `sumState (2)`, что приводит к `sumState (0 + 1 + 2)`, и в результате возвращает `3`.
4. Действия повторяются до тех пор, пока не закончится блок.
В следующем примере показано использование параметра `grouping`:
Запрос:
```sql
SELECT
grouping,
item,
runningAccumulate(state, grouping) AS res
FROM
(
SELECT
toInt8(number / 4) AS grouping,
number AS item,
sumState(number) AS state
FROM numbers(15)
GROUP BY item
ORDER BY item ASC
);
```
Результат:
```text
┌─grouping─┬─item─┬─res─┐
│ 0 │ 0 │ 0 │
│ 0 │ 1 │ 1 │
│ 0 │ 2 │ 3 │
│ 0 │ 3 │ 6 │
│ 1 │ 4 │ 4 │
│ 1 │ 5 │ 9 │
│ 1 │ 6 │ 15 │
│ 1 │ 7 │ 22 │
│ 2 │ 8 │ 8 │
│ 2 │ 9 │ 17 │
│ 2 │ 10 │ 27 │
│ 2 │ 11 │ 38 │
│ 3 │ 12 │ 12 │
│ 3 │ 13 │ 25 │
│ 3 │ 14 │ 39 │
└──────────┴──────┴─────┘
```
Как вы можете видеть, `runningAccumulate` объединяет состояния для каждой группы строк отдельно.
## joinGet {#joinget}

View File

@ -38,7 +38,7 @@ $ watch -n1 "clickhouse-client --query='SHOW PROCESSLIST'"
Выводит список таблиц.
``` sql
SHOW [TEMPORARY] TABLES [FROM <db>] [LIKE '<pattern>'] [LIMIT <N>] [INTO OUTFILE <filename>] [FORMAT <format>]
SHOW [TEMPORARY] TABLES [{FROM | IN} <db>] [LIKE '<pattern>' | WHERE expr] [LIMIT <N>] [INTO OUTFILE <filename>] [FORMAT <format>]
```
Если секция `FROM` не используется, то запрос возвращает список таблиц из текущей базы данных.

View File

@ -29,11 +29,12 @@ Upd. Сделана крупная часть задачи, но ориенти
Upd. Pull request готов для мержа.
Upd. Попало 20.4. Доступно под флагом allow_experimental_database_atomic.
### 1.3. Неблокирующие ALTER {#neblokiruiushchie-alter}
### 1.3. + Неблокирующие ALTER {#neblokiruiushchie-alter}
Q1. И полностью immutable куски. Делает [Александр Сапин](https://github.com/alesapin). Готов приступить к задаче в конце ноября 2019. Нужно для Яндекс.Метрики.
Upd. Большая часть задачи реализована и добавлена в master. Есть незначительные технические долги. Остаётся реализация неблокирующего изменения метаданных таблицы.
Upd. Всё доделано, ожидается в релизе 20.6.
### 1.4. + Нетранзитивные ALTER столбцов {#netranzitivnye-alter-stolbtsov}
@ -65,6 +66,7 @@ Upd. Включено для системных таблиц.
Требует 1.6. Антон Попов. Задача взята в работу. Q2.
Есть pull request.
Upd. В стадии код-ревью.
### 1.8. + Перенос между разделами по TTL {#perenos-mezhdu-razdelami-po-ttl}
@ -104,6 +106,8 @@ Upd. Есть pull request. Upd. Сделано.
Предлагается добавить в ClickHouse настройки по пережатию данных и фоновые потоки, выполняющие эту задачу.
Upd. Представлен прототип неизвестной степени готовности.
### 1.11. + Виртуальная файловая система {#virtualnaia-failovaia-sistema}
На VFS переведены Log, TinyLog, StripeLog, а также MergeTree, что доказывает состоятельность реализации.
@ -116,7 +120,7 @@ ClickHouse использует небольшое подмножество фу
### 1.12. Экспериментальная реализация VFS поверх S3 и HDFS {#eksperimentalnaia-realizatsiia-vfs-poverkh-s3-i-hdfs}
Q2.
Q4.
Нужно для Яндекс.Облака. Требует 1.11. Желательно 1.6 и 1.18.
Делает Александр, Яндекс.Облако (сначала часть для S3), а также Олег Ершов, ВШЭ и Яндекс.
@ -131,20 +135,23 @@ Upd: PR [#10463](https://github.com/ClickHouse/ClickHouse/pull/10463)
### 1.14. Не писать столбцы, полностью состоящие из нулей {#ne-pisat-stolbtsy-polnostiu-sostoiashchie-iz-nulei}
Антон Попов. Q2.
Антон Попов. Q3.
В очереди. Простая задача, является небольшим пререквизитом для потенциальной поддержки полуструктурированных данных.
### 1.15. Возможность иметь разный первичный ключ в разных кусках {#vozmozhnost-imet-raznyi-pervichnyi-kliuch-v-raznykh-kuskakh}
Сложная задача, только после 1.3.
Upd. В обсуждении.
### 1.16. Несколько физических представлений для одного куска данных {#neskolko-fizicheskikh-predstavlenii-dlia-odnogo-kuska-dannykh}
Сложная задача, только после 1.3 и 1.6. Позволяет компенсировать 21.20.
Upd. В обсуждении.
### 1.17. Несколько сортировок для одной таблицы {#neskolko-sortirovok-dlia-odnoi-tablitsy}
Сложная задача, только после 1.3 и 1.6.
Upd. В обсуждении.
### 1.18. Отдельное хранение файлов кусков {#otdelnoe-khranenie-failov-kuskov}
@ -155,7 +162,7 @@ Upd: PR [#10463](https://github.com/ClickHouse/ClickHouse/pull/10463)
Для обоснования необходимости смотрите ссылки в описании других задач.
### 2.1. Переделка конвейера выполнения запросов на Processors {#peredelka-konveiera-vypolneniia-zaprosov-na-processors}
### 2.1. + Переделка конвейера выполнения запросов на Processors {#peredelka-konveiera-vypolneniia-zaprosov-na-processors}
Делает [Николай Кочетов](https://github.com/KochetovNicolai). Финальная стадия разработки. Включение по-умолчанию в конце декабря 2019. Удаление старого кода в начале 2020.
@ -168,6 +175,8 @@ Upd. Уже есть первый релиз, в котором это вклю
Upd. Всё ещё ждём удаление старого кода, которое должно случиться после релиза 20.4.
Upd. Старый код по большей части удалён.
### 2.2. Инфраструктура событий/метрик/ограничений/квот/трассировки {#infrastruktura-sobytiimetrikogranicheniikvottrassirovki}
В очереди. https://gist.github.com/alexey-milovidov/d62d73222d83b9319dc519cbb13aeff6
@ -196,6 +205,7 @@ Upd. Всё ещё ждём удаление старого кода, котор
Upd. Каталог БД вынесен из Context.
Upd. SharedContext вынесен из Context.
Upd. Проблема нейтрализована и перестала быть актуальной.
### 2.8. Декларативный парсер запросов {#deklarativnyi-parser-zaprosov}
@ -298,6 +308,7 @@ Upd. Сейчас обсуждается, как сделать другую з
### 4.8. Разделить background pool для fetch и merge {#razdelit-background-pool-dlia-fetch-i-merge}
В очереди. Исправить проблему, что восстанавливающаяся реплика перестаёт мержить. Частично компенсируется 4.3.
Александр Казаков.
## 5. Операции {#operatsii}
@ -361,7 +372,7 @@ Upd. Появилась вторая версия LTS - 20.3.
Сейчас есть стек трейс для почти всех, но не всех исключений. Требует 7.4.
### 6.7. + Таблица system.stack\_trace {#tablitsa-system-stack-trace}
### 6.7. + Таблица system.stack_trace {#tablitsa-system-stack-trace}
Сравнительно простая задача, но только для опытных разработчиков.
@ -369,7 +380,9 @@ Upd. Появилась вторая версия LTS - 20.3.
Сравнительно простая задача, но только для опытных разработчиков.
### 6.9. Отправлять информацию клиенту, если сервер падает по сигналу {#otpravliat-informatsiiu-klientu-esli-server-padaet-po-signalu}
### 6.9. + Отправлять информацию клиенту, если сервер падает по сигналу {#otpravliat-informatsiiu-klientu-esli-server-padaet-po-signalu}
Сделано.
### 6.10. Сбор общих системных метрик {#sbor-obshchikh-sistemnykh-metrik}
@ -447,11 +460,12 @@ UBSan включен в функциональных тестах, но не в
Есть технический долг с лицензиями файлов консорциума Unicode.
Есть технический долг с работой \G в multiline режиме.
### 7.14.1. Улучшение возможностей интерактивного режима clickhouse-client {#uluchshenie-vozmozhnostei-interaktivnogo-rezhima-clickhouse-client}
### 7.14.1. + Улучшение возможностей интерактивного режима clickhouse-client {#uluchshenie-vozmozhnostei-interaktivnogo-rezhima-clickhouse-client}
Тагир Кускаров, ВШЭ.
Upd. В рамках данной задачи добавляем подстветку синтаксиса и исправление проблем со вставкой больших запросов.
Upd. Минимальная подсветка добавлена, а все остальные задачи не сделаны.
Для ввода запросов в интерактивном режиме в клиенте командной строки clickhouse-client использовалась библиотека readline или libedit.
@ -502,7 +516,8 @@ Upd. Сделано SSL. Ориентируемся в Q1, но приорите
### 7.18.1. Поместить ссылку на собранные бинарники под Mac на сайт {#pomestit-ssylku-na-sobrannye-binarniki-pod-mac-na-sait}
Сейчас людям приходится делать несколько кликов, чтобы их скачать.
[Иван Лежанкин](https://github.com/abyss7) или [Александр Сапин](https://github.com/alesapin).
[Александр Сапин](https://github.com/alesapin).
Upd. Добавлены прямые ссылки и инструкция в документации. Но всё ещё нет инструкции на главной странице сайта.
### 7.19. + Доделать (проверить) автосборку под AArch64 {#dodelat-proverit-avtosborku-pod-aarch64}
@ -522,12 +537,14 @@ Upd. Есть сборки, [пример](https://clickhouse-builds.s3.yandex.n
[Иван Лежанкин](https://github.com/abyss7).
Как-то медленно тащится.
Как-то вообще не тащится. Также договорились, что сделаем ещё автосборку для MIPS64.
### 7.22. Дэшборд для pull requests {#deshbord-dlia-pull-requests}
### 7.22. + Дэшборд для pull requests {#deshbord-dlia-pull-requests}
Дарья Петрова, УрФУ.
Рабочий прототип: https://pulls-dashboard-demo.herokuapp.com/dashboard/ClickHouse/ClickHouse
Upd. Мы пользуемся этим инструментом в ежедневной работе.
Над ClickHouse одновременно работает большое количество разработчиков, которые оформляют свои изменения в виде pull requests. Когда непомерженных pull requests много, то возникает сложность с организацией работы - непонятно, на какой pull request смотреть в первую очередь.
@ -551,7 +568,7 @@ Upd. Есть сборки, [пример](https://clickhouse-builds.s3.yandex.n
Похожие продукты уже есть, например: http://prs.mozilla.io/yandex:ClickHouse К сожалению, этот продукт заброшен, да и делает не совсем то, что нужно. По своему усмотрению, можно взять из него что-нибудь полезное.
### 7.23. Функции для fuzzing {#funktsii-dlia-fuzzing}
### 7.23. + Функции для fuzzing {#funktsii-dlia-fuzzing}
Андрей Некрашевич, ВШЭ.
@ -577,11 +594,10 @@ Upd. Сергей Штыков сделал функцию `randomPrintableASCII
Upd. Илья Яцишин сделал табличную функцию `generateRandom`.
Upd. Эльдар Заитов добавляет OSS Fuzz.
Upd. Сделаны randomString, randomFixedString.
Upd. Сделаны fuzzBits, fuzzBytes.
### 7.24. Fuzzing лексера и парсера запросов; кодеков и форматов {#fuzzing-leksera-i-parsera-zaprosov-kodekov-i-formatov}
Андрей Некрашевич, ВШЭ.
Продолжение 7.23.
1. Использование AFL или LibFuzzer для тестирования отдельных частей кодовой базы ClickHouse.
@ -603,6 +619,7 @@ Upd: Все патчи Максима отправлены в master. Задач
Upd: Задача в процессе реализации. Синхронизироваться будет master. Делает [Иван Лежанкин](https://github.com/abyss7)
Upd: Есть собирающийся прототип, но сборка как будто ещё не в trunk Аркадии.
Upd: Добавлено в Аркадию, но не все файлы (не побайтово).
Upd: Добавлены все файлы побайтово.
### 7.26. + Побайтовая идентичность репозитория с Аркадией {#pobaitovaia-identichnost-repozitoriia-s-arkadiei}
@ -612,6 +629,7 @@ Upd. Готово (все директории кроме contrib).
### 7.27. Запуск автотестов в Аркадии {#zapusk-avtotestov-v-arkadii}
Требует 7.26. Коллеги начали делать, есть результат.
Upd. В Аркадии частично работает небольшая часть тестов.
### 7.29. Опции clickhouse install, stop, start вместо postinst, init.d, systemd скриптов {#optsii-clickhouse-install-stop-start-vmesto-postinst-init-d-systemd-skriptov}
@ -644,7 +662,7 @@ Upd. Готово (все директории кроме contrib).
### 7.34. Бэкпортировать bugfix автоматически {#bekportirovat-bugfix-avtomaticheski}
В очереди. Иван Лежанкин.
Отсутствует прогресс.
Присутствует прогресс.
### 7.35. Начальные правила для авто-merge {#nachalnye-pravila-dlia-avto-merge}
@ -656,6 +674,7 @@ Upd. Готово (все директории кроме contrib).
Контрибьюторы, у которых есть 5 померженных PR. Для их новых PR автотесты запускаются сразу.
[Александр Сапин](https://github.com/alesapin). Может делегировать эту задачу кому угодно.
Сейчас добавляем некоторых доверенных контрибьюторов в ручном режиме.
Upd. Всё ещё добавляем в ручном режиме.
### 7.37. Разобраться с repo.yandex.ru {#razobratsia-s-repo-yandex-ru}
@ -664,6 +683,8 @@ Upd. Готово (все директории кроме contrib).
Upd. Иван Блинков настроил CDN repo.clickhouse.tech, что решает проблему с доступностью зарубежом.
Вопрос с operations, visibility пока актуален.
Upd. Частично решён вопрос с visibility - есть какой-то дэшборд.
## 8. Интеграция с внешними системами {#integratsiia-s-vneshnimi-sistemami}
@ -716,9 +737,12 @@ Upd. Задача взята в работу.
Артемий Бобровский, ВШЭ
Есть pull request.
Upd. В стадии код-ревью.
### 8.12. Пропуск столбцов в форматах Parquet, ORC {#propusk-stolbtsov-v-formatakh-parquet-orc}
Реализовано возможно частично - проверить.
### 8.13. Поддержка массивов в Parquet, ORC {#podderzhka-massivov-v-parquet-orc}
### 8.14. Запись данных в ORC {#zapis-dannykh-v-orc}
@ -747,6 +771,7 @@ Andrew Onyshchuk. Есть pull request. Q1. Сделано.
Задача взята в работу.
Upd. Почти готово - есть лишь небольшой технический долг.
Upd. Готово.
### 8.16.4. + Формат Regexp {#format-regexp}
@ -755,8 +780,7 @@ Upd. Почти готово - есть лишь небольшой технич
### 8.17. ClickHouse как MySQL реплика {#clickhouse-kak-mysql-replika}
Ильяс Адюгамов, ВШЭ.
Upd. Задачу внезапно почти сделал другой человек.
Задачу делает BohuTANG.
Реализовать возможность подписаться на row-based репликацию MySQL и сохранять полученные данные в CollapsingMergeTree или ReplacingMergeTree таблицы. Сторонние решения для этой задачи уже существуют: https://www.altinity.com/blog/2018/6/30/realtime-mysql-clickhouse-replication-in-practice Также существует стороннее решение для PostgreSQL: https://github.com/mkabilov/pg2ch
@ -775,6 +799,7 @@ Maxim Fedotov, Wargaming + Yuri Baranov, Яндекс.
Следующей по востребованности является система очередей RabbitMQ. Её поддержка в ClickHouse отсутствует.
Есть pull request в процессе разработки.
Upd. В процессе code review.
### 8.20. Интеграция с SQS {#integratsiia-s-sqs}
@ -787,11 +812,12 @@ Maxim Fedotov, Wargaming + Yuri Baranov, Яндекс.
Максимальный вариант, вроде, никому не нужен.
Upd. Всё ещё кажется, что задача не нужна.
### 8.22. Поддержка синтаксиса для переменных в стиле MySQL {#podderzhka-sintaksisa-dlia-peremennykh-v-stile-mysql}
### 8.22. + Поддержка синтаксиса для переменных в стиле MySQL {#podderzhka-sintaksisa-dlia-peremennykh-v-stile-mysql}
При парсинге запроса преобразовывать синтаксис вида `@@version_full` в вызов функции `getGlobalVariable('version_full')`. Поддержать популярные MySQL переменные. Может быть поможет Юрий Баранов, если будет энтузиазм.
Upd. Юрий Баранов работает в Google, там запрещено разрабатывать ClickHouse.
Upd. Сделано теми людьми, кому не запрещено разрабатывать ClickHouse.
### 8.23. Подписка для импорта обновляемых и ротируемых логов в ФС {#podpiska-dlia-importa-obnovliaemykh-i-rotiruemykh-logov-v-fs}
@ -841,7 +867,7 @@ Upd. Ура, нашли причину и исправили.
Нужно для БК и Метрики.
### 10.4. Словарь из YDB (KikiMR) {#slovar-iz-ydb-kikimr}
### 10.4. - Словарь из YDB (KikiMR) {#slovar-iz-ydb-kikimr}
Нужно для Метрики, а делать будет таинственный незнакомец из команды KikiMR (под вопросом). Таинственный незнакомец не подтверждает, что он будет делать эту задачу.
@ -880,16 +906,15 @@ Upd. Ура, нашли причину и исправили.
Артём Стрельцов, Николай Дегтеринский, Наталия Михненко, ВШЭ.
Приступили к этой задаче.
Готов direct, есть pull request complex_key_direct.
Готово всё.
### 10.13. Использование Join как generic layout для словарей {#ispolzovanie-join-kak-generic-layout-dlia-slovarei}
Артём Стрельцов, Николай Дегтеринский, Наталия Михненко, ВШЭ.
### 10.14. Поддержка всех типов в функции transform {#podderzhka-vsekh-tipov-v-funktsii-transform}
### 10.15. + Использование словарей как специализированного layout для Join {#ispolzovanie-slovarei-kak-spetsializirovannogo-layout-dlia-join}
### 10.16. Словари на локальном SSD {#slovari-na-lokalnom-ssd}
### 10.16. + Словари на локальном SSD {#slovari-na-lokalnom-ssd}
Никита Васильев, ВШЭ и Яндекс. Есть pull request.
@ -922,6 +947,8 @@ Upd. Александр Крашенинников перешёл в другу
### 11.4. Исправление упячек с типами Date и Decimal в clickhouse-cpp {#ispravlenie-upiachek-s-tipami-date-i-decimal-v-clickhouse-cpp}
Altinity целиком взяли на себя поддержку clickhouse-cpp драйвера.
### 11.5. Поддержка TLS в clickhouse-cpp {#podderzhka-tls-v-clickhouse-cpp}
А знаете ли вы, что библиотеку clickhouse-cpp разрабатывал один хороший человек в свободное время?
@ -930,7 +957,7 @@ Upd. Александр Крашенинников перешёл в другу
### 11.7. Интерактивный режим работы программы clickhouse-local {#interaktivnyi-rezhim-raboty-programmy-clickhouse-local}
### 11.8. Поддержка протокола PostgreSQL {#podderzhka-protokola-postgresql}
### 11.8. + Поддержка протокола PostgreSQL {#podderzhka-protokola-postgresql}
Элбакян Мовсес Андраникович, ВШЭ.
@ -967,7 +994,7 @@ Q1. Сделано управление правами полностью, но
Аутентификация через LDAP - Денис Глазачев.
[Виталий Баранов](https://github.com/vitlibar) и Денис Глазачев, Altinity. Требует 12.1.
Q2.
Q3.
### 12.4. Подключение IDM системы Яндекса как справочника пользователей и прав доступа {#podkliuchenie-idm-sistemy-iandeksa-kak-spravochnika-polzovatelei-i-prav-dostupa}
@ -987,7 +1014,7 @@ Q2.
### 13.1. Overcommit запросов по памяти и вытеснение {#overcommit-zaprosov-po-pamiati-i-vytesnenie}
Требует 2.1. Способ реализации обсуждается.
Требует 2.1. Способ реализации обсуждается. Александр Казаков.
### 13.2. Общий конвейер выполнения на сервер {#obshchii-konveier-vypolneniia-na-server}
@ -1016,6 +1043,7 @@ Upd. Задача взята в работу.
### 14.4. Поддержка подстановок для идентификаторов (имён) в SQL запросе {#podderzhka-podstanovok-dlia-identifikatorov-imion-v-sql-zaprose}
zhang2014
Задача на паузе.
### 14.5. + Поддержка задания множества как массива в правой части секции IN {#podderzhka-zadaniia-mnozhestva-kak-massiva-v-pravoi-chasti-sektsii-in}
@ -1035,10 +1063,12 @@ zhang2014
Результат некоторых агрегатных функций зависит от порядка данных. Предлагается реализовать модификатор ORDER BY, задающий порядок явно. Пример: groupArray(x ORDER BY y, z).
Upd. Есть pull request-ы.
Upd. DISTINCT готов.
### 14.9. Поддержка запроса EXPLAIN {#podderzhka-zaprosa-explain}
Требует 2.1. [Николай Кочетов](https://github.com/KochetovNicolai).
Upd. Есть pull request.
### 14.10. arrayReduce как функция высшего порядка {#arrayreduce-kak-funktsiia-vysshego-poriadka}
@ -1075,7 +1105,7 @@ zhang2014.
Для BI систем.
### 14.19. Совместимость парсера типов данных с SQL {#sovmestimost-parsera-tipov-dannykh-s-sql}
### 14.19. + Совместимость парсера типов данных с SQL {#sovmestimost-parsera-tipov-dannykh-s-sql}
Павел Потёмкин, ВШЭ.
Для BI систем.
@ -1170,6 +1200,8 @@ Upd. В ревью.
Реализовать в ClickHouse типы данных для задач обработки геоинформационных данных: Point, Line, MultiLine, Polygon и операции над ними - проверка вхождения, пересечения. Вариантом минимум будет реализация этих операций в евклидовой системе координат. Дополнительно - на сфере и WGS84.
Upd. Есть pull request.
### 17.3. + Ускорение greatCircleDistance {#uskorenie-greatcircledistance}
[Ольга Хвостикова](https://github.com/stavrolia), основано на коде Андрея Аксёнова, получено разрешение на использование кода.
@ -1229,11 +1261,11 @@ Upd. В ревью.
Upd. Алексей сделал какой-то вариант, но борется с тем, что ничего не работает.
Upd. Есть pull request на начальной стадии.
### 19.3. Подключение YT Cypress или YDB как альтернативы ZooKeeper {#podkliuchenie-yt-cypress-ili-ydb-kak-alternativy-zookeeper}
### 19.3. - Подключение YT Cypress или YDB как альтернативы ZooKeeper {#podkliuchenie-yt-cypress-ili-ydb-kak-alternativy-zookeeper}
Hold. Полезно для заказчиков внутри Яндекса, но есть риски. Эту задачу никто не будет делать.
### 19.4. internal\_replication = auto {#internal-replication-auto}
### 19.4. internal_replication = auto {#internal-replication-auto}
### 19.5. Реплицируемые базы данных {#replitsiruemye-bazy-dannykh}
@ -1243,6 +1275,8 @@ Hold. Полезно для заказчиков внутри Яндекса, н
Предлагается реализовать «движок баз данных», который осуществляет репликацию метаданных (множество имеющихся таблиц и лог DDL операций над ними: CREATE, DROP, RENAME, ALTER). Пользователь сможет создать реплицируемую базу данных; при её создании или восстановлении на другом сервере, все реплицируемые таблицы будут созданы автоматически.
Upd. Задача в разработке.
### 19.6. + Одновременный выбор кусков для слияния многими репликами, отказ от leader election в ZK {#odnovremennyi-vybor-kuskov-dlia-sliianiia-mnogimi-replikami-otkaz-ot-leader-election-v-zk}
Готово.
@ -1303,6 +1337,7 @@ Upd. Антон делает эту задачу. Большая часть уж
Upd. Есть pull request для GROUP BY. Приличные результаты.
Upd. Для GROUP BY готово, в процессе для DISTINCT.
Upd. Для DISTINCT есть pull request.
### 21.5. + Распараллеливание INSERT при INSERT SELECT, если это необходимо {#rasparallelivanie-insert-pri-insert-select-esli-eto-neobkhodimo}
@ -1315,6 +1350,7 @@ Upd. Для GROUP BY готово, в процессе для DISTINCT.
[Achimbab](https://github.com/achimbab).
Есть pull request. Но это не совсем то.
Upd. В обсуждении.
### 21.8. Взаимная интеграция аллокатора и кэша {#vzaimnaia-integratsiia-allokatora-i-kesha}
@ -1326,6 +1362,8 @@ Upd. Для GROUP BY готово, в процессе для DISTINCT.
Для domain-specific кэшей (как например, кэш разжатых данных) выгодно, чтобы они использовали как можно больший объём свободной памяти. Но в этом случае, памяти может не хватить для других структур данных в программе. Если аллокатор памяти знает про кэш, то выделение памяти можно было бы делать путём вытеснения данных из кэша.
Upd. Есть нерабочий прототип, скорее всего будет отложено.
### 21.8.1. Отдельный аллокатор для кэшей с ASLR {#otdelnyi-allokator-dlia-keshei-s-aslr}
В прошлом году задачу пытался сделать Данила Кутенин с помощью lfalloc из Аркадии и mimalloc из Microsoft, но оба решения не были квалифицированы для использования в продакшене. Успешная реализация задачи 21.8 отменит необходимость в этой задаче, поэтому холд.
@ -1351,6 +1389,7 @@ Amos Bird.
Сделана замена цепочек if на multiIf, но внезапно оказалось, что это является не оптимизацией, а наоборот.
Сделано ещё несколько оптимизаций.
Upd. Все вышеперечисленные оптимизации доступны в pull requests.
### 21.12. Алгебраические оптимизации запросов {#algebraicheskie-optimizatsii-zaprosov}
@ -1415,7 +1454,7 @@ Constraints позволяют задать выражение, истиннос
5. pdq partial sort
Хороший алгоритм сортировки сравнениями `pdqsort` не имеет варианта partial sort. Заметим, что на практике, почти все сортировки в запросах ClickHouse являются partial\_sort, так как `ORDER BY` почти всегда идёт с `LIMIT`. Кстати, Данила Кутенин уже попробовал это и показал, что в тривиальном случае преимущества нет. Но не очевидно, что нельзя сделать лучше.
Хороший алгоритм сортировки сравнениями `pdqsort` не имеет варианта partial sort. Заметим, что на практике, почти все сортировки в запросах ClickHouse являются partial_sort, так как `ORDER BY` почти всегда идёт с `LIMIT`. Кстати, Данила Кутенин уже попробовал это и показал, что в тривиальном случае преимущества нет. Но не очевидно, что нельзя сделать лучше.
### 21.20. Использование материализованных представлений для оптимизации запросов {#ispolzovanie-materializovannykh-predstavlenii-dlia-optimizatsii-zaprosov}
@ -1534,7 +1573,7 @@ Altinity.
### 22.23. Правильная обработка Nullable в функциях, которые кидают исключение на default значении: modulo, intDiv {#pravilnaia-obrabotka-nullable-v-funktsiiakh-kotorye-kidaiut-iskliuchenie-na-default-znachenii-modulo-intdiv}
### 22.24. Излишняя фильтрация ODBC connection string {#izlishniaia-filtratsiia-odbc-connection-string}
### 22.24. + Излишняя фильтрация ODBC connection string {#izlishniaia-filtratsiia-odbc-connection-string}
Нужно для Метрики. Алексей Миловидов.
@ -1635,6 +1674,8 @@ ClickHouse поддерживает LZ4 и ZSTD для сжатия данных
Внедрить их в ClickHouse в виде кодеков и изучить их работу на тестовых датасетах.
Upd. Есть два pull requests в начальной стадии, возможно будет отложено.
### 24.4. Шифрование в ClickHouse на уровне VFS {#shifrovanie-v-clickhouse-na-urovne-vfs}
Данные в ClickHouse хранятся без шифрования. При наличии доступа к дискам, злоумышленник может прочитать данные. Предлагается реализовать два подхода к шифрованию:
@ -1642,6 +1683,7 @@ ClickHouse поддерживает LZ4 и ZSTD для сжатия данных
1. Шифрование на уровне VFS.
Обсуждаются детали реализации. Q3/Q4.
Виталий Баранов.
### 24.5. Поддержка функций шифрования для отдельных значений {#podderzhka-funktsii-shifrovaniia-dlia-otdelnykh-znachenii}
@ -1680,7 +1722,7 @@ RAID позволяет одновременно увеличить надёжн
Есть pull request на стадии работающего прототипа.
### 24.8. Специализация векторизованного кода для AVX/AVX2/AVX512 и ARM NEON {#spetsializatsiia-vektorizovannogo-koda-dlia-avxavx2avx512-i-arm-neon}
### 24.8. + Специализация векторизованного кода для AVX/AVX2/AVX512 и ARM NEON {#spetsializatsiia-vektorizovannogo-koda-dlia-avxavx2avx512-i-arm-neon}
[\#1017](https://github.com/ClickHouse/ClickHouse/issues/1017)
@ -1692,13 +1734,13 @@ RAID позволяет одновременно увеличить надёжн
Во второй части задачи, предлагается адаптировать существующие куски кода, использующие SSE intrinsics на AVX/AVX2 и сравнить производительность. Также рассматривается оптимизация под ARM NEON.
### 24.9. Общий подход к CPU dispatching в фабрике функций {#obshchii-podkhod-k-cpu-dispatching-v-fabrike-funktsii}
### 24.9. + Общий подход к CPU dispatching в фабрике функций {#obshchii-podkhod-k-cpu-dispatching-v-fabrike-funktsii}
Дмитрий Ковальков, ВШЭ и Яндекс.
Продолжение 24.8.
Upd. Есть pull request. В стадии ревью.
Upd. Есть pull request. В стадии ревью. Готово.
### 24.10. Поддержка типов half/bfloat16/unum {#podderzhka-tipov-halfbfloat16unum}
@ -1847,7 +1889,7 @@ ucasFL, ICT.
Жанна Зосимова, ВШЭ.
Upd. Пока поддержали Arrow как формат ввода-вывода.
### - 24.30. ClickHouse как графовая СУБД {#clickhouse-kak-grafovaia-subd}
### - 24.30. - ClickHouse как графовая СУБД {#clickhouse-kak-grafovaia-subd}
Amos Bird, но его решение слишком громоздкое и пока не open-source. Отменено.

View File

@ -80,7 +80,8 @@ def build_for_lang(lang, args):
includes_dir=os.path.join(os.path.dirname(__file__), '..', '_includes'),
is_amp=False,
is_blog=True,
post_meta=post_meta
post_meta=post_meta,
today=datetime.date.today().isoformat()
)
)
@ -89,6 +90,13 @@ def build_for_lang(lang, args):
redirects.build_blog_redirects(args)
env = util.init_jinja2_env(args)
with open(os.path.join(args.website_dir, 'templates', 'blog', 'rss.xml'), 'rb') as f:
rss_template_string = f.read().decode('utf-8').strip()
rss_template = env.from_string(rss_template_string)
with open(os.path.join(args.blog_output_dir, lang, 'rss.xml'), 'w') as f:
f.write(rss_template.render({'config': raw_config}))
# TODO: AMP for blog
# if not args.skip_amp:
# amp.build_amp(lang, args, cfg)

View File

@ -3,8 +3,10 @@ import copy
import io
import logging
import os
import random
import sys
import tarfile
import time
import requests
@ -14,13 +16,20 @@ import util
def yield_candidates():
for page in range(1, 100):
url = f'https://api.github.com/repos/ClickHouse/ClickHouse/tags?per_page=100&page={page}'
for candidate in requests.get(url).json():
github_token = os.getenv('GITHUB_TOKEN')
if github_token:
headers = {'authorization': f'OAuth {github_token}'}
else:
headers = {}
for candidate in requests.get(url, headers=headers).json():
yield candidate
time.sleep(random.random() * 3)
def choose_latest_releases(args):
logging.info('Collecting release candidates')
seen = collections.OrderedDict()
seen_stable = collections.OrderedDict()
seen_lts = collections.OrderedDict()
candidates = []
stable_count = 0
lts_count = 0
@ -35,18 +44,24 @@ def choose_latest_releases(args):
if is_unstable or is_in_blacklist:
continue
major_version = '.'.join((name.split('.', 2))[:2])
if major_version not in seen:
if major_version not in seen_lts:
if (stable_count >= args.stable_releases_limit) and (lts_count >= args.lts_releases_limit):
break
payload = (name, tag.get('tarball_url'), is_lts,)
logging.debug(payload)
if is_lts:
if lts_count < args.lts_releases_limit:
seen[major_version] = payload
seen_lts[major_version] = payload
try:
del seen_stable[major_version]
except KeyError:
pass
lts_count += 1
else:
if stable_count < args.stable_releases_limit:
seen[major_version] = payload
if major_version not in seen_stable:
seen_stable[major_version] = payload
stable_count += 1
logging.debug(
@ -56,8 +71,9 @@ def choose_latest_releases(args):
logging.fatal('Unexpected GitHub response: %s', str(candidates))
sys.exit(1)
logging.info('Found stable releases: %s', ', '.join(seen.keys()))
return seen.items()
logging.info('Found LTS releases: %s', ', '.join(seen_lts.keys()))
logging.info('Found stable releases: %s', ', '.join(seen_stable.keys()))
return sorted(list(seen_lts.items()) + list(seen_stable.items()))
def process_release(args, callback, release):

View File

@ -14,9 +14,6 @@ import macros.plugin
import slugify as slugify_impl
import amp
import website
def slugify(value, separator):
return slugify_impl.slugify(value, separator=separator, word_boundary=True, save_order=True)
@ -119,6 +116,7 @@ class PatchedMacrosPlugin(macros.plugin.MacrosPlugin):
])
def on_env(self, env, config, files):
import util
env.add_extension('jinja2.ext.i18n')
dirname = os.path.join(config.data['theme'].dirs[0], 'locale')
lang = config.data['theme']['language']
@ -126,10 +124,7 @@ class PatchedMacrosPlugin(macros.plugin.MacrosPlugin):
get_translations(dirname, lang),
newstyle=True
)
chunk_size = 10240
env.filters['chunks'] = lambda line: [line[i:i+chunk_size] for i in range(0, len(line), chunk_size)]
env.filters['html_to_amp'] = amp.html_to_amp
env.filters['adjust_markdown_html'] = website.adjust_markdown_html
util.init_jinja2_filters(env)
return env
def render(self, markdown):

View File

@ -7,7 +7,7 @@ PUBLISH_DIR="${BASE_DIR}/../publish"
BASE_DOMAIN="${BASE_DOMAIN:-content.clickhouse.tech}"
GIT_TEST_URI="${GIT_TEST_URI:-git@github.com:ClickHouse/clickhouse-website-content.git}"
GIT_PROD_URI="git@github.com:ClickHouse/clickhouse-website-content.git"
EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS:---enable-stable-releases --minify}"
EXTRA_BUILD_ARGS="${EXTRA_BUILD_ARGS:---enable-stable-releases --minify --verbose}"
HISTORY_SIZE="${HISTORY_SIZE:-5}"
if [[ -z "$1" ]]

View File

@ -9,7 +9,7 @@ closure==20191111
cssmin==0.2.0
future==0.18.2
htmlmin==0.1.12
idna==2.9
idna==2.10
Jinja2==2.11.2
jinja2-highlight==0.6.1
jsmin==2.2.2
@ -25,7 +25,7 @@ protobuf==3.12.2
numpy==1.18.5
Pygments==2.5.2
pymdown-extensions==7.1
python-slugify==1.2.6
python-slugify==4.0.1
PyYAML==5.3.1
repackage==0.7.3
requests==2.24.0

View File

@ -2,10 +2,10 @@ Babel==2.8.0
certifi==2020.4.5.2
chardet==3.0.4
googletrans==3.0.0
idna==2.9
idna==2.10
Jinja2==2.11.2
pandocfilters==1.4.2
python-slugify==4.0.0
python-slugify==4.0.1
PyYAML==5.3.1
requests==2.24.0
text-unidecode==1.3

View File

@ -1,5 +1,6 @@
import collections
import contextlib
import datetime
import multiprocessing
import os
import shutil
@ -8,6 +9,7 @@ import socket
import tempfile
import threading
import jinja2
import yaml
@ -111,3 +113,35 @@ def represent_ordereddict(dumper, data):
yaml.add_representer(collections.OrderedDict, represent_ordereddict)
def init_jinja2_filters(env):
import amp
import website
chunk_size = 10240
env.filters['chunks'] = lambda line: [line[i:i + chunk_size] for i in range(0, len(line), chunk_size)]
env.filters['html_to_amp'] = amp.html_to_amp
env.filters['adjust_markdown_html'] = website.adjust_markdown_html
env.filters['to_rfc882'] = lambda d: datetime.datetime.strptime(d, '%Y-%m-%d').strftime('%a, %d %b %Y %H:%M:%S GMT')
def init_jinja2_env(args):
import mdx_clickhouse
env = jinja2.Environment(
loader=jinja2.FileSystemLoader([
args.website_dir,
os.path.join(args.docs_dir, '_includes')
]),
extensions=[
'jinja2.ext.i18n',
'jinja2_highlight.HighlightExtension'
]
)
env.extend(jinja2_highlight_cssclass='syntax p-3 my-3')
translations_dir = os.path.join(args.website_dir, 'locale')
env.install_gettext_translations(
mdx_clickhouse.get_translations(translations_dir, 'en'),
newstyle=True
)
init_jinja2_filters(env)
return env

View File

@ -11,15 +11,21 @@ import bs4
import closure
import cssmin
import htmlmin
import jinja2
import jsmin
import mdx_clickhouse
import util
def handle_iframe(iframe, soup):
if not iframe.attrs['src'].startswith('https://www.youtube.com/'):
raise RuntimeError('iframes are allowed only for YouTube')
allowed_domains = ['https://www.youtube.com/', 'https://datalens.yandex/']
illegal_domain = True
iframe_src = iframe.attrs['src']
for domain in allowed_domains:
if iframe_src.startswith(domain):
illegal_domain = False
break
if illegal_domain:
raise RuntimeError(f'iframe from illegal domain: {iframe_src}')
wrapper = soup.new_tag('div')
wrapper.attrs['class'] = ['embed-responsive', 'embed-responsive-16by9']
iframe.insert_before(wrapper)
@ -43,8 +49,11 @@ def adjust_markdown_html(content):
for a in soup.find_all('a'):
a_class = a.attrs.get('class')
a_href = a.attrs.get('href')
if a_class and 'headerlink' in a_class:
a.string = '\xa0'
if a_href and a_href.startswith('http'):
a.attrs['target'] = '_blank'
for iframe in soup.find_all('iframe'):
handle_iframe(iframe, soup)
@ -121,22 +130,7 @@ def minify_html(content):
def build_website(args):
logging.info('Building website')
env = jinja2.Environment(
loader=jinja2.FileSystemLoader([
args.website_dir,
os.path.join(args.docs_dir, '_includes')
]),
extensions=[
'jinja2.ext.i18n',
'jinja2_highlight.HighlightExtension'
]
)
env.extend(jinja2_highlight_cssclass='syntax p-3 my-3')
translations_dir = os.path.join(args.website_dir, 'locale')
env.install_gettext_translations(
mdx_clickhouse.get_translations(translations_dir, 'en'),
newstyle=True
)
env = util.init_jinja2_env(args)
shutil.copytree(
args.website_dir,

View File

@ -1,14 +1,12 @@
---
machine_translated: true
machine_translated_rev: 72537a2d527c63c07aa5d2361a8829f3895cf2bd
toc_priority: 63
toc_title: "\u6D4F\u89C8\u6E90\u4EE3\u7801"
---
# 浏览ClickHouse源代码 {#browse-clickhouse-source-code}
您可以使用 **Woboq** 在线代码浏览器可用 [这里](https://clickhouse.tech/codebrowser/html_report/ClickHouse/src/index.html). 它提供了代码导航和语义突出显示搜索和索引。 代码快照每天更新。
您可以使用 **Woboq** 在线代码浏览器 [点击这里](https://clickhouse.tech/codebrowser/html_report/ClickHouse/src/index.html). 它提供了代码导航和语义突出显示搜索和索引。 代码快照每天更新。
此外,您还可以浏览源 [GitHub](https://github.com/ClickHouse/ClickHouse) 像往常一样
此外,您还可以像往常一样浏览源代码 [GitHub](https://github.com/ClickHouse/ClickHouse)
如果你有兴趣使用什么样的IDE我们建议CLionQT CreatorVS Code和KDevelop有注意事项。 您可以使用任何喜欢的IDE。 Vim和Emacs也算数
如果你希望了解哪种IDE较好我们推荐使用CLionQT CreatorVS Code和KDevelop有注意事项。 您可以使用任何您喜欢的IDE。 Vim和Emacs也可以

View File

@ -6,13 +6,13 @@ ClickHouse 支持在 Mac OS X 10.12 版本中编译。若您在用更早的操
## 安装 Homebrew {#an-zhuang-homebrew}
``` bash
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
## 安装编译器,工具库 {#an-zhuang-bian-yi-qi-gong-ju-ku}
``` bash
brew install cmake ninja gcc icu4c mariadb-connector-c openssl libtool gettext
$ brew install cmake ninja libtool gettext
```
## 拉取 ClickHouse 源码 {#la-qu-clickhouse-yuan-ma}
@ -27,11 +27,11 @@ cd ClickHouse
## 编译 ClickHouse {#bian-yi-clickhouse}
``` bash
mkdir build
cd build
cmake .. -DCMAKE_CXX_COMPILER=`which g++-8` -DCMAKE_C_COMPILER=`which gcc-8`
ninja
cd ..
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_CXX_COMPILER=`which clang++` -DCMAKE_C_COMPILER=`which clang`
$ ninja
$ cd ..
```
## 注意事项 {#zhu-yi-shi-xiang}

View File

@ -1,9 +1,9 @@
---
toc_priority: 37
toc_title: "\u7248\u672C\u96C6\u5408\u5728\u65B0\u6811"
toc_title: "版本折叠MergeTree"
---
# 版本折叠合并树 {#versionedcollapsingmergetree}
# 版本折叠MergeTree {#versionedcollapsingmergetree}
这个引擎:

View File

@ -60,7 +60,6 @@ Restrictions on the «maximum amount of something» can take the value 0, which
## max\_rows\_to\_read {#max-rows-to-read}
可以在每个块(而不是每行)上检查以下限制。 也就是说,限制可以打破一点。
在多个线程中运行查询时,以下限制单独应用于每个线程。
运行查询时可从表中读取的最大行数。

View File

@ -1,16 +1,18 @@
---
machine_translated: true
machine_translated_rev: 72537a2d527c63c07aa5d2361a8829f3895cf2bd
toc_priority: 37
toc_title: SYSTEM
---
# 系统查询 {#query-language-system}
# SYSTEM Queries {#query-language-system}
- [RELOAD EMBEDDED DICTIONARIES](#query_language-system-reload-emdedded-dictionaries)
- [RELOAD DICTIONARIES](#query_language-system-reload-dictionaries)
- [RELOAD DICTIONARY](#query_language-system-reload-dictionary)
- [DROP DNS CACHE](#query_language-system-drop-dns-cache)
- [DROP MARK CACHE](#query_language-system-drop-mark-cache)
- [DROP UNCOMPRESSED CACHE](#query_language-system-drop-uncompressed-cache)
- [DROP COMPILED EXPRESSION CACHE](#query_language-system-drop-compiled-expression-cache)
- [DROP REPLICA](#query_language-system-drop-replica)
- [FLUSH LOGS](#query_language-system-flush_logs)
- [RELOAD CONFIG](#query_language-system-reload-config)
- [SHUTDOWN](#query_language-system-shutdown)
@ -20,18 +22,37 @@ toc_title: SYSTEM
- [START DISTRIBUTED SENDS](#query_language-system-start-distributed-sends)
- [STOP MERGES](#query_language-system-stop-merges)
- [START MERGES](#query_language-system-start-merges)
- [STOP TTL MERGES](#query_language-stop-ttl-merges)
- [START TTL MERGES](#query_language-start-ttl-merges)
- [STOP MOVES](#query_language-stop-moves)
- [START MOVES](#query_language-start-moves)
- [STOP FETCHES](#query_language-system-stop-fetches)
- [START FETCHES](#query_language-system-start-fetches)
- [STOP REPLICATED SENDS](#query_language-system-start-replicated-sends)
- [START REPLICATED SENDS](#query_language-system-start-replicated-sends)
- [STOP REPLICATION QUEUES](#query_language-system-stop-replication-queues)
- [START REPLICATION QUEUES](#query_language-system-start-replication-queues)
- [SYNC REPLICA](#query_language-system-sync-replica)
- [RESTART REPLICA](#query_language-system-restart-replica)
- [RESTART REPLICAS](#query_language-system-restart-replicas)
## RELOAD EMBEDDED DICTIONARIES\] {#query_language-system-reload-emdedded-dictionaries}
重新加载所有[内置字典](../../sql-reference/dictionaries/internal-dicts.md)。默认情况下内置字典是禁用的。
总是返回 OK.’,不管这些内置字典的更新结果如何。
## RELOAD DICTIONARIES {#query_language-system-reload-dictionaries}
重新加载之前已成功加载的所有字典。
默认情况下,字典是懒惰加载的(请参阅 [dictionaries\_lazy\_load](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-dictionaries_lazy_load)所以不是在启动时自动加载而是通过dictGet函数在第一次访问时初始化或者从ENGINE=Dictionary的表中选择。 该 `SYSTEM RELOAD DICTIONARIES` 查询重新加载这样的字典(加载)。
总是返回 `Ok.` 无论字典更新的结果如何。
重载已经被成功加载过的所有字典。
默认情况下,字典是延时加载的( [dictionaries\_lazy\_load](../../operations/server-configuration-parameters/settings.md#server_configuration_parameters-dictionaries_lazy_load)不是在服务启动时自动加载而是在第一次使用dictGet函数或通过 `SELECT from tables with ENGINE = Dictionary` 进行访问时被初始化。这个命令 `SYSTEM RELOAD DICTIONARIES` 就是针对这类表进行重新加载的。
## 重新加载字典Dictionary\_name {#query_language-system-reload-dictionary}
完全重新加载字典 `dictionary_name`与字典的状态无关LOADED/NOT\_LOADED/FAILED
总是返回 `Ok.` 无论更新字典的结果如何。
字典的状态可以通过查询 `system.dictionaries` 桌子
## RELOAD DICTIONARY Dictionary\_name {#query_language-system-reload-dictionary}
完全重新加载指定字典 `dictionary_name`,不管该字典的状态如何(LOADED / NOT\_LOADED / FAILED)。不管字典的更新结果如何,总是返回 `OK.`
字典的状态可以通过查询 `system.dictionaries`表来检查。
``` sql
SELECT name, status FROM system.dictionaries;
@ -39,37 +60,67 @@ SELECT name, status FROM system.dictionaries;
## DROP DNS CACHE {#query_language-system-drop-dns-cache}
重置ClickHouse的内部DNS缓存。 有时对于旧的ClickHouse版本在更改基础架构更改另一个ClickHouse服务器或字典使用的服务器的IP地址时需要使用此命令。
重置CH的dns缓存。有时候对于旧的ClickHouse版本当某些底层环境发生变化时修改其它Clickhouse服务器的ip或字典所在服务器的ip需要使用该命令。
更多自动化的缓存管理相关信息参见disable\_internal\_dns\_cache, dns\_cache\_update\_period这些参数。
有关更方便自动缓存管理请参阅disable\_internal\_dns\_cache、dns\_cache\_update\_period参数。
## DROP MARK CACHE {#query_language-system-drop-mark-cache}
重置标记缓存。 用于开发ClickHouse和性能测试。
重置mark缓存。在进行ClickHouse开发或性能测试时使用。
## DROP REPLICA {#query_language-system-drop-replica}
使用下面的语句可以删除已经无效的副本。
```sql
SYSTEM DROP REPLICA 'replica_name' FROM TABLE database.table;
SYSTEM DROP REPLICA 'replica_name' FROM DATABASE database;
SYSTEM DROP REPLICA 'replica_name';
SYSTEM DROP REPLICA 'replica_name' FROM ZKPATH '/path/to/table/in/zk';
```
该操作将副本的路径从Zookeeper中删除。当副本失效并且由于该副本已经不存在导致它的元数据不能通过 `DROP TABLE`从zookeeper中删除这种情形下可以使用该命令。它只会删除失效或过期的副本不会删除本地的副本。请使用 `DROP TABLE` 来删除本地副本。 `DROP REPLICA` 不会删除任何表,并且不会删除磁盘上的任何数据或元数据信息。
第1条语句删除 `database.table`表的 `replica_name`副本的元数据
第2条语句删除 `database` 数据库的 所有`replica_name`副本的元数据
第3条语句删除本地服务器所有 `replica_name`副本的元数据
第4条语句用于在表的其它所有副本都删除时删除已失效副本的元数据。使用时需要明确指定表的路径。该路径必须和创建表时 `ReplicatedMergeTree`引擎的第一个参数一致。
## DROP UNCOMPRESSED CACHE {#query_language-system-drop-uncompressed-cache}
重置未压缩数据的缓存。用于ClickHouse开发和性能测试。
管理未压缩数据缓存的参数,使用以下的服务器级别设置 [uncompressed\_cache\_size](../../operations/server-configuration-parameters/settings.md#server-settings-uncompressed_cache_size)以及 `query/user/profile`级别设置 [use\_uncompressed\_cache](../../operations/settings/settings.md#setting-use_uncompressed_cache)
## DROP COMPILED EXPRESSION CACHE {#query_language-system-drop-compiled-expression-cache}
重置已编译的表达式缓存。用于ClickHouse开发和性能测试。
`query/user/profile` 启用配置项 [compile](../../operations/settings/settings.md#compile)时,编译的表达式缓存开启。
## FLUSH LOGS {#query_language-system-flush_logs}
Flushes buffers of log messages to system tables (e.g. system.query\_log). Allows you to not wait 7.5 seconds when debugging.
将日志信息缓冲数据刷入系统表例如system.query\_log。调试时允许等待不超过7.5秒。当信息队列为空时,会创建系统表。
## RELOAD CONFIG {#query_language-system-reload-config}
重新加载ClickHouse配置。 当配置存储在ZooKeeeper中时使用。
重新加载ClickHouse的配置。用于当配置信息存放在ZooKeeper时
## SHUTDOWN {#query_language-system-shutdown}
通常关闭ClickHouse`service clickhouse-server stop` / `kill {$pid_clickhouse-server}`)
关闭ClickHouse服务类似于 `service clickhouse-server stop` / `kill {$pid_clickhouse-server}`
## KILL {#query_language-system-kill}
中止ClickHouse进程`kill -9 {$ pid_clickhouse-server}`)
关闭ClickHouse进程 `kill -9 {$ pid_clickhouse-server}`
## 管理分布式表 {#query-language-system-distributed}
## Managing Distributed Tables {#query-language-system-distributed}
ClickHouse可以管理 [distribute](../../engines/table-engines/special/distributed.md)表。当用户向这类表插入数据时ClickHouse首先为需要发送到集群节点的数据创建一个队列然后异步的发送它们。你可以维护队列的处理过程通过[STOP DISTRIBUTED SENDS](#query_language-system-stop-distributed-sends), [FLUSH DISTRIBUTED](#query_language-system-flush-distributed), 以及 [START DISTRIBUTED SENDS](#query_language-system-start-distributed-sends)。你也可以设置 `insert_distributed_sync`参数来以同步的方式插入分布式数据。
ClickHouse可以管理 [分布](../../engines/table-engines/special/distributed.md) 桌子 当用户将数据插入到这些表中时ClickHouse首先创建应发送到群集节点的数据队列然后异步发送它。 您可以使用 [STOP DISTRIBUTED SENDS](#query_language-system-stop-distributed-sends), [FLUSH DISTRIBUTED](#query_language-system-flush-distributed),和 [START DISTRIBUTED SENDS](#query_language-system-start-distributed-sends) 查询。 您也可以同步插入分布式数据与 `insert_distributed_sync` 设置。
### STOP DISTRIBUTED SENDS {#query_language-system-stop-distributed-sends}
将数据插入分布式表时禁用后台数据分发。
当向分布式表插入数据时,禁用后台的分布式数据分发。
``` sql
SYSTEM STOP DISTRIBUTED SENDS [db.]<distributed_table_name>
@ -77,7 +128,7 @@ SYSTEM STOP DISTRIBUTED SENDS [db.]<distributed_table_name>
### FLUSH DISTRIBUTED {#query_language-system-flush-distributed}
强制ClickHouse将数据同步发送到群集节点。 如果任何节点不可用ClickHouse将引发异常并停止查询执行。 您可以重试查询,直到查询成功,这将在所有节点恢复联机时发生
强制让ClickHouse同步向集群节点同步发送数据。如果有节点失效ClickHouse抛出异常并停止插入操作。当所有节点都恢复上线时你可以重试之前的操作直到成功执行
``` sql
SYSTEM FLUSH DISTRIBUTED [db.]<distributed_table_name>
@ -85,29 +136,152 @@ SYSTEM FLUSH DISTRIBUTED [db.]<distributed_table_name>
### START DISTRIBUTED SENDS {#query_language-system-start-distributed-sends}
将数据插入分布式表时启用后台数据分发。
当向分布式表插入数据时,允许后台的分布式数据分发。
``` sql
SYSTEM START DISTRIBUTED SENDS [db.]<distributed_table_name>
```
## Managing MergeTree Tables {#query-language-system-mergetree}
ClickHouse可以管理 [MergeTree](../../engines/table-engines/mergetree-family/mergetree.md)表的后台处理进程。
### STOP MERGES {#query_language-system-stop-merges}
提供停止MergeTree系列中表的后台合并的可能性:
为MergeTree系列引擎表停止后台合并操作。
``` sql
SYSTEM STOP MERGES [[db.]merge_tree_family_table_name]
```
!!! note "注"
`DETACH / ATTACH` 即使在之前所有MergeTree表的合并已停止的情况下table也会为表启动后台合并。
!!! note "Note"
`DETACH / ATTACH` 表操作会在后台进行表的merge操作甚至当所有MergeTree表的合并操作已经停止的情况下。
### START MERGES {#query_language-system-start-merges}
为MergeTree系列中的表提供启动后台合并的可能性:
为MergeTree系列引擎表启动后台合并操作。
``` sql
SYSTEM START MERGES [[db.]merge_tree_family_table_name]
```
[原始文章](https://clickhouse.tech/docs/en/query_language/system/) <!--hide-->
### STOP TTL MERGES {#query_language-stop-ttl-merges}
根据 [TTL expression](../../engines/table-engines/mergetree-family/mergetree.md#table_engine-mergetree-ttl)为MergeTree系列引擎表停止后台删除旧数据。
不管表存在与否,都返回 `OK.`。当数据库不存在时返回错误。
``` sql
SYSTEM STOP TTL MERGES [[db.]merge_tree_family_table_name]
```
### START TTL MERGES {#query_language-start-ttl-merges}
根据 [TTL expression](../../engines/table-engines/mergetree-family/mergetree.md#table_engine-mergetree-ttl)为MergeTree系列引擎表启动后台删除旧数据。不管表存在与否都返回 `OK.`。当数据库不存在时返回错误。
``` sql
SYSTEM START TTL MERGES [[db.]merge_tree_family_table_name]
```
### STOP MOVES {#query_language-stop-moves}
根据 [TTL expression](../../engines/table-engines/mergetree-family/mergetree.md#table_engine-mergetree-ttl)为MergeTree系列引擎表停止后台移动数据。不管表存在与否都返回 `OK.`。当数据库不存在时返回错误。
``` sql
SYSTEM STOP MOVES [[db.]merge_tree_family_table_name]
```
### START MOVES {#query_language-start-moves}
根据 [TTL expression](../../engines/table-engines/mergetree-family/mergetree.md#table_engine-mergetree-ttl)为MergeTree系列引擎表启动后台移动数据。不管表存在与否都返回 `OK.`。当数据库不存在时返回错误。
``` sql
SYSTEM STOP MOVES [[db.]merge_tree_family_table_name]
```
## Managing ReplicatedMergeTree Tables {#query-language-system-replicated}
管理 [ReplicatedMergeTree](../../engines/table-engines/mergetree-family/replacingmergetree.md)表的后台复制相关进程。
### STOP FETCHES {#query_language-system-stop-fetches}
停止后台获取 `ReplicatedMergeTree`系列引擎表中插入的数据块。
不管表引擎类型如何或表/数据库是否存,都返回 `OK.`
``` sql
SYSTEM STOP FETCHES [[db.]replicated_merge_tree_family_table_name]
```
### START FETCHES {#query_language-system-start-fetches}
启动后台获取 `ReplicatedMergeTree`系列引擎表中插入的数据块。
不管表引擎类型如何或表/数据库是否存,都返回 `OK.`
``` sql
SYSTEM START FETCHES [[db.]replicated_merge_tree_family_table_name]
```
### STOP REPLICATED SENDS {#query_language-system-start-replicated-sends}
停止通过后台分发 `ReplicatedMergeTree`系列引擎表中新插入的数据块到集群的其它副本节点。
``` sql
SYSTEM STOP REPLICATED SENDS [[db.]replicated_merge_tree_family_table_name]
```
### START REPLICATED SENDS {#query_language-system-start-replicated-sends}
启动通过后台分发 `ReplicatedMergeTree`系列引擎表中新插入的数据块到集群的其它副本节点。
``` sql
SYSTEM START REPLICATED SENDS [[db.]replicated_merge_tree_family_table_name]
```
### STOP REPLICATION QUEUES {#query_language-system-stop-replication-queues}
停止从Zookeeper中获取 `ReplicatedMergeTree`系列表的复制队列的后台任务。可能的后台任务类型包含merges, fetches, mutation带有 `ON CLUSTER`的ddl语句
``` sql
SYSTEM STOP REPLICATION QUEUES [[db.]replicated_merge_tree_family_table_name]
```
### START REPLICATION QUEUES {#query_language-system-start-replication-queues}
启动从Zookeeper中获取 `ReplicatedMergeTree`系列表的复制队列的后台任务。可能的后台任务类型包含merges, fetches, mutation带有 `ON CLUSTER`的ddl语句
``` sql
SYSTEM START REPLICATION QUEUES [[db.]replicated_merge_tree_family_table_name]
```
### SYNC REPLICA {#query_language-system-sync-replica}
直到 `ReplicatedMergeTree`表将要和集群的其它副本进行同步之前会一直运行。如果当前对表的获取操作禁用的话,在达到 `receive_timeout`之前会一直运行。
``` sql
SYSTEM SYNC REPLICA [db.]replicated_merge_tree_family_table_name
```
### RESTART REPLICA {#query_language-system-restart-replica}
重置 `ReplicatedMergeTree`表的Zookeeper会话状态。该操作会以Zookeeper为参照对比当前状态有需要的情况下将任务添加到ZooKeeper队列。
基于ZooKeeper的日期初始化复制队列类似于 `ATTACH TABLE`语句。短时间内不能对表进行任何操作。
``` sql
SYSTEM RESTART REPLICA [db.]replicated_merge_tree_family_table_name
```
### RESTART REPLICAS {#query_language-system-restart-replicas}
重置所有 `ReplicatedMergeTree`表的ZooKeeper会话状态。该操作会以Zookeeper为参照对比当前状态有需要的情况下将任务添加到ZooKeeper队列。
``` sql
SYSTEM RESTART QUEUES [db.]replicated_merge_tree_family_table_name
```
[原始文档](https://clickhouse.tech/docs/en/query_language/system/) <!--hide-->

View File

@ -103,6 +103,7 @@ namespace ErrorCodes
extern const int CLIENT_OUTPUT_FORMAT_SPECIFIED;
extern const int INVALID_USAGE_OF_INPUT;
extern const int DEADLOCK_AVOIDED;
extern const int UNRECOGNIZED_ARGUMENTS;
}
@ -132,7 +133,12 @@ private:
std::unique_ptr<Connection> connection; /// Connection to DB.
String query_id; /// Current query_id.
String query; /// Current query.
String full_query; /// Current query as it was given to the client.
// Current query as it will be sent to the server. It may differ from the
// full query for INSERT queries, for which the data that follows the query
// is stripped and sent separately.
String query_to_send;
String format; /// Query results output format.
bool is_default_format = true; /// false, if format is set in the config or command line.
@ -177,10 +183,10 @@ private:
ASTPtr parsed_query;
/// The last exception that was received from the server. Is used for the return code in batch mode.
std::unique_ptr<Exception> last_exception;
std::unique_ptr<Exception> last_exception_received_from_server;
/// If the last query resulted in exception.
bool got_exception = false;
bool received_exception_from_server = false;
int expected_server_error = 0;
int expected_client_error = 0;
int actual_server_error = 0;
@ -616,7 +622,7 @@ private:
try
{
if (!process(input))
if (!processQueryText(input))
break;
}
catch (const Exception & e)
@ -657,8 +663,8 @@ private:
nonInteractive();
/// If exception code isn't zero, we should return non-zero return code anyway.
if (last_exception)
return last_exception->code() != 0 ? last_exception->code() : -1;
if (last_exception_received_from_server)
return last_exception_received_from_server->code() != 0 ? last_exception_received_from_server->code() : -1;
return 0;
}
@ -753,22 +759,31 @@ private:
readStringUntilEOF(text, in);
}
process(text);
processQueryText(text);
}
bool process(const String & text)
bool processQueryText(const String & text)
{
if (exit_strings.end() != exit_strings.find(trim(text, [](char c){ return isWhitespaceASCII(c) || c == ';'; })))
return false;
const bool test_mode = config().has("testmode");
if (config().has("multiquery"))
if (!config().has("multiquery"))
{
processTextAsSingleQuery(text);
return true;
}
return processMultiQuery(text);
}
bool processMultiQuery(const String & text)
{
const bool test_mode = config().has("testmode");
{ /// disable logs if expects errors
TestHint test_hint(test_mode, text);
if (test_hint.clientError() || test_hint.serverError())
process("SET send_logs_level = 'none'");
processTextAsSingleQuery("SET send_logs_level = 'none'");
}
/// Several queries separated by ';'.
@ -780,9 +795,9 @@ private:
while (begin < end)
{
const char * pos = begin;
ASTPtr ast = parseQuery(pos, end, true);
ASTPtr orig_ast = parseQuery(pos, end, true);
if (!ast)
if (!orig_ast)
{
if (ignore_error)
{
@ -797,7 +812,7 @@ private:
return true;
}
auto * insert = ast->as<ASTInsertQuery>();
auto * insert = orig_ast->as<ASTInsertQuery>();
if (insert && insert->data)
{
@ -817,26 +832,33 @@ private:
try
{
auto ast_to_process = ast;
auto ast_to_process = orig_ast;
if (insert && insert->data)
{
ast_to_process = nullptr;
if (!processSingleQuery(str, ast_to_process) && !ignore_error)
return false;
processTextAsSingleQuery(str);
}
else
{
parsed_query = ast_to_process;
full_query = str;
query_to_send = str;
processParsedSingleQuery();
}
}
catch (...)
{
last_exception = std::make_unique<Exception>(getCurrentExceptionMessage(true), getCurrentExceptionCode());
actual_client_error = last_exception->code();
last_exception_received_from_server = std::make_unique<Exception>(getCurrentExceptionMessage(true), getCurrentExceptionCode());
actual_client_error = last_exception_received_from_server->code();
if (!ignore_error && (!actual_client_error || actual_client_error != expected_client_error))
std::cerr << "Error on processing query: " << str << std::endl << last_exception->message();
got_exception = true;
std::cerr << "Error on processing query: " << str << std::endl << last_exception_received_from_server->message();
received_exception_from_server = true;
}
if (!test_hint.checkActual(actual_server_error, actual_client_error, got_exception, last_exception))
if (!test_hint.checkActual(actual_server_error, actual_client_error, received_exception_from_server, last_exception_received_from_server))
connection->forceConnected(connection_parameters.timeouts);
if (got_exception && !ignore_error)
if (received_exception_from_server && !ignore_error)
{
if (is_interactive)
break;
@ -847,41 +869,53 @@ private:
return true;
}
void processTextAsSingleQuery(const String & text_)
{
full_query = text_;
/// Some parts of a query (result output and formatting) are executed
/// client-side. Thus we need to parse the query.
const char * begin = full_query.data();
parsed_query = parseQuery(begin, begin + full_query.size(), false);
if (!parsed_query)
return;
// An INSERT query may have the data that follow query text. Remove the
/// Send part of query without data, because data will be sent separately.
auto * insert = parsed_query->as<ASTInsertQuery>();
if (insert && insert->data)
{
query_to_send = full_query.substr(0, insert->data - full_query.data());
}
else
{
return processSingleQuery(text);
}
query_to_send = full_query;
}
processParsedSingleQuery();
}
bool processSingleQuery(const String & line, ASTPtr parsed_query_ = nullptr)
// Parameters are in global variables:
// 'parsed_query' -- the query AST,
// 'query_to_send' -- the query text that is sent to server,
// 'full_query' -- for INSERT queries, contains the query and the data that
// follow it. Its memory is referenced by ASTInsertQuery::begin, end.
void processParsedSingleQuery()
{
resetOutput();
got_exception = false;
received_exception_from_server = false;
if (echo_queries)
{
writeString(line, std_out);
writeString(full_query, std_out);
writeChar('\n', std_out);
std_out.next();
}
watch.restart();
query = line;
/// Some parts of a query (result output and formatting) are executed client-side.
/// Thus we need to parse the query.
parsed_query = parsed_query_;
if (!parsed_query)
{
const char * begin = query.data();
parsed_query = parseQuery(begin, begin + query.size(), false);
}
if (!parsed_query)
return true;
processed_rows = 0;
progress.reset();
show_progress_bar = false;
@ -924,7 +958,7 @@ private:
}
/// Do not change context (current DB, settings) in case of an exception.
if (!got_exception)
if (!received_exception_from_server)
{
if (const auto * set_query = parsed_query->as<ASTSetQuery>())
{
@ -962,8 +996,6 @@ private:
{
std::cerr << watch.elapsedSeconds() << "\n";
}
return true;
}
@ -995,17 +1027,19 @@ private:
visitor.visit(parsed_query);
/// Get new query after substitutions. Note that it cannot be done for INSERT query with embedded data.
query = serializeAST(*parsed_query);
query_to_send = serializeAST(*parsed_query);
}
static constexpr size_t max_retries = 10;
for (size_t retry = 0; retry < max_retries; ++retry)
int retries_left = 10;
for (;;)
{
assert(retries_left > 0);
try
{
connection->sendQuery(
connection_parameters.timeouts,
query,
query_to_send,
query_id,
QueryProcessingStage::Complete,
&context.getSettingsRef(),
@ -1019,31 +1053,34 @@ private:
}
catch (const Exception & e)
{
/// Retry when the server said "Client should retry" and no rows has been received yet.
if (processed_rows == 0 && e.code() == ErrorCodes::DEADLOCK_AVOIDED && retry + 1 < max_retries)
continue;
/// Retry when the server said "Client should retry" and no rows
/// has been received yet.
if (processed_rows == 0
&& e.code() == ErrorCodes::DEADLOCK_AVOIDED
&& --retries_left)
{
std::cerr << "Got a transient error from the server, will"
<< " retry (" << retries_left << " retries left)";
}
else
{
throw;
}
}
}
}
/// Process the query that requires transferring data blocks to the server.
void processInsertQuery()
{
/// Send part of query without data, because data will be sent separately.
const auto & parsed_insert_query = parsed_query->as<ASTInsertQuery &>();
String query_without_data = parsed_insert_query.data
? query.substr(0, parsed_insert_query.data - query.data())
: query;
const auto parsed_insert_query = parsed_query->as<ASTInsertQuery &>();
if (!parsed_insert_query.data && (is_interactive || (!stdin_is_a_tty && std_in.eof())))
throw Exception("No data to insert", ErrorCodes::NO_DATA_TO_INSERT);
connection->sendQuery(
connection_parameters.timeouts,
query_without_data,
query_to_send,
query_id,
QueryProcessingStage::Complete,
&context.getSettingsRef(),
@ -1310,8 +1347,8 @@ private:
return true;
case Protocol::Server::Exception:
onException(*packet.exception);
last_exception = std::move(packet.exception);
onReceiveExceptionFromServer(*packet.exception);
last_exception_received_from_server = std::move(packet.exception);
return false;
case Protocol::Server::Log:
@ -1342,8 +1379,8 @@ private:
return true;
case Protocol::Server::Exception:
onException(*packet.exception);
last_exception = std::move(packet.exception);
onReceiveExceptionFromServer(*packet.exception);
last_exception_received_from_server = std::move(packet.exception);
return false;
case Protocol::Server::Log:
@ -1376,8 +1413,8 @@ private:
return true;
case Protocol::Server::Exception:
onException(*packet.exception);
last_exception = std::move(packet.exception);
onReceiveExceptionFromServer(*packet.exception);
last_exception_received_from_server = std::move(packet.exception);
return false;
case Protocol::Server::Log:
@ -1660,10 +1697,10 @@ private:
}
void onException(const Exception & e)
void onReceiveExceptionFromServer(const Exception & e)
{
resetOutput();
got_exception = true;
received_exception_from_server = true;
actual_server_error = e.code();
if (expected_server_error)
@ -1875,6 +1912,12 @@ public:
/// Parse main commandline options.
po::parsed_options parsed = po::command_line_parser(common_arguments).options(main_description).run();
auto unrecognized_options = po::collect_unrecognized(parsed.options, po::collect_unrecognized_mode::include_positional);
// unrecognized_options[0] is "", I don't understand why we need "" as the first argument which unused
if (unrecognized_options.size() > 1)
{
throw Exception("Unrecognized option '" + unrecognized_options[1] + "'", ErrorCodes::UNRECOGNIZED_ARGUMENTS);
}
po::variables_map options;
po::store(parsed, options);
po::notify(options);
@ -2033,6 +2076,12 @@ int mainEntryClickHouseClient(int argc, char ** argv)
std::cerr << "Bad arguments: " << e.what() << std::endl;
return 1;
}
catch (const DB::Exception & e)
{
std::string text = e.displayText();
std::cerr << "Code: " << e.code() << ". " << text << std::endl;
return 1;
}
catch (...)
{
std::cerr << DB::getCurrentExceptionMessage(true) << std::endl;

View File

@ -1,4 +1,6 @@
#include "ClusterCopierApp.h"
#include <Common/StatusFile.h>
namespace DB
{
@ -91,7 +93,7 @@ void ClusterCopierApp::defineOptions(Poco::Util::OptionSet & options)
void ClusterCopierApp::mainImpl()
{
StatusFile status_file(process_path + "/status");
StatusFile status_file(process_path + "/status", StatusFile::write_full_info);
ThreadStatus thread_status;
auto * log = &logger();

View File

@ -66,7 +66,6 @@
#include <Dictionaries/registerDictionaries.h>
#include <Disks/registerDisks.h>
#include <Databases/DatabaseMemory.h>
#include <Common/StatusFile.h>
#include "Aliases.h"

View File

@ -78,10 +78,12 @@ void LocalServer::initialize(Poco::Util::Application & self)
config().add(loaded_config.configuration.duplicate(), PRIO_DEFAULT, false);
}
if (config().has("logger") || config().has("logger.level") || config().has("logger.log"))
if (config().has("logger.console") || config().has("logger.level") || config().has("logger.log"))
{
// force enable logging
config().setString("logger", "logger");
// sensitive data rules are not used here
buildLoggers(config(), logger(), self.commandName());
buildLoggers(config(), logger(), "clickhouse-local");
}
else
{
@ -130,7 +132,7 @@ void LocalServer::tryInitPath()
// This is a directory that is left by a previous run of
// clickhouse-local that had the same pid and did not complete
// correctly. Remove it, with an additional sanity check.
if (default_path.parent_path() != tmp)
if (!std::filesystem::equivalent(default_path.parent_path(), tmp))
{
throw Exception(ErrorCodes::LOGICAL_ERROR,
"The temporary directory of clickhouse-local '{}' is not"
@ -246,7 +248,7 @@ try
if (!context->getPath().empty())
{
/// Lock path directory before read
status.emplace(context->getPath() + "status");
status.emplace(context->getPath() + "status", StatusFile::write_full_info);
LOG_DEBUG(log, "Loading metadata from {}", context->getPath());
loadMetadataSystem(*context);
@ -265,6 +267,7 @@ try
context->shutdown();
context.reset();
status.reset();
cleanup();
return Application::EXIT_OK;
@ -431,7 +434,7 @@ void LocalServer::cleanup()
const auto dir = *temporary_directory_to_delete;
temporary_directory_to_delete.reset();
if (dir.parent_path() != tmp)
if (!std::filesystem::equivalent(dir.parent_path(), tmp))
{
throw Exception(ErrorCodes::LOGICAL_ERROR,
"The temporary directory of clickhouse-local '{}' is not inside"
@ -498,6 +501,7 @@ void LocalServer::init(int argc, char ** argv)
("stacktrace", "print stack traces of exceptions")
("echo", "print query before execution")
("verbose", "print query and other debugging info")
("logger.console", po::value<bool>()->implicit_value(true), "Log to console")
("logger.log", po::value<std::string>(), "Log file name")
("logger.level", po::value<std::string>(), "Log level")
("ignore-error", "do not stop processing if a query failed")
@ -553,6 +557,8 @@ void LocalServer::init(int argc, char ** argv)
config().setBool("echo", true);
if (options.count("verbose"))
config().setBool("verbose", true);
if (options.count("logger.console"))
config().setBool("logger.console", options["logger.console"].as<bool>());
if (options.count("logger.log"))
config().setString("logger.log", options["logger.log"].as<std::string>());
if (options.count("logger.level"))

View File

@ -378,7 +378,7 @@ int Server::main(const std::vector<std::string> & /*args*/)
global_context->setPath(path);
StatusFile status{path + "status"};
StatusFile status{path + "status", StatusFile::write_full_info};
SCOPE_EXIT({
/** Ask to cancel background jobs all table engines,
@ -781,7 +781,7 @@ int Server::main(const std::vector<std::string> & /*args*/)
if (!hasLinuxCapability(CAP_SYS_NICE))
{
LOG_INFO(log, "It looks like the process has no CAP_SYS_NICE capability, the setting 'os_thread_nice' will have no effect."
LOG_INFO(log, "It looks like the process has no CAP_SYS_NICE capability, the setting 'os_thread_priority' will have no effect."
" It could happen due to incorrect ClickHouse package installation."
" You could resolve the problem manually with 'sudo setcap cap_sys_nice=+ep {}'."
" Note that it will not work on 'nosuid' mounted filesystems.",
@ -861,7 +861,8 @@ int Server::main(const std::vector<std::string> & /*args*/)
};
/// This object will periodically calculate some metrics.
AsynchronousMetrics async_metrics(*global_context);
AsynchronousMetrics async_metrics(*global_context,
config().getUInt("asynchronous_metrics_update_period_s", 60));
attachSystemTablesAsync(*DatabaseCatalog::instance().getSystemDatabase(), async_metrics);
for (const auto & listen_host : listen_hosts)

View File

@ -66,7 +66,6 @@
<https_port>8443</https_port>
<tcp_port_secure>9440</tcp_port_secure>
-->
<!-- Used with https_port and tcp_port_secure. Full ssl options list: https://github.com/ClickHouse-Extras/poco/blob/master/NetSSL_OpenSSL/include/Poco/Net/SSLManager.h#L71 -->
<openSSL>
<server> <!-- Used for https server AND secure tcp port -->

View File

@ -40,27 +40,8 @@ class AccessControlManager::ContextAccessCache
public:
explicit ContextAccessCache(const AccessControlManager & manager_) : manager(manager_) {}
std::shared_ptr<const ContextAccess> getContextAccess(
const UUID & user_id,
const boost::container::flat_set<UUID> & current_roles,
bool use_default_roles,
const Settings & settings,
const String & current_database,
const ClientInfo & client_info)
std::shared_ptr<const ContextAccess> getContextAccess(const ContextAccessParams & params)
{
ContextAccess::Params params;
params.user_id = user_id;
params.current_roles = current_roles;
params.use_default_roles = use_default_roles;
params.current_database = current_database;
params.readonly = settings.readonly;
params.allow_ddl = settings.allow_ddl;
params.allow_introspection = settings.allow_introspection_functions;
params.interface = client_info.interface;
params.http_method = client_info.http_method;
params.address = client_info.current_address.host();
params.quota_key = client_info.quota_key;
std::lock_guard lock{mutex};
auto x = cache.get(params);
if (x)
@ -119,7 +100,25 @@ std::shared_ptr<const ContextAccess> AccessControlManager::getContextAccess(
const String & current_database,
const ClientInfo & client_info) const
{
return context_access_cache->getContextAccess(user_id, current_roles, use_default_roles, settings, current_database, client_info);
ContextAccessParams params;
params.user_id = user_id;
params.current_roles = current_roles;
params.use_default_roles = use_default_roles;
params.current_database = current_database;
params.readonly = settings.readonly;
params.allow_ddl = settings.allow_ddl;
params.allow_introspection = settings.allow_introspection_functions;
params.interface = client_info.interface;
params.http_method = client_info.http_method;
params.address = client_info.current_address.host();
params.quota_key = client_info.quota_key;
return getContextAccess(params);
}
std::shared_ptr<const ContextAccess> AccessControlManager::getContextAccess(const ContextAccessParams & params) const
{
return context_access_cache->getContextAccess(params);
}

View File

@ -21,6 +21,7 @@ namespace Poco
namespace DB
{
class ContextAccess;
struct ContextAccessParams;
struct User;
using UserPtr = std::shared_ptr<const User>;
class EnabledRoles;
@ -58,6 +59,8 @@ public:
const String & current_database,
const ClientInfo & client_info) const;
std::shared_ptr<const ContextAccess> getContextAccess(const ContextAccessParams & params) const;
std::shared_ptr<const EnabledRoles> getEnabledRoles(
const boost::container::flat_set<UUID> & current_roles,
const boost::container::flat_set<UUID> & current_roles_with_admin_option) const;

View File

@ -1,7 +1,9 @@
#include <Access/AccessRights.h>
#include <Common/Exception.h>
#include <common/logger_useful.h>
#include <boost/container/small_vector.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <unordered_map>
namespace DB
@ -9,7 +11,6 @@ namespace DB
namespace ErrorCodes
{
extern const int INVALID_GRANT;
extern const int LOGICAL_ERROR;
}
@ -58,12 +59,194 @@ namespace
const AccessFlags system_reload_embedded_dictionaries = AccessType::SYSTEM_RELOAD_EMBEDDED_DICTIONARIES;
};
std::string_view checkCurrentDatabase(const std::string_view & current_database)
using Kind = AccessRightsElementWithOptions::Kind;
struct ProtoElement
{
if (current_database.empty())
throw Exception("No current database", ErrorCodes::LOGICAL_ERROR);
return current_database;
AccessFlags access_flags;
boost::container::small_vector<std::string_view, 3> full_name;
bool grant_option = false;
Kind kind = Kind::GRANT;
friend bool operator<(const ProtoElement & left, const ProtoElement & right)
{
static constexpr auto compare_name = [](const boost::container::small_vector<std::string_view, 3> & left_name,
const boost::container::small_vector<std::string_view, 3> & right_name,
size_t i)
{
if (i < left_name.size())
{
if (i < right_name.size())
return left_name[i].compare(right_name[i]);
else
return 1; /// left_name is longer => left_name > right_name
}
else if (i < right_name.size())
return 1; /// right_name is longer => left < right
else
return 0; /// left_name == right_name
};
if (int cmp = compare_name(left.full_name, right.full_name, 0))
return cmp < 0;
if (int cmp = compare_name(left.full_name, right.full_name, 1))
return cmp < 0;
if (left.kind != right.kind)
return (left.kind == Kind::GRANT);
if (left.grant_option != right.grant_option)
return right.grant_option;
if (int cmp = compare_name(left.full_name, right.full_name, 2))
return cmp < 0;
return (left.access_flags < right.access_flags);
}
AccessRightsElementWithOptions getResult() const
{
AccessRightsElementWithOptions res;
res.access_flags = access_flags;
res.grant_option = grant_option;
res.kind = kind;
switch (full_name.size())
{
case 0:
{
res.any_database = true;
res.any_table = true;
res.any_column = true;
break;
}
case 1:
{
res.any_database = false;
res.database = full_name[0];
res.any_table = true;
res.any_column = true;
break;
}
case 2:
{
res.any_database = false;
res.database = full_name[0];
res.any_table = false;
res.table = full_name[1];
res.any_column = true;
break;
}
case 3:
{
res.any_database = false;
res.database = full_name[0];
res.any_table = false;
res.table = full_name[1];
res.any_column = false;
res.columns.emplace_back(full_name[2]);
break;
}
}
return res;
}
};
class ProtoElements : public std::vector<ProtoElement>
{
public:
AccessRightsElementsWithOptions getResult() const
{
ProtoElements sorted = *this;
boost::range::sort(sorted);
AccessRightsElementsWithOptions res;
res.reserve(sorted.size());
for (size_t i = 0; i != sorted.size();)
{
size_t count_elements_with_diff_columns = sorted.countElementsWithDifferenceInColumnOnly(i);
if (count_elements_with_diff_columns == 1)
{
/// Easy case: one Element is converted to one AccessRightsElement.
const auto & element = sorted[i];
if (element.access_flags)
res.emplace_back(element.getResult());
++i;
}
else
{
/// Difficult case: multiple Elements are converted to one or multiple AccessRightsElements.
sorted.appendResultWithElementsWithDifferenceInColumnOnly(i, count_elements_with_diff_columns, res);
i += count_elements_with_diff_columns;
}
}
return res;
}
private:
size_t countElementsWithDifferenceInColumnOnly(size_t start) const
{
const auto & start_element = (*this)[start];
if ((start_element.full_name.size() != 3) || (start == size() - 1))
return 1;
auto it = std::find_if(begin() + start + 1, end(), [&](const ProtoElement & element)
{
return (element.full_name.size() != 3) || (element.full_name[0] != start_element.full_name[0])
|| (element.full_name[1] != start_element.full_name[1]) || (element.grant_option != start_element.grant_option)
|| (element.kind != start_element.kind);
});
return it - (begin() + start);
}
/// Collects columns together to write multiple columns into one AccessRightsElement.
/// That procedure allows to output access rights in more compact way,
/// e.g. "SELECT(x, y)" instead of "SELECT(x), SELECT(y)".
void appendResultWithElementsWithDifferenceInColumnOnly(size_t start, size_t count, AccessRightsElementsWithOptions & res) const
{
const auto * pbegin = data() + start;
const auto * pend = pbegin + count;
AccessFlags handled_flags;
while (pbegin < pend)
{
while (pbegin < pend && !(pbegin->access_flags - handled_flags))
++pbegin;
while (pbegin < pend && !((pend - 1)->access_flags - handled_flags))
--pend;
if (pbegin >= pend)
break;
AccessFlags common_flags = (pbegin->access_flags - handled_flags);
for (const auto * element = pbegin + 1; element != pend; ++element)
{
if (auto new_common_flags = (element->access_flags - handled_flags) & common_flags)
common_flags = new_common_flags;
}
res.emplace_back();
auto & back = res.back();
back.grant_option = pbegin->grant_option;
back.kind = pbegin->kind;
back.any_database = false;
back.database = pbegin->full_name[0];
back.any_table = false;
back.table = pbegin->full_name[1];
back.any_column = false;
back.access_flags = common_flags;
for (const auto * element = pbegin; element != pend; ++element)
{
if (((element->access_flags - handled_flags) & common_flags) == common_flags)
back.columns.emplace_back(element->full_name[2]);
}
handled_flags |= common_flags;
}
}
};
}
@ -243,23 +426,43 @@ public:
friend bool operator!=(const Node & left, const Node & right) { return !(left == right); }
void merge(const Node & other, const Helper & helper)
void makeUnion(const Node & other, const Helper & helper)
{
mergeAccessRec(other);
makeUnionRec(other);
calculateFinalAccessRec(helper);
}
void logTree(Poco::Logger * log) const
void makeIntersection(const Node & other, const Helper & helper)
{
LOG_TRACE(log, "Tree({}): name={}, access={}, final_access={}, min_access={}, max_access={}, num_children={}",
level, node_name ? *node_name : "NULL", access.toString(),
makeIntersectionRec(other);
calculateFinalAccessRec(helper);
}
ProtoElements getElements() const
{
ProtoElements res;
getElementsRec(res, {}, *this, {});
return res;
}
static ProtoElements getElements(const Node * node, const Node * node_with_grant_option)
{
ProtoElements res;
getElementsRec(res, {}, node, {}, node_with_grant_option, {});
return res;
}
void logTree(Poco::Logger * log, const String & title) const
{
LOG_TRACE(log, "Tree({}): level={}, name={}, access={}, final_access={}, min_access={}, max_access={}, num_children={}",
title, level, node_name ? *node_name : "NULL", access.toString(),
final_access.toString(), min_access.toString(), max_access.toString(),
(children ? children->size() : 0));
if (children)
{
for (auto & child : *children | boost::adaptors::map_values)
child.logTree(log);
child.logTree(log, title);
}
}
@ -342,6 +545,93 @@ private:
}
}
static void getElementsRec(
ProtoElements & res,
const boost::container::small_vector<std::string_view, 3> & full_name,
const Node & node,
const AccessFlags & parent_access)
{
auto access = node.access;
auto revokes = parent_access - access;
auto grants = access - parent_access;
if (revokes)
res.push_back(ProtoElement{revokes, full_name, false, Kind::REVOKE});
if (grants)
res.push_back(ProtoElement{grants, full_name, false, Kind::GRANT});
if (node.children)
{
for (const auto & [child_name, child] : *node.children)
{
boost::container::small_vector<std::string_view, 3> child_full_name = full_name;
child_full_name.push_back(child_name);
getElementsRec(res, child_full_name, child, access);
}
}
}
static void getElementsRec(
ProtoElements & res,
const boost::container::small_vector<std::string_view, 3> & full_name,
const Node * node,
const AccessFlags & parent_access,
const Node * node_go,
const AccessFlags & parent_access_go)
{
auto access = node ? node->access : parent_access;
auto access_go = node_go ? node_go->access : parent_access_go;
auto revokes = parent_access - access;
auto revokes_go = parent_access_go - access_go - revokes;
auto grants_go = access_go - parent_access_go;
auto grants = access - parent_access - grants_go;
if (revokes)
res.push_back(ProtoElement{revokes, full_name, false, Kind::REVOKE});
if (revokes_go)
res.push_back(ProtoElement{revokes_go, full_name, true, Kind::REVOKE});
if (grants)
res.push_back(ProtoElement{grants, full_name, false, Kind::GRANT});
if (grants_go)
res.push_back(ProtoElement{grants_go, full_name, true, Kind::GRANT});
if (node && node->children)
{
for (const auto & [child_name, child] : *node->children)
{
boost::container::small_vector<std::string_view, 3> child_full_name = full_name;
child_full_name.push_back(child_name);
const Node * child_node = &child;
const Node * child_node_go = nullptr;
if (node_go && node_go->children)
{
auto it = node_go->children->find(child_name);
if (it != node_go->children->end())
child_node_go = &it->second;
}
getElementsRec(res, child_full_name, child_node, access, child_node_go, access_go);
}
}
if (node_go && node_go->children)
{
for (const auto & [child_name, child] : *node_go->children)
{
if (node && node->children && node->children->count(child_name))
continue; /// already processed
boost::container::small_vector<std::string_view, 3> child_full_name = full_name;
child_full_name.push_back(child_name);
const Node * child_node = nullptr;
const Node * child_node_go = &child;
getElementsRec(res, child_full_name, child_node, access, child_node_go, access_go);
}
}
}
void calculateFinalAccessRec(const Helper & helper)
{
/// Traverse tree.
@ -438,12 +728,12 @@ private:
max_access = final_access | max_access_among_children;
}
void mergeAccessRec(const Node & rhs)
void makeUnionRec(const Node & rhs)
{
if (rhs.children)
{
for (const auto & [rhs_childname, rhs_child] : *rhs.children)
getChild(rhs_childname).mergeAccessRec(rhs_child);
getChild(rhs_childname).makeUnionRec(rhs_child);
}
access |= rhs.access;
if (children)
@ -455,6 +745,24 @@ private:
}
}
}
void makeIntersectionRec(const Node & rhs)
{
if (rhs.children)
{
for (const auto & [rhs_childname, rhs_child] : *rhs.children)
getChild(rhs_childname).makeIntersectionRec(rhs_child);
}
access &= rhs.access;
if (children)
{
for (auto & [lhs_childname, lhs_child] : *children)
{
if (!rhs.tryGetChild(lhs_childname))
lhs_child.access &= rhs.access;
}
}
}
};
@ -476,6 +784,10 @@ AccessRights & AccessRights::operator =(const AccessRights & src)
root = std::make_unique<Node>(*src.root);
else
root = nullptr;
if (src.root_with_grant_option)
root_with_grant_option = std::make_unique<Node>(*src.root_with_grant_option);
else
root_with_grant_option = nullptr;
return *this;
}
@ -488,302 +800,267 @@ AccessRights::AccessRights(const AccessFlags & access)
bool AccessRights::isEmpty() const
{
return !root;
return !root && !root_with_grant_option;
}
void AccessRights::clear()
{
root = nullptr;
root_with_grant_option = nullptr;
}
template <typename... Args>
template <bool with_grant_option, typename... Args>
void AccessRights::grantImpl(const AccessFlags & flags, const Args &... args)
{
if (!root)
root = std::make_unique<Node>();
root->grant(flags, Helper::instance(), args...);
if (!root->access && !root->children)
root = nullptr;
auto helper = [&](std::unique_ptr<Node> & root_node)
{
if (!root_node)
root_node = std::make_unique<Node>();
root_node->grant(flags, Helper::instance(), args...);
if (!root_node->access && !root_node->children)
root_node = nullptr;
};
helper(root);
if constexpr (with_grant_option)
helper(root_with_grant_option);
}
void AccessRights::grant(const AccessFlags & flags) { grantImpl(flags); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database) { grantImpl(flags, database); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { grantImpl(flags, database, table); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { grantImpl(flags, database, table, column); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { grantImpl(flags, database, table, columns); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { grantImpl(flags, database, table, columns); }
void AccessRights::grant(const AccessRightsElement & element, std::string_view current_database)
template <bool with_grant_option>
void AccessRights::grantImpl(const AccessRightsElement & element)
{
if (element.any_database)
{
grant(element.access_flags);
}
grantImpl<with_grant_option>(element.access_flags);
else if (element.any_table)
{
if (element.database.empty())
grant(element.access_flags, checkCurrentDatabase(current_database));
else
grant(element.access_flags, element.database);
}
grantImpl<with_grant_option>(element.access_flags, element.database);
else if (element.any_column)
{
if (element.database.empty())
grant(element.access_flags, checkCurrentDatabase(current_database), element.table);
grantImpl<with_grant_option>(element.access_flags, element.database, element.table);
else
grant(element.access_flags, element.database, element.table);
}
else
{
if (element.database.empty())
grant(element.access_flags, checkCurrentDatabase(current_database), element.table, element.columns);
else
grant(element.access_flags, element.database, element.table, element.columns);
}
grantImpl<with_grant_option>(element.access_flags, element.database, element.table, element.columns);
}
void AccessRights::grant(const AccessRightsElements & elements, std::string_view current_database)
template <bool with_grant_option>
void AccessRights::grantImpl(const AccessRightsElements & elements)
{
for (const auto & element : elements)
grant(element, current_database);
grantImpl<with_grant_option>(element);
}
void AccessRights::grant(const AccessFlags & flags) { grantImpl<false>(flags); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database) { grantImpl<false>(flags, database); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { grantImpl<false>(flags, database, table); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { grantImpl<false>(flags, database, table, column); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { grantImpl<false>(flags, database, table, columns); }
void AccessRights::grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { grantImpl<false>(flags, database, table, columns); }
void AccessRights::grant(const AccessRightsElement & element) { grantImpl<false>(element); }
void AccessRights::grant(const AccessRightsElements & elements) { grantImpl<false>(elements); }
template <typename... Args>
void AccessRights::grantWithGrantOption(const AccessFlags & flags) { grantImpl<true>(flags); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database) { grantImpl<true>(flags, database); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { grantImpl<true>(flags, database, table); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { grantImpl<true>(flags, database, table, column); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { grantImpl<true>(flags, database, table, columns); }
void AccessRights::grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { grantImpl<true>(flags, database, table, columns); }
void AccessRights::grantWithGrantOption(const AccessRightsElement & element) { grantImpl<true>(element); }
void AccessRights::grantWithGrantOption(const AccessRightsElements & elements) { grantImpl<true>(elements); }
template <bool grant_option, typename... Args>
void AccessRights::revokeImpl(const AccessFlags & flags, const Args &... args)
{
if (!root)
auto helper = [&](std::unique_ptr<Node> & root_node)
{
if (!root_node)
return;
root->revoke(flags, Helper::instance(), args...);
if (!root->access && !root->children)
root = nullptr;
root_node->revoke(flags, Helper::instance(), args...);
if (!root_node->access && !root_node->children)
root_node = nullptr;
};
helper(root_with_grant_option);
if constexpr (!grant_option)
helper(root);
}
void AccessRights::revoke(const AccessFlags & flags) { revokeImpl(flags); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database) { revokeImpl(flags, database); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { revokeImpl(flags, database, table); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { revokeImpl(flags, database, table, column); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { revokeImpl(flags, database, table, columns); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { revokeImpl(flags, database, table, columns); }
void AccessRights::revoke(const AccessRightsElement & element, std::string_view current_database)
template <bool grant_option>
void AccessRights::revokeImpl(const AccessRightsElement & element)
{
if (element.any_database)
{
revoke(element.access_flags);
}
revokeImpl<grant_option>(element.access_flags);
else if (element.any_table)
{
if (element.database.empty())
revoke(element.access_flags, checkCurrentDatabase(current_database));
else
revoke(element.access_flags, element.database);
}
revokeImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
{
if (element.database.empty())
revoke(element.access_flags, checkCurrentDatabase(current_database), element.table);
revokeImpl<grant_option>(element.access_flags, element.database, element.table);
else
revoke(element.access_flags, element.database, element.table);
}
else
{
if (element.database.empty())
revoke(element.access_flags, checkCurrentDatabase(current_database), element.table, element.columns);
else
revoke(element.access_flags, element.database, element.table, element.columns);
}
revokeImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
void AccessRights::revoke(const AccessRightsElements & elements, std::string_view current_database)
template <bool grant_option>
void AccessRights::revokeImpl(const AccessRightsElements & elements)
{
for (const auto & element : elements)
revoke(element, current_database);
revokeImpl<grant_option>(element);
}
void AccessRights::revoke(const AccessFlags & flags) { revokeImpl<false>(flags); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database) { revokeImpl<false>(flags, database); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { revokeImpl<false>(flags, database, table); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { revokeImpl<false>(flags, database, table, column); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { revokeImpl<false>(flags, database, table, columns); }
void AccessRights::revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { revokeImpl<false>(flags, database, table, columns); }
void AccessRights::revoke(const AccessRightsElement & element) { revokeImpl<false>(element); }
void AccessRights::revoke(const AccessRightsElements & elements) { revokeImpl<false>(elements); }
AccessRightsElements AccessRights::getGrants() const
{
AccessRightsElements grants;
getGrantsAndPartialRevokesImpl(&grants, nullptr);
return grants;
}
AccessRightsElements AccessRights::getPartialRevokes() const
{
AccessRightsElements partial_revokes;
getGrantsAndPartialRevokesImpl(nullptr, &partial_revokes);
return partial_revokes;
}
AccessRights::GrantsAndPartialRevokes AccessRights::getGrantsAndPartialRevokes() const
{
GrantsAndPartialRevokes res;
getGrantsAndPartialRevokesImpl(&res.grants, &res.revokes);
return res;
}
void AccessRights::getGrantsAndPartialRevokesImpl(AccessRightsElements * out_grants, AccessRightsElements * out_partial_revokes) const
void AccessRights::revokeGrantOption(const AccessFlags & flags) { revokeImpl<true>(flags); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database) { revokeImpl<true>(flags, database); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) { revokeImpl<true>(flags, database, table); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) { revokeImpl<true>(flags, database, table, column); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) { revokeImpl<true>(flags, database, table, columns); }
void AccessRights::revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) { revokeImpl<true>(flags, database, table, columns); }
void AccessRights::revokeGrantOption(const AccessRightsElement & element) { revokeImpl<true>(element); }
void AccessRights::revokeGrantOption(const AccessRightsElements & elements) { revokeImpl<true>(elements); }
AccessRightsElementsWithOptions AccessRights::getElements() const
{
#if 0
logTree();
#endif
if (!root)
return;
auto global_access = root->access;
if (out_grants && global_access)
out_grants->push_back({global_access});
if (root->children)
{
for (const auto & [db_name, db_node] : *root->children)
{
if (out_grants)
{
if (auto db_grants = db_node.access - global_access)
out_grants->push_back({db_grants, db_name});
}
if (out_partial_revokes)
{
if (auto db_partial_revokes = global_access - db_node.access)
out_partial_revokes->push_back({db_partial_revokes, db_name});
}
if (db_node.children)
{
for (const auto & [table_name, table_node] : *db_node.children)
{
if (out_grants)
{
if (auto table_grants = table_node.access - db_node.access)
out_grants->push_back({table_grants, db_name, table_name});
}
if (out_partial_revokes)
{
if (auto table_partial_revokes = db_node.access - table_node.access)
out_partial_revokes->push_back({table_partial_revokes, db_name, table_name});
}
if (table_node.children)
{
for (const auto & [column_name, column_node] : *table_node.children)
{
if (out_grants)
{
if (auto column_grants = column_node.access - table_node.access)
out_grants->push_back({column_grants, db_name, table_name, column_name});
}
if (out_partial_revokes)
{
if (auto column_partial_revokes = table_node.access - column_node.access)
out_partial_revokes->push_back({column_partial_revokes, db_name, table_name, column_name});
}
}
}
}
}
}
}
return {};
if (!root_with_grant_option)
return root->getElements().getResult();
return Node::getElements(root.get(), root_with_grant_option.get()).getResult();
}
String AccessRights::toString() const
{
String res;
auto gr = getGrantsAndPartialRevokes();
if (!gr.grants.empty())
{
res += "GRANT ";
res += gr.grants.toString();
}
if (!gr.revokes.empty())
{
if (!res.empty())
res += ", ";
res += "REVOKE ";
res += gr.revokes.toString();
}
if (res.empty())
res = "GRANT USAGE ON *.*";
return res;
return getElements().toString();
}
template <typename... Args>
template <bool grant_option, typename... Args>
bool AccessRights::isGrantedImpl(const AccessFlags & flags, const Args &... args) const
{
if (!root)
auto helper = [&](const std::unique_ptr<Node> & root_node) -> bool
{
if (!root_node)
return flags.isEmpty();
return root->isGranted(flags, args...);
return root_node->isGranted(flags, args...);
};
if constexpr (grant_option)
return helper(root_with_grant_option);
else
return helper(root);
}
bool AccessRights::isGranted(const AccessFlags & flags) const { return isGrantedImpl(flags); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl(flags, database); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl(flags, database, table); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl(flags, database, table, column); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessRightsElement & element, std::string_view current_database) const
template <bool grant_option>
bool AccessRights::isGrantedImpl(const AccessRightsElement & element) const
{
if (element.any_database)
{
return isGranted(element.access_flags);
}
return isGrantedImpl<grant_option>(element.access_flags);
else if (element.any_table)
{
if (element.database.empty())
return isGranted(element.access_flags, checkCurrentDatabase(current_database));
else
return isGranted(element.access_flags, element.database);
}
return isGrantedImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
{
if (element.database.empty())
return isGranted(element.access_flags, checkCurrentDatabase(current_database), element.table);
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table);
else
return isGranted(element.access_flags, element.database, element.table);
}
else
{
if (element.database.empty())
return isGranted(element.access_flags, checkCurrentDatabase(current_database), element.table, element.columns);
else
return isGranted(element.access_flags, element.database, element.table, element.columns);
}
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
bool AccessRights::isGranted(const AccessRightsElements & elements, std::string_view current_database) const
template <bool grant_option>
bool AccessRights::isGrantedImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!isGranted(element, current_database))
if (!isGrantedImpl<grant_option>(element))
return false;
return true;
}
bool AccessRights::isGranted(const AccessFlags & flags) const { return isGrantedImpl<false>(flags); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<false>(flags, database); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<false>(flags, database, table); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<false>(flags, database, table, column); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool AccessRights::isGranted(const AccessRightsElement & element) const { return isGrantedImpl<false>(element); }
bool AccessRights::isGranted(const AccessRightsElements & elements) const { return isGrantedImpl<false>(elements); }
bool AccessRights::hasGrantOption(const AccessFlags & flags) const { return isGrantedImpl<true>(flags); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<true>(flags, database); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<true>(flags, database, table); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<true>(flags, database, table, column); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool AccessRights::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool AccessRights::hasGrantOption(const AccessRightsElement & element) const { return isGrantedImpl<true>(element); }
bool AccessRights::hasGrantOption(const AccessRightsElements & elements) const { return isGrantedImpl<true>(elements); }
bool operator ==(const AccessRights & left, const AccessRights & right)
{
if (!left.root)
return !right.root;
if (!right.root)
auto helper = [](const std::unique_ptr<AccessRights::Node> & left_node, const std::unique_ptr<AccessRights::Node> & right_node)
{
if (!left_node)
return !right_node;
if (!right_node)
return false;
return *left.root == *right.root;
return *left_node == *right_node;
};
return helper(left.root, right.root) && helper(left.root_with_grant_option, right.root_with_grant_option);
}
void AccessRights::merge(const AccessRights & other)
void AccessRights::makeUnion(const AccessRights & other)
{
if (!root)
auto helper = [](std::unique_ptr<Node> & root_node, const std::unique_ptr<Node> & other_root_node)
{
*this = other;
if (!root_node)
{
if (other_root_node)
root_node = std::make_unique<Node>(*other_root_node);
return;
}
if (other.root)
if (other_root_node)
{
root->merge(*other.root, Helper::instance());
if (!root->access && !root->children)
root = nullptr;
root_node->makeUnion(*other_root_node, Helper::instance());
if (!root_node->access && !root_node->children)
root_node = nullptr;
}
};
helper(root, other.root);
helper(root_with_grant_option, other.root_with_grant_option);
}
void AccessRights::makeIntersection(const AccessRights & other)
{
auto helper = [](std::unique_ptr<Node> & root_node, const std::unique_ptr<Node> & other_root_node)
{
if (!root_node)
{
if (other_root_node)
root_node = std::make_unique<Node>(*other_root_node);
return;
}
if (other_root_node)
{
root_node->makeIntersection(*other_root_node, Helper::instance());
if (!root_node->access && !root_node->children)
root_node = nullptr;
}
};
helper(root, other.root);
helper(root_with_grant_option, other.root_with_grant_option);
}
AccessRights AccessRights::getFullAccess()
{
AccessRights res;
res.grantWithGrantOption(AccessType::ALL);
return res;
}
@ -791,7 +1068,11 @@ void AccessRights::logTree() const
{
auto * log = &Poco::Logger::get("AccessRights");
if (root)
root->logTree(log);
{
root->logTree(log, "");
if (root_with_grant_option)
root->logTree(log, "go");
}
else
LOG_TRACE(log, "Tree: NULL");
}

View File

@ -26,6 +26,12 @@ public:
/// Revokes everything. It's the same as revoke(AccessType::ALL).
void clear();
/// Returns the information about all the access granted as a string.
String toString() const;
/// Returns the information about all the access granted.
AccessRightsElementsWithOptions getElements() const;
/// Grants access on a specified database/table/column.
/// Does nothing if the specified access has been already granted.
void grant(const AccessFlags & flags);
@ -34,8 +40,17 @@ public:
void grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void grant(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void grant(const AccessRightsElement & element, std::string_view current_database = {});
void grant(const AccessRightsElements & elements, std::string_view current_database = {});
void grant(const AccessRightsElement & element);
void grant(const AccessRightsElements & elements);
void grantWithGrantOption(const AccessFlags & flags);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void grantWithGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void grantWithGrantOption(const AccessRightsElement & element);
void grantWithGrantOption(const AccessRightsElements & elements);
/// Revokes a specified access granted earlier on a specified database/table/column.
/// For example, revoke(AccessType::ALL) revokes all grants at all, just like clear();
@ -45,21 +60,17 @@ public:
void revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void revoke(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void revoke(const AccessRightsElement & element, std::string_view current_database = {});
void revoke(const AccessRightsElements & elements, std::string_view current_database = {});
void revoke(const AccessRightsElement & element);
void revoke(const AccessRightsElements & elements);
/// Returns the information about all the access granted.
struct GrantsAndPartialRevokes
{
AccessRightsElements grants;
AccessRightsElements revokes;
};
AccessRightsElements getGrants() const;
AccessRightsElements getPartialRevokes() const;
GrantsAndPartialRevokes getGrantsAndPartialRevokes() const;
/// Returns the information about all the access granted as a string.
String toString() const;
void revokeGrantOption(const AccessFlags & flags);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns);
void revokeGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns);
void revokeGrantOption(const AccessRightsElement & element);
void revokeGrantOption(const AccessRightsElements & elements);
/// Whether a specified access granted.
bool isGranted(const AccessFlags & flags) const;
@ -68,38 +79,62 @@ public:
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool isGranted(const AccessRightsElement & element, std::string_view current_database = {}) const;
bool isGranted(const AccessRightsElements & elements, std::string_view current_database = {}) const;
bool isGranted(const AccessRightsElement & element) const;
bool isGranted(const AccessRightsElements & elements) const;
bool hasGrantOption(const AccessFlags & flags) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool hasGrantOption(const AccessRightsElement & element) const;
bool hasGrantOption(const AccessRightsElements & elements) const;
/// Merges two sets of access rights together.
/// It's used to combine access rights from multiple roles.
void makeUnion(const AccessRights & other);
void makeIntersection(const AccessRights & other);
friend bool operator ==(const AccessRights & left, const AccessRights & right);
friend bool operator !=(const AccessRights & left, const AccessRights & right) { return !(left == right); }
/// Merges two sets of access rights together.
/// It's used to combine access rights from multiple roles.
void merge(const AccessRights & other);
static AccessRights getFullAccess();
private:
template <typename... Args>
template <bool with_grant_option, typename... Args>
void grantImpl(const AccessFlags & flags, const Args &... args);
template <typename... Args>
template <bool with_grant_options>
void grantImpl(const AccessRightsElement & element);
template <bool with_grant_options>
void grantImpl(const AccessRightsElements & elements);
template <bool grant_option, typename... Args>
void revokeImpl(const AccessFlags & flags, const Args &... args);
template <typename... Args>
template <bool grant_option>
void revokeImpl(const AccessRightsElement & element);
template <bool grant_option>
void revokeImpl(const AccessRightsElements & elements);
template <bool grant_option, typename... Args>
bool isGrantedImpl(const AccessFlags & flags, const Args &... args) const;
bool isGrantedImpl(const AccessRightsElement & element, std::string_view current_database) const;
bool isGrantedImpl(const AccessRightsElements & elements, std::string_view current_database) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElement & element) const;
template <typename... Args>
AccessFlags getAccessImpl(const Args &... args) const;
void getGrantsAndPartialRevokesImpl(AccessRightsElements * grants, AccessRightsElements * partial_revokes) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElements & elements) const;
void logTree() const;
struct Node;
std::unique_ptr<Node> root;
std::unique_ptr<Node> root_with_grant_option;
};
}

View File

@ -12,137 +12,30 @@ namespace DB
{
namespace
{
size_t groupElements(AccessRightsElements & elements, size_t start)
{
auto & start_element = elements[start];
auto it = std::find_if(elements.begin() + start + 1, elements.end(),
[&](const AccessRightsElement & element)
{
return (element.database != start_element.database) ||
(element.any_database != start_element.any_database) ||
(element.table != start_element.table) ||
(element.any_table != start_element.any_table) ||
(element.any_column != start_element.any_column);
});
size_t end = it - elements.begin();
using Kind = AccessRightsElementWithOptions::Kind;
/// All the elements at indices from start to end here specify
/// the same database and table.
if (start_element.any_column)
String formatOptions(bool grant_option, Kind kind, const String & inner_part)
{
/// Easy case: the elements don't specify columns.
/// All we need is to combine the access flags.
for (size_t i = start + 1; i != end; ++i)
if (kind == Kind::REVOKE)
{
start_element.access_flags |= elements[i].access_flags;
elements[i].access_flags = {};
if (grant_option)
return "REVOKE GRANT OPTION " + inner_part;
else
return "REVOKE " + inner_part;
}
return end;
}
/// Difficult case: the elements specify columns.
/// We have to find groups of columns with common access flags.
for (size_t i = start; i != end; ++i)
else
{
if (!elements[i].access_flags)
continue;
AccessFlags common_flags = elements[i].access_flags;
size_t num_elements_with_common_flags = 1;
for (size_t j = i + 1; j != end; ++j)
{
auto new_common_flags = common_flags & elements[j].access_flags;
if (new_common_flags)
{
common_flags = new_common_flags;
++num_elements_with_common_flags;
if (grant_option)
return "GRANT " + inner_part + " WITH GRANT OPTION";
else
return "GRANT " + inner_part;
}
}
if (num_elements_with_common_flags == 1)
continue;
if (elements[i].access_flags != common_flags)
String formatONClause(const String & database, bool any_database, const String & table, bool any_table)
{
elements.insert(elements.begin() + i + 1, elements[i]);
elements[i].access_flags = common_flags;
elements[i].columns.clear();
++end;
}
for (size_t j = i + 1; j != end; ++j)
{
if ((elements[j].access_flags & common_flags) == common_flags)
{
boost::range::push_back(elements[i].columns, elements[j].columns);
elements[j].access_flags -= common_flags;
}
}
}
return end;
}
/// Tries to combine elements to decrease their number.
void groupElements(AccessRightsElements & elements)
{
if (!boost::range::is_sorted(elements))
boost::range::sort(elements); /// Algorithm in groupElement() requires elements to be sorted.
for (size_t start = 0; start != elements.size();)
start = groupElements(elements, start);
}
/// Removes unnecessary elements, sorts elements and makes them unique.
void sortElementsAndMakeUnique(AccessRightsElements & elements)
{
/// Remove empty elements.
boost::range::remove_erase_if(elements, [](const AccessRightsElement & element)
{
return !element.access_flags || (!element.any_column && element.columns.empty());
});
/// Sort columns and make them unique.
for (auto & element : elements)
{
if (element.any_column)
continue;
if (!boost::range::is_sorted(element.columns))
boost::range::sort(element.columns);
element.columns.erase(std::unique(element.columns.begin(), element.columns.end()), element.columns.end());
}
/// Sort elements themselves.
boost::range::sort(elements);
elements.erase(std::unique(elements.begin(), elements.end()), elements.end());
}
}
void AccessRightsElement::setDatabase(const String & new_database)
{
database = new_database;
any_database = false;
}
void AccessRightsElement::replaceEmptyDatabase(const String & new_database)
{
if (isEmptyDatabase())
setDatabase(new_database);
}
bool AccessRightsElement::isEmptyDatabase() const
{
return !any_database && database.empty();
}
String AccessRightsElement::toString() const
{
String msg = toStringWithoutON();
msg += " ON ";
String msg = "ON ";
if (any_database)
msg += "*.";
@ -156,7 +49,8 @@ String AccessRightsElement::toString() const
return msg;
}
String AccessRightsElement::toStringWithoutON() const
String formatAccessFlagsWithColumns(const AccessFlags & access_flags, const Strings & columns, bool any_column)
{
String columns_in_parentheses;
if (!any_column)
@ -184,50 +78,92 @@ String AccessRightsElement::toStringWithoutON() const
}
return msg;
}
void AccessRightsElements::replaceEmptyDatabase(const String & new_database)
{
for (auto & element : *this)
element.replaceEmptyDatabase(new_database);
}
String AccessRightsElements::toString()
String AccessRightsElement::toString() const
{
normalize();
return formatAccessFlagsWithColumns(access_flags, columns, any_column) + " " + formatONClause(database, any_database, table, any_table);
}
String AccessRightsElementWithOptions::toString() const
{
return formatOptions(grant_option, kind, AccessRightsElement::toString());
}
String AccessRightsElements::toString() const
{
if (empty())
return "USAGE ON *.*";
String msg;
bool need_comma = false;
String res;
String inner_part;
for (size_t i = 0; i != size(); ++i)
{
const auto & element = (*this)[i];
if (std::exchange(need_comma, true))
msg += ", ";
bool next_element_on_same_db_and_table = false;
if (!inner_part.empty())
inner_part += ", ";
inner_part += formatAccessFlagsWithColumns(element.access_flags, element.columns, element.any_column);
bool next_element_uses_same_table = false;
if (i != size() - 1)
{
const auto & next_element = (*this)[i + 1];
if ((element.database == next_element.database) && (element.any_database == next_element.any_database)
&& (element.table == next_element.table) && (element.any_table == next_element.any_table))
next_element_on_same_db_and_table = true;
}
if (next_element_on_same_db_and_table)
msg += element.toStringWithoutON();
else
msg += element.toString();
}
return msg;
if (element.sameDatabaseAndTable(next_element))
next_element_uses_same_table = true;
}
void AccessRightsElements::normalize()
if (!next_element_uses_same_table)
{
groupElements(*this);
sortElementsAndMakeUnique(*this);
if (!res.empty())
res += ", ";
res += inner_part + " " + formatONClause(element.database, element.any_database, element.table, element.any_table);
inner_part.clear();
}
}
return res;
}
String AccessRightsElementsWithOptions::toString() const
{
if (empty())
return "GRANT USAGE ON *.*";
String res;
String inner_part;
for (size_t i = 0; i != size(); ++i)
{
const auto & element = (*this)[i];
if (!inner_part.empty())
inner_part += ", ";
inner_part += formatAccessFlagsWithColumns(element.access_flags, element.columns, element.any_column);
bool next_element_uses_same_mode_and_table = false;
if (i != size() - 1)
{
const auto & next_element = (*this)[i + 1];
if (element.sameDatabaseAndTable(next_element) && element.sameOptions(next_element))
next_element_uses_same_mode_and_table = true;
}
if (!next_element_uses_same_mode_and_table)
{
if (!res.empty())
res += ", ";
res += formatOptions(
element.grant_option,
element.kind,
inner_part + " " + formatONClause(element.database, element.any_database, element.table, element.any_table));
inner_part.clear();
}
}
return res;
}
}

View File

@ -71,26 +71,48 @@ struct AccessRightsElement
{
}
auto toTuple() const { return std::tie(access_flags, database, any_database, table, any_table, columns, any_column); }
auto toTuple() const { return std::tie(access_flags, any_database, database, any_table, table, any_column, columns); }
friend bool operator==(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() == right.toTuple(); }
friend bool operator!=(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() != right.toTuple(); }
friend bool operator<(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() < right.toTuple(); }
friend bool operator>(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() > right.toTuple(); }
friend bool operator<=(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() <= right.toTuple(); }
friend bool operator>=(const AccessRightsElement & left, const AccessRightsElement & right) { return left.toTuple() >= right.toTuple(); }
friend bool operator!=(const AccessRightsElement & left, const AccessRightsElement & right) { return !(left == right); }
/// Sets the database.
void setDatabase(const String & new_database);
bool sameDatabaseAndTable(const AccessRightsElement & other) const
{
return (database == other.database) && (any_database == other.any_database) && (table == other.table)
&& (any_table == other.any_table);
}
bool isEmptyDatabase() const { return !any_database && database.empty(); }
/// If the database is empty, replaces it with `new_database`. Otherwise does nothing.
void replaceEmptyDatabase(const String & new_database);
bool isEmptyDatabase() const;
/// Returns a human-readable representation like "SELECT, UPDATE(x, y) ON db.table".
/// The returned string isn't prefixed with the "GRANT" keyword.
String toString() const;
String toStringWithoutON() const;
};
struct AccessRightsElementWithOptions : public AccessRightsElement
{
bool grant_option = false;
enum class Kind
{
GRANT,
REVOKE,
};
Kind kind = Kind::GRANT;
bool sameOptions(const AccessRightsElementWithOptions & other) const
{
return (grant_option == other.grant_option) && (kind == other.kind);
}
auto toTuple() const { return std::tie(access_flags, any_database, database, any_table, table, any_column, columns, grant_option, kind); }
friend bool operator==(const AccessRightsElementWithOptions & left, const AccessRightsElementWithOptions & right) { return left.toTuple() == right.toTuple(); }
friend bool operator!=(const AccessRightsElementWithOptions & left, const AccessRightsElementWithOptions & right) { return !(left == right); }
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
};
@ -101,13 +123,38 @@ public:
/// Replaces the empty database with `new_database`.
void replaceEmptyDatabase(const String & new_database);
/// Returns a human-readable representation like "SELECT, UPDATE(x, y) ON db.table".
/// The returned string isn't prefixed with the "GRANT" keyword.
String toString() const { return AccessRightsElements(*this).toString(); }
String toString();
/// Reorder and group elements to show them in more readable form.
void normalize();
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
};
class AccessRightsElementsWithOptions : public std::vector<AccessRightsElementWithOptions>
{
public:
/// Replaces the empty database with `new_database`.
void replaceEmptyDatabase(const String & new_database);
/// Returns a human-readable representation like "GRANT SELECT, UPDATE(x, y) ON db.table".
String toString() const;
};
inline void AccessRightsElement::replaceEmptyDatabase(const String & new_database)
{
if (isEmptyDatabase())
database = new_database;
}
inline void AccessRightsElements::replaceEmptyDatabase(const String & new_database)
{
for (auto & element : *this)
element.replaceEmptyDatabase(new_database);
}
inline void AccessRightsElementsWithOptions::replaceEmptyDatabase(const String & new_database)
{
for (auto & element : *this)
element.replaceEmptyDatabase(new_database);
}
}

View File

@ -151,6 +151,7 @@ enum class AccessType
M(FILE, "", GLOBAL, SOURCES) \
M(URL, "", GLOBAL, SOURCES) \
M(REMOTE, "", GLOBAL, SOURCES) \
M(MONGO, "", GLOBAL, SOURCES) \
M(MYSQL, "", GLOBAL, SOURCES) \
M(ODBC, "", GLOBAL, SOURCES) \
M(JDBC, "", GLOBAL, SOURCES) \

View File

@ -15,8 +15,6 @@
#include <Poco/Logger.h>
#include <common/logger_useful.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/range/algorithm/fill.hpp>
#include <boost/range/algorithm/set_algorithm.hpp>
#include <assert.h>
@ -35,61 +33,66 @@ namespace ErrorCodes
namespace
{
enum CheckAccessRightsMode
std::shared_ptr<AccessRights> mixAccessRightsFromUserAndRoles(const User & user, const EnabledRolesInfo & roles_info)
{
RETURN_FALSE_IF_ACCESS_DENIED,
LOG_WARNING_IF_ACCESS_DENIED,
THROW_IF_ACCESS_DENIED,
};
String formatSkippedMessage()
{
return "";
auto res = std::make_shared<AccessRights>(user.access);
res->makeUnion(roles_info.access);
return res;
}
String formatSkippedMessage(const std::string_view & database)
std::shared_ptr<AccessRights> applyParamsToAccessRights(const AccessRights & access, const ContextAccessParams & params)
{
return ". Skipped database " + backQuoteIfNeed(database);
auto res = std::make_shared<AccessRights>(access);
static const AccessFlags table_ddl = AccessType::CREATE_DATABASE | AccessType::CREATE_TABLE | AccessType::CREATE_VIEW
| AccessType::ALTER_TABLE | AccessType::ALTER_VIEW | AccessType::DROP_DATABASE | AccessType::DROP_TABLE | AccessType::DROP_VIEW
| AccessType::TRUNCATE;
static const AccessFlags dictionary_ddl = AccessType::CREATE_DICTIONARY | AccessType::DROP_DICTIONARY;
static const AccessFlags table_and_dictionary_ddl = table_ddl | dictionary_ddl;
static const AccessFlags write_table_access = AccessType::INSERT | AccessType::OPTIMIZE;
static const AccessFlags write_dcl_access = AccessType::ACCESS_MANAGEMENT - AccessType::SHOW_ACCESS;
if (params.readonly)
res->revoke(write_table_access | table_and_dictionary_ddl | write_dcl_access | AccessType::SYSTEM | AccessType::KILL_QUERY);
if (params.readonly == 1)
{
/// Table functions are forbidden in readonly mode.
/// For example, for readonly = 2 - allowed.
res->revoke(AccessType::CREATE_TEMPORARY_TABLE);
}
String formatSkippedMessage(const std::string_view & database, const std::string_view & table)
if (!params.allow_ddl)
res->revoke(table_and_dictionary_ddl);
if (!params.allow_introspection)
res->revoke(AccessType::INTROSPECTION);
/// Anyone has access to the "system" database.
res->grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE);
if (params.readonly != 1)
{
String str = ". Skipped table ";
if (!database.empty())
str += backQuoteIfNeed(database) + ".";
str += backQuoteIfNeed(table);
return str;
/// User has access to temporary or external table if such table was resolved in session or query context
res->grant(AccessFlags::allTableFlags() | AccessFlags::allColumnFlags(), DatabaseCatalog::TEMPORARY_DATABASE);
}
String formatSkippedMessage(const std::string_view & database, const std::string_view & table, const std::string_view & column)
if (params.readonly)
{
String str = ". Skipped column " + backQuoteIfNeed(column) + " ON ";
if (!database.empty())
str += backQuoteIfNeed(database) + ".";
str += backQuoteIfNeed(table);
return str;
/// No grant option in readonly mode.
res->revokeGrantOption(AccessType::ALL);
}
template <typename StringT>
String formatSkippedMessage(const std::string_view & database, const std::string_view & table, const std::vector<StringT> & columns)
{
if (columns.size() == 1)
return formatSkippedMessage(database, table, columns[0]);
String str = ". Skipped columns ";
bool need_comma = false;
for (const auto & column : columns)
{
if (std::exchange(need_comma, true))
str += ", ";
str += backQuoteIfNeed(column);
return res;
}
str += " ON ";
if (!database.empty())
str += backQuoteIfNeed(database) + ".";
str += backQuoteIfNeed(table);
return str;
std::array<UUID, 1> to_array(const UUID & id)
{
std::array<UUID, 1> ids;
ids[0] = id;
return ids;
}
}
@ -116,8 +119,12 @@ void ContextAccess::setUser(const UserPtr & user_) const
if (!user)
{
/// User has been dropped.
auto nothing_granted = boost::make_shared<AccessRights>();
boost::range::fill(result_access, nothing_granted);
auto nothing_granted = std::make_shared<AccessRights>();
access = nothing_granted;
access_without_readonly = nothing_granted;
access_with_allow_ddl = nothing_granted;
access_with_allow_introspection = nothing_granted;
access_from_user_and_roles = nothing_granted;
subscription_for_user_change = {};
subscription_for_roles_changes = {};
enabled_roles = nullptr;
@ -169,10 +176,33 @@ void ContextAccess::setRolesInfo(const std::shared_ptr<const EnabledRolesInfo> &
{
assert(roles_info_);
roles_info = roles_info_;
boost::range::fill(result_access, nullptr /* need recalculate */);
enabled_row_policies = manager->getEnabledRowPolicies(*params.user_id, roles_info->enabled_roles);
enabled_quota = manager->getEnabledQuota(*params.user_id, user_name, roles_info->enabled_roles, params.address, params.quota_key);
enabled_settings = manager->getEnabledSettings(*params.user_id, user->settings, roles_info->enabled_roles, roles_info->settings_from_enabled_roles);
calculateAccessRights();
}
void ContextAccess::calculateAccessRights() const
{
access_from_user_and_roles = mixAccessRightsFromUserAndRoles(*user, *roles_info);
access = applyParamsToAccessRights(*access_from_user_and_roles, params);
access_without_readonly = nullptr;
access_with_allow_ddl = nullptr;
access_with_allow_introspection = nullptr;
if (trace_log)
{
if (roles_info && !roles_info->getCurrentRolesNames().empty())
{
LOG_TRACE(trace_log, "Current_roles: {}, enabled_roles: {}",
boost::algorithm::join(roles_info->getCurrentRolesNames(), ", "),
boost::algorithm::join(roles_info->getEnabledRolesNames(), ", "));
}
LOG_TRACE(trace_log, "Settings: readonly={}, allow_ddl={}, allow_introspection_functions={}", params.readonly, params.allow_ddl, params.allow_introspection);
LOG_TRACE(trace_log, "List of all grants: {}", access->toString());
}
}
@ -193,284 +223,6 @@ bool ContextAccess::isClientHostAllowed() const
}
template <int mode, bool grant_option, typename... Args>
bool ContextAccess::calculateResultAccessAndCheck(Poco::Logger * log_, const AccessFlags & flags, const Args &... args) const
{
auto access = calculateResultAccess(grant_option);
bool is_granted = access->isGranted(flags, args...);
if (trace_log)
LOG_TRACE(trace_log, "Access {}: {}", (is_granted ? "granted" : "denied"), (AccessRightsElement{flags, args...}.toString()));
if (is_granted)
return true;
if constexpr (mode == RETURN_FALSE_IF_ACCESS_DENIED)
return false;
if constexpr (mode == LOG_WARNING_IF_ACCESS_DENIED)
{
if (!log_)
return false;
}
auto show_error = [&](const String & msg, [[maybe_unused]] int error_code)
{
if constexpr (mode == THROW_IF_ACCESS_DENIED)
throw Exception(user_name + ": " + msg, error_code);
else if constexpr (mode == LOG_WARNING_IF_ACCESS_DENIED)
LOG_WARNING(log_, "{}: {}{}", user_name, msg, formatSkippedMessage(args...));
};
if (!user)
{
show_error("User has been dropped", ErrorCodes::UNKNOWN_USER);
}
else if (grant_option && calculateResultAccess(false, params.readonly, params.allow_ddl, params.allow_introspection)->isGranted(flags, args...))
{
show_error(
"Not enough privileges. "
"The required privileges have been granted, but without grant option. "
"To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + " WITH GRANT OPTION",
ErrorCodes::ACCESS_DENIED);
}
else if (params.readonly && calculateResultAccess(false, false, params.allow_ddl, params.allow_introspection)->isGranted(flags, args...))
{
if (params.interface == ClientInfo::Interface::HTTP && params.http_method == ClientInfo::HTTPMethod::GET)
show_error(
"Cannot execute query in readonly mode. "
"For queries over HTTP, method GET implies readonly. You should use method POST for modifying queries",
ErrorCodes::READONLY);
else
show_error("Cannot execute query in readonly mode", ErrorCodes::READONLY);
}
else if (!params.allow_ddl && calculateResultAccess(false, params.readonly, true, params.allow_introspection)->isGranted(flags, args...))
{
show_error("Cannot execute query. DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED);
}
else if (!params.allow_introspection && calculateResultAccess(false, params.readonly, params.allow_ddl, true)->isGranted(flags, args...))
{
show_error("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED);
}
else
{
show_error(
"Not enough privileges. To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + (grant_option ? " WITH GRANT OPTION" : ""),
ErrorCodes::ACCESS_DENIED);
}
return false;
}
template <int mode, bool grant_option>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags) const
{
return calculateResultAccessAndCheck<mode, grant_option>(log_, flags);
}
template <int mode, bool grant_option, typename... Args>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
if (database.empty())
return calculateResultAccessAndCheck<mode, grant_option>(log_, flags, params.current_database, args...);
else
return calculateResultAccessAndCheck<mode, grant_option>(log_, flags, database, args...);
}
template <int mode, bool grant_option>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessRightsElement & element) const
{
if (element.any_database)
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags);
}
else if (element.any_table)
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags, element.database);
}
else if (element.any_column)
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags, element.database, element.table);
}
else
{
return checkAccessImpl<mode, grant_option>(log_, element.access_flags, element.database, element.table, element.columns);
}
}
template <int mode, bool grant_option>
bool ContextAccess::checkAccessImpl(Poco::Logger * log_, const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!checkAccessImpl<mode, grant_option>(log_, element))
return false;
return true;
}
void ContextAccess::checkAccess(const AccessFlags & flags) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, column); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessRightsElement & element) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, element); }
void ContextAccess::checkAccess(const AccessRightsElements & elements) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, false>(nullptr, elements); }
bool ContextAccess::isGranted(const AccessFlags & flags) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, column); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessRightsElement & element) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, element); }
bool ContextAccess::isGranted(const AccessRightsElements & elements) const { return checkAccessImpl<RETURN_FALSE_IF_ACCESS_DENIED, false>(nullptr, elements); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table, column); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table, columns); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, flags, database, table, columns); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessRightsElement & element) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, element); }
bool ContextAccess::isGranted(Poco::Logger * log_, const AccessRightsElements & elements) const { return checkAccessImpl<LOG_WARNING_IF_ACCESS_DENIED, false>(log_, elements); }
void ContextAccess::checkGrantOption(const AccessFlags & flags) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table, column); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessRightsElement & element) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, element); }
void ContextAccess::checkGrantOption(const AccessRightsElements & elements) const { checkAccessImpl<THROW_IF_ACCESS_DENIED, true>(nullptr, elements); }
void ContextAccess::checkAdminOption(const UUID & role_id) const
{
if (isGranted(AccessType::ROLE_ADMIN))
return;
auto info = getRolesInfo();
if (info && info->enabled_roles_with_admin_option.count(role_id))
return;
if (!user)
throw Exception(user_name + ": User has been dropped", ErrorCodes::UNKNOWN_USER);
std::optional<String> role_name = manager->readName(role_id);
if (!role_name)
role_name = "ID {" + toString(role_id) + "}";
throw Exception(
getUserName() + ": Not enough privileges. To execute this query it's necessary to have the grant " + backQuoteIfNeed(*role_name)
+ " WITH ADMIN OPTION ",
ErrorCodes::ACCESS_DENIED);
}
boost::shared_ptr<const AccessRights> ContextAccess::calculateResultAccess(bool grant_option) const
{
return calculateResultAccess(grant_option, params.readonly, params.allow_ddl, params.allow_introspection);
}
boost::shared_ptr<const AccessRights> ContextAccess::calculateResultAccess(bool grant_option, UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const
{
size_t index = static_cast<size_t>(readonly_ != params.readonly)
+ static_cast<size_t>(allow_ddl_ != params.allow_ddl) * 2 +
+ static_cast<size_t>(allow_introspection_ != params.allow_introspection) * 3
+ static_cast<size_t>(grant_option) * 4;
assert(index < std::size(result_access));
auto res = result_access[index].load();
if (res)
return res;
std::lock_guard lock{mutex};
res = result_access[index].load();
if (res)
return res;
auto merged_access = boost::make_shared<AccessRights>();
if (grant_option)
{
*merged_access = user->access.access_with_grant_option;
if (roles_info)
merged_access->merge(roles_info->access_with_grant_option);
}
else
{
*merged_access = user->access.access;
if (roles_info)
merged_access->merge(roles_info->access);
}
static const AccessFlags table_ddl = AccessType::CREATE_DATABASE | AccessType::CREATE_TABLE | AccessType::CREATE_VIEW
| AccessType::ALTER_TABLE | AccessType::ALTER_VIEW | AccessType::DROP_DATABASE | AccessType::DROP_TABLE | AccessType::DROP_VIEW
| AccessType::TRUNCATE;
static const AccessFlags dictionary_ddl = AccessType::CREATE_DICTIONARY | AccessType::DROP_DICTIONARY;
static const AccessFlags table_and_dictionary_ddl = table_ddl | dictionary_ddl;
static const AccessFlags write_table_access = AccessType::INSERT | AccessType::OPTIMIZE;
static const AccessFlags write_dcl_access = AccessType::ACCESS_MANAGEMENT - AccessType::SHOW_ACCESS;
if (readonly_)
merged_access->revoke(write_table_access | table_and_dictionary_ddl | write_dcl_access | AccessType::SYSTEM | AccessType::KILL_QUERY);
if (readonly_ == 1)
{
/// Table functions are forbidden in readonly mode.
/// For example, for readonly = 2 - allowed.
merged_access->revoke(AccessType::CREATE_TEMPORARY_TABLE);
}
if (!allow_ddl_ && !grant_option)
merged_access->revoke(table_and_dictionary_ddl);
if (!allow_introspection_ && !grant_option)
merged_access->revoke(AccessType::INTROSPECTION);
/// Anyone has access to the "system" database.
merged_access->grant(AccessType::SELECT, DatabaseCatalog::SYSTEM_DATABASE);
if (readonly_ != 1)
{
/// User has access to temporary or external table if such table was resolved in session or query context
merged_access->grant(AccessFlags::allTableFlags() | AccessFlags::allColumnFlags(), DatabaseCatalog::TEMPORARY_DATABASE);
}
if (readonly_ && grant_option)
{
/// No grant option in readonly mode.
merged_access->revoke(AccessType::ALL);
}
if (trace_log && (params.readonly == readonly_) && (params.allow_ddl == allow_ddl_) && (params.allow_introspection == allow_introspection_))
{
if (grant_option)
LOG_TRACE(trace_log, "List of all grants: {} WITH GRANT OPTION", merged_access->toString());
else
LOG_TRACE(trace_log, "List of all grants: {}", merged_access->toString());
if (roles_info && !roles_info->getCurrentRolesNames().empty())
{
LOG_TRACE(trace_log, "Current_roles: {}, enabled_roles: {}",
boost::algorithm::join(roles_info->getCurrentRolesNames(), ", "),
boost::algorithm::join(roles_info->getEnabledRolesNames(), ", "));
}
LOG_TRACE(trace_log, "Settings: readonly={}, allow_ddl={}, allow_introspection_functions={}", readonly_, allow_ddl_, allow_introspection_);
}
res = std::move(merged_access);
result_access[index].store(res);
return res;
}
UserPtr ContextAccess::getUser() const
{
std::lock_guard lock{mutex};
@ -520,9 +272,7 @@ std::shared_ptr<const ContextAccess> ContextAccess::getFullAccess()
static const std::shared_ptr<const ContextAccess> res = []
{
auto full_access = std::shared_ptr<ContextAccess>(new ContextAccess);
auto everything_granted = boost::make_shared<AccessRights>();
everything_granted->grant(AccessType::ALL);
boost::range::fill(full_access->result_access, everything_granted);
full_access->access = std::make_shared<AccessRights>(AccessRights::getFullAccess());
full_access->enabled_quota = EnabledQuota::getUnlimitedQuota();
return full_access;
}();
@ -543,4 +293,284 @@ std::shared_ptr<const SettingsConstraints> ContextAccess::getSettingsConstraints
return enabled_settings ? enabled_settings->getConstraints() : nullptr;
}
std::shared_ptr<const AccessRights> ContextAccess::getAccess() const
{
std::lock_guard lock{mutex};
return access;
}
template <bool grant_option, typename... Args>
bool ContextAccess::isGrantedImpl2(const AccessFlags & flags, const Args &... args) const
{
bool access_granted;
if constexpr (grant_option)
access_granted = getAccess()->hasGrantOption(flags, args...);
else
access_granted = getAccess()->isGranted(flags, args...);
if (trace_log)
LOG_TRACE(trace_log, "Access {}: {}{}", (access_granted ? "granted" : "denied"), (AccessRightsElement{flags, args...}.toString()),
(grant_option ? " WITH GRANT OPTION" : ""));
return access_granted;
}
template <bool grant_option>
bool ContextAccess::isGrantedImpl(const AccessFlags & flags) const
{
return isGrantedImpl2<grant_option>(flags);
}
template <bool grant_option, typename... Args>
bool ContextAccess::isGrantedImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
return isGrantedImpl2<grant_option>(flags, database.empty() ? params.current_database : database, args...);
}
template <bool grant_option>
bool ContextAccess::isGrantedImpl(const AccessRightsElement & element) const
{
if (element.any_database)
return isGrantedImpl<grant_option>(element.access_flags);
else if (element.any_table)
return isGrantedImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table);
else
return isGrantedImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool grant_option>
bool ContextAccess::isGrantedImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
if (!isGrantedImpl<grant_option>(element))
return false;
return true;
}
bool ContextAccess::isGranted(const AccessFlags & flags) const { return isGrantedImpl<false>(flags); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<false>(flags, database); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<false>(flags, database, table); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<false>(flags, database, table, column); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<false>(flags, database, table, columns); }
bool ContextAccess::isGranted(const AccessRightsElement & element) const { return isGrantedImpl<false>(element); }
bool ContextAccess::isGranted(const AccessRightsElements & elements) const { return isGrantedImpl<false>(elements); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags) const { return isGrantedImpl<true>(flags); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database) const { return isGrantedImpl<true>(flags, database); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { return isGrantedImpl<true>(flags, database, table); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { return isGrantedImpl<true>(flags, database, table, column); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool ContextAccess::hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { return isGrantedImpl<true>(flags, database, table, columns); }
bool ContextAccess::hasGrantOption(const AccessRightsElement & element) const { return isGrantedImpl<true>(element); }
bool ContextAccess::hasGrantOption(const AccessRightsElements & elements) const { return isGrantedImpl<true>(elements); }
template <bool grant_option, typename... Args>
void ContextAccess::checkAccessImpl2(const AccessFlags & flags, const Args &... args) const
{
if constexpr (grant_option)
{
if (hasGrantOption(flags, args...))
return;
}
else
{
if (isGranted(flags, args...))
return;
}
auto show_error = [&](const String & msg, int error_code)
{
throw Exception(user_name + ": " + msg, error_code);
};
std::lock_guard lock{mutex};
if (!user)
show_error("User has been dropped", ErrorCodes::UNKNOWN_USER);
if (grant_option && access->isGranted(flags, args...))
{
show_error(
"Not enough privileges. "
"The required privileges have been granted, but without grant option. "
"To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + " WITH GRANT OPTION",
ErrorCodes::ACCESS_DENIED);
}
if (params.readonly)
{
if (!access_without_readonly)
{
Params changed_params = params;
changed_params.readonly = 0;
access_without_readonly = applyParamsToAccessRights(*access_from_user_and_roles, changed_params);
}
if (access_without_readonly->isGranted(flags, args...))
{
if (params.interface == ClientInfo::Interface::HTTP && params.http_method == ClientInfo::HTTPMethod::GET)
show_error(
"Cannot execute query in readonly mode. "
"For queries over HTTP, method GET implies readonly. You should use method POST for modifying queries",
ErrorCodes::READONLY);
else
show_error("Cannot execute query in readonly mode", ErrorCodes::READONLY);
}
}
if (!params.allow_ddl)
{
if (!access_with_allow_ddl)
{
Params changed_params = params;
changed_params.allow_ddl = true;
access_with_allow_ddl = applyParamsToAccessRights(*access_from_user_and_roles, changed_params);
}
if (access_with_allow_ddl->isGranted(flags, args...))
{
show_error("Cannot execute query. DDL queries are prohibited for the user", ErrorCodes::QUERY_IS_PROHIBITED);
}
}
if (!params.allow_introspection)
{
if (!access_with_allow_introspection)
{
Params changed_params = params;
changed_params.allow_introspection = true;
access_with_allow_introspection = applyParamsToAccessRights(*access_from_user_and_roles, changed_params);
}
if (access_with_allow_introspection->isGranted(flags, args...))
{
show_error("Introspection functions are disabled, because setting 'allow_introspection_functions' is set to 0", ErrorCodes::FUNCTION_NOT_ALLOWED);
}
}
show_error(
"Not enough privileges. To execute this query it's necessary to have the grant "
+ AccessRightsElement{flags, args...}.toString() + (grant_option ? " WITH GRANT OPTION" : ""),
ErrorCodes::ACCESS_DENIED);
}
template <bool grant_option>
void ContextAccess::checkAccessImpl(const AccessFlags & flags) const
{
checkAccessImpl2<grant_option>(flags);
}
template <bool grant_option, typename... Args>
void ContextAccess::checkAccessImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const
{
checkAccessImpl2<grant_option>(flags, database.empty() ? params.current_database : database, args...);
}
template <bool grant_option>
void ContextAccess::checkAccessImpl(const AccessRightsElement & element) const
{
if (element.any_database)
checkAccessImpl<grant_option>(element.access_flags);
else if (element.any_table)
checkAccessImpl<grant_option>(element.access_flags, element.database);
else if (element.any_column)
checkAccessImpl<grant_option>(element.access_flags, element.database, element.table);
else
checkAccessImpl<grant_option>(element.access_flags, element.database, element.table, element.columns);
}
template <bool grant_option>
void ContextAccess::checkAccessImpl(const AccessRightsElements & elements) const
{
for (const auto & element : elements)
checkAccessImpl<grant_option>(element);
}
void ContextAccess::checkAccess(const AccessFlags & flags) const { checkAccessImpl<false>(flags); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<false>(flags, database); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<false>(flags, database, table); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<false>(flags, database, table, column); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<false>(flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<false>(flags, database, table, columns); }
void ContextAccess::checkAccess(const AccessRightsElement & element) const { checkAccessImpl<false>(element); }
void ContextAccess::checkAccess(const AccessRightsElements & elements) const { checkAccessImpl<false>(elements); }
void ContextAccess::checkGrantOption(const AccessFlags & flags) const { checkAccessImpl<true>(flags); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database) const { checkAccessImpl<true>(flags, database); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const { checkAccessImpl<true>(flags, database, table); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const { checkAccessImpl<true>(flags, database, table, column); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const { checkAccessImpl<true>(flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const { checkAccessImpl<true>(flags, database, table, columns); }
void ContextAccess::checkGrantOption(const AccessRightsElement & element) const { checkAccessImpl<true>(element); }
void ContextAccess::checkGrantOption(const AccessRightsElements & elements) const { checkAccessImpl<true>(elements); }
template <typename Container, typename GetNameFunction>
void ContextAccess::checkAdminOptionImpl(const Container & role_ids, const GetNameFunction & get_name_function) const
{
if (isGranted(AccessType::ROLE_ADMIN))
return;
auto info = getRolesInfo();
if (!info)
{
if (!user)
throw Exception(user_name + ": User has been dropped", ErrorCodes::UNKNOWN_USER);
return;
}
size_t i = 0;
for (auto it = std::begin(role_ids); it != std::end(role_ids); ++it, ++i)
{
const UUID & role_id = *it;
if (info->enabled_roles_with_admin_option.count(role_id))
continue;
auto role_name = get_name_function(role_id, i);
if (!role_name)
role_name = "ID {" + toString(role_id) + "}";
String msg = "To execute this query it's necessary to have the role " + backQuoteIfNeed(*role_name) + " granted with ADMIN option";
if (info->enabled_roles.count(role_id))
msg = "Role " + backQuote(*role_name) + " is granted, but without ADMIN option. " + msg;
throw Exception(getUserName() + ": Not enough privileges. " + msg, ErrorCodes::ACCESS_DENIED);
}
}
void ContextAccess::checkAdminOption(const UUID & role_id) const
{
checkAdminOptionImpl(to_array(role_id), [this](const UUID & id, size_t) { return manager->tryReadName(id); });
}
void ContextAccess::checkAdminOption(const UUID & role_id, const String & role_name) const
{
checkAdminOptionImpl(to_array(role_id), [&role_name](const UUID &, size_t) { return std::optional<String>{role_name}; });
}
void ContextAccess::checkAdminOption(const UUID & role_id, const std::unordered_map<UUID, String> & names_of_roles) const
{
checkAdminOptionImpl(to_array(role_id), [&names_of_roles](const UUID & id, size_t) { auto it = names_of_roles.find(id); return (it != names_of_roles.end()) ? it->second : std::optional<String>{}; });
}
void ContextAccess::checkAdminOption(const std::vector<UUID> & role_ids) const
{
checkAdminOptionImpl(role_ids, [this](const UUID & id, size_t) { return manager->tryReadName(id); });
}
void ContextAccess::checkAdminOption(const std::vector<UUID> & role_ids, const Strings & names_of_roles) const
{
checkAdminOptionImpl(role_ids, [&names_of_roles](const UUID &, size_t i) { return std::optional<String>{names_of_roles[i]}; });
}
void ContextAccess::checkAdminOption(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const
{
checkAdminOptionImpl(role_ids, [&names_of_roles](const UUID & id, size_t) { auto it = names_of_roles.find(id); return (it != names_of_roles.end()) ? it->second : std::optional<String>{}; });
}
}

View File

@ -6,7 +6,6 @@
#include <Core/UUID.h>
#include <ext/scope_guard.h>
#include <ext/shared_ptr_helper.h>
#include <boost/smart_ptr/atomic_shared_ptr.hpp>
#include <boost/container/flat_set.hpp>
#include <mutex>
@ -30,10 +29,7 @@ class IAST;
using ASTPtr = std::shared_ptr<IAST>;
class ContextAccess
{
public:
struct Params
struct ContextAccessParams
{
std::optional<UUID> user_id;
boost::container::flat_set<UUID> current_roles;
@ -48,14 +44,19 @@ public:
String quota_key;
auto toTuple() const { return std::tie(user_id, current_roles, use_default_roles, readonly, allow_ddl, allow_introspection, current_database, interface, http_method, address, quota_key); }
friend bool operator ==(const Params & lhs, const Params & rhs) { return lhs.toTuple() == rhs.toTuple(); }
friend bool operator !=(const Params & lhs, const Params & rhs) { return !(lhs == rhs); }
friend bool operator <(const Params & lhs, const Params & rhs) { return lhs.toTuple() < rhs.toTuple(); }
friend bool operator >(const Params & lhs, const Params & rhs) { return rhs < lhs; }
friend bool operator <=(const Params & lhs, const Params & rhs) { return !(rhs < lhs); }
friend bool operator >=(const Params & lhs, const Params & rhs) { return !(lhs < rhs); }
friend bool operator ==(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return lhs.toTuple() == rhs.toTuple(); }
friend bool operator !=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(lhs == rhs); }
friend bool operator <(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return lhs.toTuple() < rhs.toTuple(); }
friend bool operator >(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return rhs < lhs; }
friend bool operator <=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(rhs < lhs); }
friend bool operator >=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(lhs < rhs); }
};
class ContextAccess
{
public:
using Params = ContextAccessParams;
const Params & getParams() const { return params; }
/// Returns the current user. The function can return nullptr.
@ -90,16 +91,8 @@ public:
/// The function returns nullptr if there are no constraints.
std::shared_ptr<const SettingsConstraints> getSettingsConstraints() const;
/// Checks if a specified access is granted, and throws an exception if not.
/// Empty database means the current database.
void checkAccess(const AccessFlags & flags) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
void checkAccess(const AccessRightsElement & element) const;
void checkAccess(const AccessRightsElements & elements) const;
/// Returns the current access rights.
std::shared_ptr<const AccessRights> getAccess() const;
/// Checks if a specified access is granted.
bool isGranted(const AccessFlags & flags) const;
@ -111,17 +104,26 @@ public:
bool isGranted(const AccessRightsElement & element) const;
bool isGranted(const AccessRightsElements & elements) const;
/// Checks if a specified access is granted, and logs a warning if not.
bool isGranted(Poco::Logger * log_, const AccessFlags & flags) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool isGranted(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool isGranted(Poco::Logger * log_, const AccessRightsElement & element) const;
bool isGranted(Poco::Logger * log_, const AccessRightsElements & elements) const;
bool hasGrantOption(const AccessFlags & flags) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
bool hasGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
bool hasGrantOption(const AccessRightsElement & element) const;
bool hasGrantOption(const AccessRightsElements & elements) const;
/// Checks if a specified access is granted, and throws an exception if not.
/// Empty database means the current database.
void checkAccess(const AccessFlags & flags) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::string_view & column) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const std::vector<std::string_view> & columns) const;
void checkAccess(const AccessFlags & flags, const std::string_view & database, const std::string_view & table, const Strings & columns) const;
void checkAccess(const AccessRightsElement & element) const;
void checkAccess(const AccessRightsElements & elements) const;
/// Checks if a specified access is granted with grant option, and throws an exception if not.
void checkGrantOption(const AccessFlags & flags) const;
void checkGrantOption(const AccessFlags & flags, const std::string_view & database) const;
void checkGrantOption(const AccessFlags & flags, const std::string_view & database, const std::string_view & table) const;
@ -133,6 +135,11 @@ public:
/// Checks if a specified role is granted with admin option, and throws an exception if not.
void checkAdminOption(const UUID & role_id) const;
void checkAdminOption(const UUID & role_id, const String & role_name) const;
void checkAdminOption(const UUID & role_id, const std::unordered_map<UUID, String> & names_of_roles) const;
void checkAdminOption(const std::vector<UUID> & role_ids) const;
void checkAdminOption(const std::vector<UUID> & role_ids, const Strings & names_of_roles) const;
void checkAdminOption(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const;
/// Makes an instance of ContextAccess which provides full access to everything
/// without any limitations. This is used for the global context.
@ -146,24 +153,40 @@ private:
void setUser(const UserPtr & user_) const;
void setRolesInfo(const std::shared_ptr<const EnabledRolesInfo> & roles_info_) const;
void setSettingsAndConstraints() const;
void calculateAccessRights() const;
template <int mode, bool grant_option>
bool checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags) const;
template <bool grant_option>
bool isGrantedImpl(const AccessFlags & flags) const;
template <int mode, bool grant_option, typename... Args>
bool checkAccessImpl(Poco::Logger * log_, const AccessFlags & flags, const std::string_view & database, const Args &... args) const;
template <bool grant_option, typename... Args>
bool isGrantedImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const;
template <int mode, bool grant_option>
bool checkAccessImpl(Poco::Logger * log_, const AccessRightsElement & element) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElement & element) const;
template <int mode, bool grant_option>
bool checkAccessImpl(Poco::Logger * log_, const AccessRightsElements & elements) const;
template <bool grant_option>
bool isGrantedImpl(const AccessRightsElements & elements) const;
template <int mode, bool grant_option, typename... Args>
bool calculateResultAccessAndCheck(Poco::Logger * log_, const AccessFlags & flags, const Args &... args) const;
template <bool grant_option, typename... Args>
bool isGrantedImpl2(const AccessFlags & flags, const Args &... args) const;
boost::shared_ptr<const AccessRights> calculateResultAccess(bool grant_option) const;
boost::shared_ptr<const AccessRights> calculateResultAccess(bool grant_option, UInt64 readonly_, bool allow_ddl_, bool allow_introspection_) const;
template <bool grant_option>
void checkAccessImpl(const AccessFlags & flags) const;
template <bool grant_option, typename... Args>
void checkAccessImpl(const AccessFlags & flags, const std::string_view & database, const Args &... args) const;
template <bool grant_option>
void checkAccessImpl(const AccessRightsElement & element) const;
template <bool grant_option>
void checkAccessImpl(const AccessRightsElements & elements) const;
template <bool grant_option, typename... Args>
void checkAccessImpl2(const AccessFlags & flags, const Args &... args) const;
template <typename Container, typename GetNameFunction>
void checkAdminOptionImpl(const Container & role_ids, const GetNameFunction & get_name_function) const;
const AccessControlManager * manager = nullptr;
const Params params;
@ -174,10 +197,14 @@ private:
mutable std::shared_ptr<const EnabledRoles> enabled_roles;
mutable ext::scope_guard subscription_for_roles_changes;
mutable std::shared_ptr<const EnabledRolesInfo> roles_info;
mutable boost::atomic_shared_ptr<const AccessRights> result_access[7];
mutable std::shared_ptr<const AccessRights> access;
mutable std::shared_ptr<const EnabledRowPolicies> enabled_row_policies;
mutable std::shared_ptr<const EnabledQuota> enabled_quota;
mutable std::shared_ptr<const EnabledSettings> enabled_settings;
mutable std::shared_ptr<const AccessRights> access_without_readonly;
mutable std::shared_ptr<const AccessRights> access_with_allow_ddl;
mutable std::shared_ptr<const AccessRights> access_with_allow_introspection;
mutable std::shared_ptr<const AccessRights> access_from_user_and_roles;
mutable std::mutex mutex;
};

View File

@ -28,8 +28,7 @@ bool operator==(const EnabledRolesInfo & lhs, const EnabledRolesInfo & rhs)
{
return (lhs.current_roles == rhs.current_roles) && (lhs.enabled_roles == rhs.enabled_roles)
&& (lhs.enabled_roles_with_admin_option == rhs.enabled_roles_with_admin_option) && (lhs.names_of_roles == rhs.names_of_roles)
&& (lhs.access == rhs.access) && (lhs.access_with_grant_option == rhs.access_with_grant_option)
&& (lhs.settings_from_enabled_roles == rhs.settings_from_enabled_roles);
&& (lhs.access == rhs.access) && (lhs.settings_from_enabled_roles == rhs.settings_from_enabled_roles);
}
}

View File

@ -18,7 +18,6 @@ struct EnabledRolesInfo
boost::container::flat_set<UUID> enabled_roles_with_admin_option;
std::unordered_map<UUID, String> names_of_roles;
AccessRights access;
AccessRights access_with_grant_option;
SettingsProfileElements settings_from_enabled_roles;
Strings getCurrentRolesNames() const;

View File

@ -1,22 +0,0 @@
#include <Access/GrantedAccess.h>
namespace DB
{
GrantedAccess::GrantsAndPartialRevokes GrantedAccess::getGrantsAndPartialRevokes() const
{
GrantsAndPartialRevokes res;
res.grants_with_grant_option = access_with_grant_option.getGrants();
AccessRights access_without_gg = access;
access_without_gg.revoke(res.grants_with_grant_option);
auto gr = access_without_gg.getGrantsAndPartialRevokes();
res.grants = std::move(gr.grants);
res.revokes = std::move(gr.revokes);
AccessRights access_with_grant_options_without_r = access_with_grant_option;
access_with_grant_options_without_r.grant(res.revokes);
res.revokes_grant_option = access_with_grant_options_without_r.getPartialRevokes();
return res;
}
}

View File

@ -1,55 +0,0 @@
#pragma once
#include <Access/AccessRights.h>
namespace DB
{
/// Access rights as they are granted to a role or user.
/// Stores both the access rights themselves and the access rights with grant option.
struct GrantedAccess
{
AccessRights access;
AccessRights access_with_grant_option;
template <typename... Args>
void grant(const Args &... args)
{
access.grant(args...);
}
template <typename... Args>
void grantWithGrantOption(const Args &... args)
{
access.grant(args...);
access_with_grant_option.grant(args...);
}
template <typename... Args>
void revoke(const Args &... args)
{
access.revoke(args...);
access_with_grant_option.revoke(args...);
}
template <typename... Args>
void revokeGrantOption(const Args &... args)
{
access_with_grant_option.revoke(args...);
}
struct GrantsAndPartialRevokes
{
AccessRightsElements grants;
AccessRightsElements revokes;
AccessRightsElements grants_with_grant_option;
AccessRightsElements revokes_grant_option;
};
/// Retrieves the information about grants and partial revokes.
GrantsAndPartialRevokes getGrantsAndPartialRevokes() const;
friend bool operator ==(const GrantedAccess & left, const GrantedAccess & right) { return (left.access == right.access) && (left.access_with_grant_option == right.access_with_grant_option); }
friend bool operator !=(const GrantedAccess & left, const GrantedAccess & right) { return !(left == right); }
};
}

Some files were not shown because too many files have changed in this diff Show More