mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-22 23:52:03 +00:00
Merge branch 'master' into initial-explain
This commit is contained in:
commit
8587c2025d
@ -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;
|
||||
|
||||
|
2
contrib/replxx
vendored
2
contrib/replxx
vendored
@ -1 +1 @@
|
||||
Subproject commit 2d37daaad24be71e76514a36b0a47120be2f9086
|
||||
Subproject commit 94b1f568d16183214d26c7c0e9ce69a4ce407f65
|
@ -1,19 +1,82 @@
|
||||
{
|
||||
"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/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": []
|
||||
}
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -27,7 +27,6 @@ RUN apt-get update \
|
||||
luajit \
|
||||
libssl-dev \
|
||||
gdb \
|
||||
virtualenv \
|
||||
&& rm -rf \
|
||||
/var/lib/apt/lists/* \
|
||||
/var/cache/debconf \
|
||||
@ -36,9 +35,8 @@ RUN apt-get update \
|
||||
|
||||
ENV TZ=Europe/Moscow
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
RUN mkdir /venv && virtualenv /venv
|
||||
|
||||
RUN /bin/bash -c "source /venv/bin/activate && pip install requests urllib3 pytest docker-compose==1.22.0 docker dicttoxml kazoo PyMySQL psycopg2-binary==2.7.5 pymongo tzlocal kafka-python protobuf redis aerospike pytest-timeout minio rpm-confluent-schemaregistry grpcio grpcio-tools cassandra-driver"
|
||||
RUN pip install urllib3==1.23 pytest docker-compose==1.22.0 docker dicttoxml kazoo PyMySQL psycopg2==2.7.5 pymongo tzlocal kafka-python protobuf redis aerospike pytest-timeout minio rpm-confluent-schemaregistry grpcio grpcio-tools cassandra-driver
|
||||
|
||||
ENV DOCKER_CHANNEL stable
|
||||
ENV DOCKER_VERSION 17.09.1-ce
|
||||
@ -75,4 +73,5 @@ RUN set -x \
|
||||
VOLUME /var/lib/docker
|
||||
EXPOSE 2375
|
||||
ENTRYPOINT ["dockerd-entrypoint.sh"]
|
||||
CMD ["bash", "-c", "source /venv/bin/activate && pytest $PYTEST_OPTS"]
|
||||
CMD ["sh", "-c", "pytest $PYTEST_OPTS"]
|
||||
|
||||
|
@ -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` 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`.
|
||||
`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 don’t 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`.
|
||||
`IColumn` has methods for common relational transformations of data, but they don’t 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` doesn’t 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.
|
||||
|
||||
@ -122,7 +122,7 @@ Ordinary functions don’t change the number of rows – they work as if they ar
|
||||
|
||||
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.
|
||||
|
||||
ClickHouse has strong typing, so there’s 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.
|
||||
ClickHouse has strong typing, so there’s 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 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.
|
||||
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. That’s 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 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 simplicity’s 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 simplicity’s sake, and because we are already inserting data in batches in our applications.
|
||||
|
||||
> MergeTree tables can only have one (primary) index: there aren’t 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 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.
|
||||
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.
|
||||
|
||||
|
4
docs/en/interfaces/third-party/gui.md
vendored
4
docs/en/interfaces/third-party/gui.md
vendored
@ -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}
|
||||
|
@ -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.
|
||||
|
@ -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] │
|
||||
|
@ -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 │
|
||||
|
@ -1 +0,0 @@
|
||||
../../en/development/architecture.md
|
197
docs/ru/development/architecture.md
Normal file
197
docs/ru/development/architecture.md
Normal 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#function-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/) ##}
|
@ -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, расположенной на каждом сервере кластера.
|
||||
|
@ -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`.
|
||||
|
||||
|
@ -798,17 +798,34 @@ SELECT
|
||||
└──────────────┴───────────┘
|
||||
```
|
||||
|
||||
## arrayReduce(agg\_func, arr1, …) {#array-functions-arrayreduce}
|
||||
## arrayReduce (#arrayreduce}
|
||||
|
||||
Применяет агрегатную функцию к элементам массива и возвращает ее результат. Имя агрегирующей функции передается как строка в одинарных кавычках `'max'`, `'sum'`. При использовании параметрических агрегатных функций, параметр указывается после имени функции в круглых скобках `'uniqUpTo(6)'`.
|
||||
|
||||
Пример:
|
||||
**Синтаксис**
|
||||
|
||||
``` sql
|
||||
```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
|
||||
Ответ:
|
||||
|
||||
```text
|
||||
┌─arrayReduce('max', [1, 2, 3])─┐
|
||||
│ 3 │
|
||||
└───────────────────────────────┘
|
||||
@ -816,13 +833,17 @@ SELECT arrayReduce('max', [1, 2, 3])
|
||||
|
||||
Если агрегатная функция имеет несколько аргументов, то эту функцию можно применять к нескольким массивам одинакового размера.
|
||||
|
||||
Пример:
|
||||
**Пример**
|
||||
|
||||
``` sql
|
||||
Запрос:
|
||||
|
||||
```sql
|
||||
SELECT arrayReduce('maxIf', [3, 5], [1, 0])
|
||||
```
|
||||
|
||||
``` text
|
||||
Ответ:
|
||||
|
||||
```text
|
||||
┌─arrayReduce('maxIf', [3, 5], [1, 0])─┐
|
||||
│ 3 │
|
||||
└──────────────────────────────────────┘
|
||||
@ -830,16 +851,62 @@ SELECT arrayReduce('maxIf', [3, 5], [1, 0])
|
||||
|
||||
Пример с параметрической агрегатной функцией:
|
||||
|
||||
``` sql
|
||||
Запрос:
|
||||
|
||||
```sql
|
||||
SELECT arrayReduce('uniqUpTo(3)', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
```
|
||||
|
||||
``` text
|
||||
Ответ:
|
||||
|
||||
```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}
|
||||
|
||||
Возвращает массив того же размера, что и исходный массив, содержащий элементы в обратном порядке.
|
||||
|
@ -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}
|
||||
|
||||
Возвращает имя класса, которым представлен тип данных столбца в оперативной памяти.
|
||||
|
@ -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` не используется, то запрос возвращает список таблиц из текущей базы данных.
|
||||
|
@ -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. Отменено.
|
||||
|
||||
|
@ -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,19 +44,25 @@ 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
|
||||
stable_count += 1
|
||||
if major_version not in seen_stable:
|
||||
seen_stable[major_version] = payload
|
||||
stable_count += 1
|
||||
|
||||
logging.debug(
|
||||
f'Stables: {stable_count}/{args.stable_releases_limit} LTS: {lts_count}/{args.lts_releases_limit}'
|
||||
@ -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):
|
||||
|
@ -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" ]]
|
||||
|
@ -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
|
||||
|
@ -2,7 +2,7 @@ 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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -1911,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);
|
||||
@ -2069,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;
|
||||
|
@ -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"
|
||||
@ -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"))
|
||||
|
@ -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.",
|
||||
|
@ -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) \
|
||||
|
@ -428,7 +428,7 @@ boost::shared_ptr<const AccessRights> ContextAccess::calculateResultAccess(bool
|
||||
merged_access->revoke(AccessType::CREATE_TEMPORARY_TABLE);
|
||||
}
|
||||
|
||||
if (!allow_ddl_ && !grant_option)
|
||||
if (!allow_ddl_)
|
||||
merged_access->revoke(table_and_dictionary_ddl);
|
||||
|
||||
if (!allow_introspection_ && !grant_option)
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
// Include this header last, because it is an auto-generated dump of questionable
|
||||
// garbage that breaks the build (e.g. it changes _POSIX_C_SOURCE).
|
||||
// TODO: find out what it is. On github, they have proper inteface headers like
|
||||
// TODO: find out what it is. On github, they have proper interface headers like
|
||||
// this one: https://github.com/RoaringBitmap/CRoaring/blob/master/include/roaring/roaring.h
|
||||
#include <roaring/roaring.h>
|
||||
|
||||
|
@ -278,6 +278,7 @@ dbms_target_link_libraries (
|
||||
clickhouse_parsers
|
||||
lz4
|
||||
Poco::JSON
|
||||
Poco::MongoDB
|
||||
string_utils
|
||||
PUBLIC
|
||||
${MYSQLXX_LIBRARY}
|
||||
|
@ -26,7 +26,6 @@ namespace DB
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int ILLEGAL_COLUMN;
|
||||
extern const int NOT_IMPLEMENTED;
|
||||
extern const int BAD_ARGUMENTS;
|
||||
extern const int PARAMETER_OUT_OF_BOUND;
|
||||
@ -38,8 +37,18 @@ namespace ErrorCodes
|
||||
ColumnArray::ColumnArray(MutableColumnPtr && nested_column, MutableColumnPtr && offsets_column)
|
||||
: data(std::move(nested_column)), offsets(std::move(offsets_column))
|
||||
{
|
||||
if (!typeid_cast<const ColumnOffsets *>(offsets.get()))
|
||||
throw Exception("offsets_column must be a ColumnUInt64", ErrorCodes::ILLEGAL_COLUMN);
|
||||
const ColumnOffsets * offsets_concrete = typeid_cast<const ColumnOffsets *>(offsets.get());
|
||||
|
||||
if (!offsets_concrete)
|
||||
throw Exception("offsets_column must be a ColumnUInt64", ErrorCodes::LOGICAL_ERROR);
|
||||
|
||||
size_t size = offsets_concrete->size();
|
||||
if (size != 0 && nested_column)
|
||||
{
|
||||
/// This will also prevent possible overflow in offset.
|
||||
if (nested_column->size() != offsets_concrete->getData()[size - 1])
|
||||
throw Exception("offsets_column has data inconsistent with nested_column", ErrorCodes::LOGICAL_ERROR);
|
||||
}
|
||||
|
||||
/** NOTE
|
||||
* Arrays with constant value are possible and used in implementation of higher order functions (see FunctionReplicate).
|
||||
@ -51,7 +60,7 @@ ColumnArray::ColumnArray(MutableColumnPtr && nested_column)
|
||||
: data(std::move(nested_column))
|
||||
{
|
||||
if (!data->empty())
|
||||
throw Exception("Not empty data passed to ColumnArray, but no offsets passed", ErrorCodes::ILLEGAL_COLUMN);
|
||||
throw Exception("Not empty data passed to ColumnArray, but no offsets passed", ErrorCodes::LOGICAL_ERROR);
|
||||
|
||||
offsets = ColumnOffsets::create();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace CurrentMetrics
|
||||
add(metric, -value);
|
||||
}
|
||||
|
||||
/// For lifetime of object, add amout for specified metric. Then subtract.
|
||||
/// For lifetime of object, add amount for specified metric. Then subtract.
|
||||
class Increment
|
||||
{
|
||||
private:
|
||||
|
@ -504,6 +504,7 @@ namespace ErrorCodes
|
||||
|
||||
extern const int CONDITIONAL_TREE_PARENT_NOT_FOUND = 2001;
|
||||
extern const int ILLEGAL_PROJECTION_MANIPULATOR = 2002;
|
||||
extern const int UNRECOGNIZED_ARGUMENTS = 2003;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,13 +14,13 @@ namespace Util
|
||||
|
||||
/// SensitiveDataMasker allows to remove sensitive data from queries using set of regexp-based rules
|
||||
|
||||
/// It's used as a singelton via getInstance method
|
||||
/// It's used as a singleton via getInstance method
|
||||
|
||||
/// Initially it's empty (nullptr) and after manual initialization
|
||||
/// (one-time, done by setInstance call) it takes the proper value which
|
||||
/// is stored in unique_ptr.
|
||||
|
||||
/// It looks like the singelton is the best option here, as
|
||||
/// It looks like the singleton is the best option here, as
|
||||
/// two users of that object (OwnSplitChannel & Interpreters/executeQuery)
|
||||
/// can't own/share that Masker properly without syncronization & locks,
|
||||
/// and we can't afford setting global locks for each logged line.
|
||||
|
@ -95,7 +95,7 @@ namespace
|
||||
|
||||
enum ComputeWidthMode
|
||||
{
|
||||
Width, /// Calcualte and return visible width
|
||||
Width, /// Calculate and return visible width
|
||||
BytesBeforLimit /// Calculate and return the maximum number of bytes when substring fits in visible width.
|
||||
};
|
||||
|
||||
|
@ -22,7 +22,7 @@ using TemporaryFile = Poco::TemporaryFile;
|
||||
bool enoughSpaceInDirectory(const std::string & path, size_t data_size);
|
||||
std::unique_ptr<TemporaryFile> createTemporaryFile(const std::string & path);
|
||||
|
||||
/// Returns mount point of filesystem where absoulte_path (must exist) is located
|
||||
/// Returns mount point of filesystem where absolute_path (must exist) is located
|
||||
std::filesystem::path getMountPoint(std::filesystem::path absolute_path);
|
||||
|
||||
/// Returns name of filesystem mounted to mount_point
|
||||
|
@ -26,7 +26,7 @@ struct InitializeJemallocZoneAllocatorForOSX
|
||||
/// and even will be optimized out:
|
||||
///
|
||||
/// It is ok to call it twice (i.e. in case of shared libraries)
|
||||
/// Since zone_register() is a no-op if the defualt zone is already replaced with something.
|
||||
/// Since zone_register() is a no-op if the default zone is already replaced with something.
|
||||
///
|
||||
/// https://github.com/jemalloc/jemalloc/issues/708
|
||||
zone_register();
|
||||
|
@ -152,7 +152,7 @@ UInt32 getCompressedDataSize(UInt8 data_bytes_size, UInt32 uncompressed_size)
|
||||
template <typename ValueType>
|
||||
UInt32 compressDataForType(const char * source, UInt32 source_size, char * dest)
|
||||
{
|
||||
// Since only unsinged int has granted 2-complement overflow handling,
|
||||
// Since only unsigned int has granted 2-complement overflow handling,
|
||||
// we are doing math here only on unsigned types.
|
||||
// To simplify and booletproof code, we enforce ValueType to be unsigned too.
|
||||
static_assert(is_unsigned_v<ValueType>, "ValueType must be unsigned.");
|
||||
@ -218,7 +218,7 @@ UInt32 compressDataForType(const char * source, UInt32 source_size, char * dest)
|
||||
const SignedDeltaType signed_dd = static_cast<SignedDeltaType>(double_delta);
|
||||
const auto sign = signed_dd < 0;
|
||||
|
||||
// -1 shirnks dd down to fit into number of bits, and there can't be 0, so it is OK.
|
||||
// -1 shrinks dd down to fit into number of bits, and there can't be 0, so it is OK.
|
||||
const auto abs_value = static_cast<UnsignedDeltaType>(std::abs(signed_dd) - 1);
|
||||
const auto write_spec = getDeltaWriteSpec(signed_dd);
|
||||
|
||||
|
@ -11,7 +11,7 @@ namespace DB
|
||||
* to support 64bit types. The drawback is 1 extra bit for 32-byte wide deltas: 5-bit prefix
|
||||
* instead of 4-bit prefix.
|
||||
*
|
||||
* This codec is best used against monotonic integer sequences with constant (or almost contant)
|
||||
* This codec is best used against monotonic integer sequences with constant (or almost constant)
|
||||
* stride, like event timestamp for some monitoring application.
|
||||
*
|
||||
* Given input sequence a: [a0, a1, ... an]:
|
||||
@ -45,7 +45,7 @@ namespace DB
|
||||
* write sign bit (1 if signed): x
|
||||
* write 64-1 bits of abs(double_delta - 1): xxxxxxxxxxx...
|
||||
*
|
||||
* @example sequence of UInt8 values [1, 2, 3, 4, 5, 6, 7, 8, 9 10] is encoded as (codec header is ommited):
|
||||
* @example sequence of UInt8 values [1, 2, 3, 4, 5, 6, 7, 8, 9 10] is encoded as (codec header is omitted):
|
||||
*
|
||||
* .- 4-byte little-endian sequence length (10 == 0xa)
|
||||
* | .- 1 byte (sizeof(UInt8) a[0] : 0x01
|
||||
|
@ -546,7 +546,7 @@ TEST_P(CodecTest, TranscodingWithoutDataType)
|
||||
class CodecTestCompatibility : public ::testing::TestWithParam<std::tuple<Codec, std::tuple<CodecTestSequence, std::string>>>
|
||||
{};
|
||||
|
||||
// Check that iput sequence when encoded matches the encoded string binary.
|
||||
// Check that input sequence when encoded matches the encoded string binary.
|
||||
TEST_P(CodecTestCompatibility, Encoding)
|
||||
{
|
||||
const auto & codec_spec = std::get<0>(GetParam());
|
||||
@ -1275,7 +1275,7 @@ INSTANTIATE_TEST_SUITE_P(Gorilla,
|
||||
);
|
||||
|
||||
// These 'tests' try to measure performance of encoding and decoding and hence only make sence to be run locally,
|
||||
// also they require pretty big data to run agains and generating this data slows down startup of unit test process.
|
||||
// also they require pretty big data to run against and generating this data slows down startup of unit test process.
|
||||
// So un-comment only at your discretion.
|
||||
|
||||
// Just as if all sequences from generatePyramidOfSequences were appended to one-by-one to the first one.
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <DataTypes/DataTypeString.h>
|
||||
#include <DataTypes/DataTypeUUID.h>
|
||||
#include <DataTypes/DataTypesNumber.h>
|
||||
#include <DataTypes/DataTypeEnum.h>
|
||||
#include <Common/typeid_cast.h>
|
||||
#include <ext/range.h>
|
||||
|
||||
@ -60,6 +61,10 @@ void ExternalResultDescription::init(const Block & sample_block_)
|
||||
types.emplace_back(ValueType::vtDateTime, is_nullable);
|
||||
else if (typeid_cast<const DataTypeUUID *>(type))
|
||||
types.emplace_back(ValueType::vtUUID, is_nullable);
|
||||
else if (typeid_cast<const DataTypeEnum8 *>(type))
|
||||
types.emplace_back(ValueType::vtString, is_nullable);
|
||||
else if (typeid_cast<const DataTypeEnum16 *>(type))
|
||||
types.emplace_back(ValueType::vtString, is_nullable);
|
||||
else
|
||||
throw Exception{"Unsupported type " + type->getName(), ErrorCodes::UNKNOWN_TYPE};
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace DB
|
||||
///
|
||||
/// If a query returns data, the server sends an empty header block containing
|
||||
/// the description of resulting columns before executing the query.
|
||||
/// Using this block the client can initialise the output formatter and display the prefix of resulting table
|
||||
/// Using this block the client can initialize the output formatter and display the prefix of resulting table
|
||||
/// beforehand.
|
||||
|
||||
namespace Protocol
|
||||
|
@ -421,6 +421,7 @@ struct Settings : public SettingsCollection<Settings>
|
||||
M(SettingBool, input_format_values_interpret_expressions, true, "For Values format: if the field could not be parsed by streaming parser, run SQL parser and try to interpret it as SQL expression.", 0) \
|
||||
M(SettingBool, input_format_values_deduce_templates_of_expressions, true, "For Values format: if the field could not be parsed by streaming parser, run SQL parser, deduce template of the SQL expression, try to parse all rows using template and then interpret expression for all rows.", 0) \
|
||||
M(SettingBool, input_format_values_accurate_types_of_literals, true, "For Values format: when parsing and interpreting expressions using template, check actual type of literal to avoid possible overflow and precision issues.", 0) \
|
||||
M(SettingBool, input_format_avro_allow_missing_fields, false, "For Avro/AvroConfluent format: when field is not found in schema use default value instead of error", 0) \
|
||||
M(SettingURI, format_avro_schema_registry_url, {}, "For AvroConfluent format: Confluent Schema Registry URL.", 0) \
|
||||
\
|
||||
M(SettingBool, output_format_json_quote_64bit_integers, true, "Controls quoting of 64-bit integers in JSON output format.", 0) \
|
||||
|
@ -56,7 +56,7 @@ void testGetFractional(const DecimalUtilsSplitAndCombineTestParam & param)
|
||||
DecimalUtils::getFractionalPart(DecimalType{param.decimal_value}, param.scale));
|
||||
}
|
||||
|
||||
// unfortunatelly typed parametrized tests () are not supported in this version of gtest, so I have to emulate by hand.
|
||||
// Unfortunately typed parametrized tests () are not supported in this version of gtest, so I have to emulate by hand.
|
||||
TEST_P(DecimalUtilsSplitAndCombineTest, splitDecimal32)
|
||||
{
|
||||
testSplit<Decimal32>(GetParam());
|
||||
|
@ -2,37 +2,166 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <common/logger_useful.h>
|
||||
#include <Poco/MongoDB/Connection.h>
|
||||
#include <Poco/MongoDB/Cursor.h>
|
||||
#include <Poco/MongoDB/Element.h>
|
||||
#include <Poco/MongoDB/Database.h>
|
||||
#include <Poco/MongoDB/ObjectId.h>
|
||||
|
||||
#include <Columns/ColumnNullable.h>
|
||||
#include <Columns/ColumnString.h>
|
||||
#include <Columns/ColumnsNumber.h>
|
||||
#include <IO/ReadHelpers.h>
|
||||
#include <Common/assert_cast.h>
|
||||
#include <Common/quoteString.h>
|
||||
#include <ext/range.h>
|
||||
#include <DataStreams/MongoDBBlockInputStream.h>
|
||||
#include <Poco/URI.h>
|
||||
#include <Poco/Util/AbstractConfiguration.h>
|
||||
#include <Poco/Version.h>
|
||||
|
||||
// only after poco
|
||||
// naming conflict:
|
||||
// Poco/MongoDB/BSONWriter.h:54: void writeCString(const std::string & value);
|
||||
// src/IO/WriteHelpers.h:146 #define writeCString(s, buf)
|
||||
#include <IO/WriteHelpers.h>
|
||||
#include <Common/FieldVisitors.h>
|
||||
#include <Common/assert_cast.h>
|
||||
#include <ext/range.h>
|
||||
#include "DictionaryStructure.h"
|
||||
#include "MongoDBBlockInputStream.h"
|
||||
|
||||
#include <ext/enumerate.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int TYPE_MISMATCH;
|
||||
extern const int MONGODB_CANNOT_AUTHENTICATE;
|
||||
extern const int NOT_FOUND_COLUMN_IN_BLOCK;
|
||||
}
|
||||
|
||||
|
||||
#if POCO_VERSION < 0x01070800
|
||||
/// See https://pocoproject.org/forum/viewtopic.php?f=10&t=6326&p=11426&hilit=mongodb+auth#p11485
|
||||
void authenticate(Poco::MongoDB::Connection & connection, const std::string & database, const std::string & user, const std::string & password)
|
||||
{
|
||||
Poco::MongoDB::Database db(database);
|
||||
|
||||
/// Challenge-response authentication.
|
||||
std::string nonce;
|
||||
|
||||
/// First step: request nonce.
|
||||
{
|
||||
auto command = db.createCommand();
|
||||
command->setNumberToReturn(1);
|
||||
command->selector().add<Int32>("getnonce", 1);
|
||||
|
||||
Poco::MongoDB::ResponseMessage response;
|
||||
connection.sendRequest(*command, response);
|
||||
|
||||
if (response.documents().empty())
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned empty response for 'getnonce' command",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
|
||||
auto doc = response.documents()[0];
|
||||
try
|
||||
{
|
||||
double ok = doc->get<double>("ok", 0);
|
||||
if (ok != 1)
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'getnonce' command that"
|
||||
" has field 'ok' missing or having wrong value",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
|
||||
nonce = doc->get<std::string>("nonce", "");
|
||||
if (nonce.empty())
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'getnonce' command that"
|
||||
" has field 'nonce' missing or empty",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
catch (Poco::NotFoundException & e)
|
||||
{
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'getnonce' command that has missing required field: "
|
||||
+ e.displayText(),
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
}
|
||||
|
||||
/// Second step: use nonce to calculate digest and send it back to the server.
|
||||
/// Digest is hex_md5(n.nonce + username + hex_md5(username + ":mongo:" + password))
|
||||
{
|
||||
std::string first = user + ":mongo:" + password;
|
||||
|
||||
Poco::MD5Engine md5;
|
||||
md5.update(first);
|
||||
std::string digest_first(Poco::DigestEngine::digestToHex(md5.digest()));
|
||||
std::string second = nonce + user + digest_first;
|
||||
md5.reset();
|
||||
md5.update(second);
|
||||
std::string digest_second(Poco::DigestEngine::digestToHex(md5.digest()));
|
||||
|
||||
auto command = db.createCommand();
|
||||
command->setNumberToReturn(1);
|
||||
command->selector()
|
||||
.add<Int32>("authenticate", 1)
|
||||
.add<std::string>("user", user)
|
||||
.add<std::string>("nonce", nonce)
|
||||
.add<std::string>("key", digest_second);
|
||||
|
||||
Poco::MongoDB::ResponseMessage response;
|
||||
connection.sendRequest(*command, response);
|
||||
|
||||
if (response.empty())
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned empty response for 'authenticate' command",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
|
||||
auto doc = response.documents()[0];
|
||||
try
|
||||
{
|
||||
double ok = doc->get<double>("ok", 0);
|
||||
if (ok != 1)
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'authenticate' command that"
|
||||
" has field 'ok' missing or having wrong value",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
catch (Poco::NotFoundException & e)
|
||||
{
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'authenticate' command that has missing required field: "
|
||||
+ e.displayText(),
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Poco::MongoDB::Cursor> createCursor(const std::string & database, const std::string & collection, const Block & sample_block_to_select)
|
||||
{
|
||||
auto cursor = std::make_unique<Poco::MongoDB::Cursor>(database, collection);
|
||||
|
||||
/// Looks like selecting _id column is implicit by default.
|
||||
if (!sample_block_to_select.has("_id"))
|
||||
cursor->query().returnFieldSelector().add("_id", 0);
|
||||
|
||||
for (const auto & column : sample_block_to_select)
|
||||
cursor->query().returnFieldSelector().add(column.name, 1);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
MongoDBBlockInputStream::MongoDBBlockInputStream(
|
||||
std::shared_ptr<Poco::MongoDB::Connection> & connection_,
|
||||
std::unique_ptr<Poco::MongoDB::Cursor> cursor_,
|
||||
const Block & sample_block,
|
||||
const UInt64 max_block_size_)
|
||||
: connection(connection_), cursor{std::move(cursor_)}, max_block_size{max_block_size_}
|
||||
UInt64 max_block_size_,
|
||||
bool strict_check_names_)
|
||||
: connection(connection_)
|
||||
, cursor{std::move(cursor_)}
|
||||
, max_block_size{max_block_size_}
|
||||
, strict_check_names{strict_check_names_}
|
||||
{
|
||||
description.init(sample_block);
|
||||
}
|
||||
@ -192,13 +321,17 @@ Block MongoDBBlockInputStream::readImpl()
|
||||
{
|
||||
Poco::MongoDB::ResponseMessage & response = cursor->next(*connection);
|
||||
|
||||
for (const auto & document : response.documents())
|
||||
for (auto & document : response.documents())
|
||||
{
|
||||
++num_rows;
|
||||
|
||||
for (const auto idx : ext::range(0, size))
|
||||
{
|
||||
const auto & name = description.sample_block.getByPosition(idx).name;
|
||||
|
||||
if (strict_check_names && !document->exists(name))
|
||||
throw Exception(fmt::format("Column {} is absent in MongoDB collection", backQuote(name)), ErrorCodes::NOT_FOUND_COLUMN_IN_BLOCK);
|
||||
|
||||
const Poco::MongoDB::Element::Ptr value = document->get(name);
|
||||
|
||||
if (value.isNull() || value->type() == Poco::MongoDB::ElementTraits<Poco::MongoDB::NullValue>::TypeId)
|
@ -14,9 +14,13 @@ namespace MongoDB
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
void authenticate(Poco::MongoDB::Connection & connection, const std::string & database, const std::string & user, const std::string & password);
|
||||
|
||||
std::unique_ptr<Poco::MongoDB::Cursor> createCursor(const std::string & database, const std::string & collection, const Block & sample_block_to_select);
|
||||
|
||||
/// Converts MongoDB Cursor to a stream of Blocks
|
||||
class MongoDBBlockInputStream final : public IBlockInputStream
|
||||
{
|
||||
@ -25,7 +29,8 @@ public:
|
||||
std::shared_ptr<Poco::MongoDB::Connection> & connection_,
|
||||
std::unique_ptr<Poco::MongoDB::Cursor> cursor_,
|
||||
const Block & sample_block,
|
||||
const UInt64 max_block_size_);
|
||||
UInt64 max_block_size_,
|
||||
bool strict_check_names_ = false);
|
||||
|
||||
~MongoDBBlockInputStream() override;
|
||||
|
||||
@ -41,6 +46,10 @@ private:
|
||||
const UInt64 max_block_size;
|
||||
ExternalResultDescription description;
|
||||
bool all_read = false;
|
||||
|
||||
/// if true stream will check, that all required fields present in MongoDB
|
||||
/// collection, otherwise throw exception.
|
||||
bool strict_check_names;
|
||||
};
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ LIBRARY()
|
||||
|
||||
PEERDIR(
|
||||
clickhouse/src/Common
|
||||
contrib/libs/poco/MongoDB
|
||||
)
|
||||
|
||||
NO_COMPILER_WARNINGS()
|
||||
@ -44,6 +45,7 @@ SRCS(
|
||||
SquashingBlockOutputStream.cpp
|
||||
SquashingTransform.cpp
|
||||
TTLBlockInputStream.cpp
|
||||
MongoDBBlockInputStream.cpp
|
||||
)
|
||||
|
||||
END()
|
||||
|
@ -112,6 +112,10 @@ void registerDataTypeDomainIPv4AndIPv6(DataTypeFactory & factory)
|
||||
return std::make_pair(DataTypeFactory::instance().get("FixedString(16)"),
|
||||
std::make_unique<DataTypeCustomDesc>(std::make_unique<DataTypeCustomFixedName>("IPv6"), std::make_unique<DataTypeCustomIPv6Serialization>()));
|
||||
});
|
||||
|
||||
/// MySQL, MariaDB
|
||||
factory.registerAlias("INET4", "IPv4", DataTypeFactory::CaseInsensitive);
|
||||
factory.registerAlias("INET6", "IPv6", DataTypeFactory::CaseInsensitive);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public:
|
||||
* OR
|
||||
* R execute(DateTime64 value, Int64 scale_factor, ... , const TimeZoneImpl &)
|
||||
*
|
||||
* Wehere R and T could be arbitrary types.
|
||||
* Where R and T could be arbitrary types.
|
||||
*/
|
||||
template <typename Transform>
|
||||
class TransformDateTime64 : public Transform
|
||||
|
@ -348,6 +348,25 @@ Field DataTypeEnum<Type>::castToValue(const Field & value_or_name) const
|
||||
}
|
||||
|
||||
|
||||
template <typename Type>
|
||||
bool DataTypeEnum<Type>::contains(const IDataType & rhs) const
|
||||
{
|
||||
auto check = [&](const auto & value)
|
||||
{
|
||||
auto it = name_to_value_map.find(value.first);
|
||||
if (it == name_to_value_map.end())
|
||||
return false;
|
||||
return it->value.second == value.second;
|
||||
};
|
||||
|
||||
if (const auto * rhs_enum8 = typeid_cast<const DataTypeEnum8 *>(&rhs))
|
||||
return std::all_of(rhs_enum8->getValues().begin(), rhs_enum8->getValues().end(), check);
|
||||
if (const auto * rhs_enum16 = typeid_cast<const DataTypeEnum16 *>(&rhs))
|
||||
return std::all_of(rhs_enum16->getValues().begin(), rhs_enum16->getValues().end(), check);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// Explicit instantiations.
|
||||
template class DataTypeEnum<Int8>;
|
||||
template class DataTypeEnum<Int16>;
|
||||
|
@ -119,6 +119,9 @@ public:
|
||||
|
||||
bool textCanContainOnlyValidUTF8() const override;
|
||||
size_t getSizeOfValueInMemory() const override { return sizeof(FieldType); }
|
||||
|
||||
/// Check current Enum type extends another Enum type (contains all fields with same values).
|
||||
bool contains(const IDataType & rhs) const;
|
||||
};
|
||||
|
||||
|
||||
|
@ -397,7 +397,7 @@ void registerDataTypeString(DataTypeFactory & factory)
|
||||
{
|
||||
factory.registerDataType("String", create);
|
||||
|
||||
/// These synonyms are added for compatibility.
|
||||
/// These synonims are added for compatibility.
|
||||
|
||||
factory.registerAlias("CHAR", "String", DataTypeFactory::CaseInsensitive);
|
||||
factory.registerAlias("NCHAR", "String", DataTypeFactory::CaseInsensitive);
|
||||
|
@ -48,7 +48,7 @@ void registerDataTypeNumbers(DataTypeFactory & factory)
|
||||
factory.registerDataType("Float32", createNumericDataType<Float32>);
|
||||
factory.registerDataType("Float64", createNumericDataType<Float64>);
|
||||
|
||||
/// These synonyms are added for compatibility.
|
||||
/// These synonims are added for compatibility.
|
||||
|
||||
factory.registerAlias("TINYINT", "Int8", DataTypeFactory::CaseInsensitive);
|
||||
factory.registerAlias("BOOL", "Int8", DataTypeFactory::CaseInsensitive);
|
||||
|
@ -423,7 +423,7 @@ void DatabaseOnDisk::iterateMetadataFiles(const Context & context, const Iterati
|
||||
}
|
||||
}
|
||||
|
||||
ASTPtr DatabaseOnDisk::parseQueryFromMetadata(Poco::Logger * loger, const Context & context, const String & metadata_file_path, bool throw_on_error /*= true*/, bool remove_empty /*= false*/)
|
||||
ASTPtr DatabaseOnDisk::parseQueryFromMetadata(Poco::Logger * logger, const Context & context, const String & metadata_file_path, bool throw_on_error /*= true*/, bool remove_empty /*= false*/)
|
||||
{
|
||||
String query;
|
||||
|
||||
@ -445,7 +445,7 @@ ASTPtr DatabaseOnDisk::parseQueryFromMetadata(Poco::Logger * loger, const Contex
|
||||
*/
|
||||
if (remove_empty && query.empty())
|
||||
{
|
||||
LOG_ERROR(loger, "File {} is empty. Removing.", metadata_file_path);
|
||||
LOG_ERROR(logger, "File {} is empty. Removing.", metadata_file_path);
|
||||
Poco::File(metadata_file_path).remove();
|
||||
return nullptr;
|
||||
}
|
||||
@ -469,7 +469,7 @@ ASTPtr DatabaseOnDisk::parseQueryFromMetadata(Poco::Logger * loger, const Contex
|
||||
table_name = unescapeForFileName(table_name);
|
||||
|
||||
if (create.table != TABLE_WITH_UUID_NAME_PLACEHOLDER)
|
||||
LOG_WARNING(loger, "File {} contains both UUID and table name. Will use name `{}` instead of `{}`", metadata_file_path, table_name, create.table);
|
||||
LOG_WARNING(logger, "File {} contains both UUID and table name. Will use name `{}` instead of `{}`", metadata_file_path, table_name, create.table);
|
||||
create.table = table_name;
|
||||
}
|
||||
|
||||
|
226
src/Dictionaries/BucketCache.h
Normal file
226
src/Dictionaries/BucketCache.h
Normal file
@ -0,0 +1,226 @@
|
||||
#pragma once
|
||||
|
||||
#include <Common/HashTable/Hash.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
inline size_t roundUpToPowerOfTwoOrZero(size_t x)
|
||||
{
|
||||
size_t r = 8;
|
||||
while (x > r)
|
||||
r <<= 1;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyDeleter {};
|
||||
|
||||
struct Int64Hasher
|
||||
{
|
||||
size_t operator()(const size_t x) const
|
||||
{
|
||||
return intHash64(x);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Class for storing cache index.
|
||||
It consists of two arrays.
|
||||
The first one is splitted into buckets (each stores 8 elements (cells)) determined by hash of the element key.
|
||||
The second one is splitted into 4bit numbers, which are positions in bucket for next element write (So cache uses FIFO eviction algorithm inside each bucket).
|
||||
*/
|
||||
template <typename K, typename V, typename Hasher, typename Deleter = EmptyDeleter>
|
||||
class BucketCacheIndex
|
||||
{
|
||||
struct Cell
|
||||
{
|
||||
K key;
|
||||
V index;
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename = std::enable_if<std::is_same_v<EmptyDeleter, Deleter>>>
|
||||
BucketCacheIndex(size_t cells_)
|
||||
: buckets(roundUpToPowerOfTwoOrZero(cells_) / bucket_size)
|
||||
, bucket_mask(buckets - 1)
|
||||
, cells(buckets * bucket_size)
|
||||
, positions((buckets / 2) + 1)
|
||||
{
|
||||
for (auto & cell : cells)
|
||||
cell.index.setNotExists();
|
||||
for (size_t bucket = 0; bucket < buckets; ++bucket)
|
||||
setPosition(bucket, 0);
|
||||
}
|
||||
|
||||
template <typename = std::enable_if<!std::is_same_v<EmptyDeleter, Deleter>>>
|
||||
BucketCacheIndex(size_t cells_, Deleter deleter_)
|
||||
: deleter(deleter_)
|
||||
, buckets(roundUpToPowerOfTwoOrZero(cells_) / bucket_size)
|
||||
, bucket_mask(buckets - 1)
|
||||
, cells(buckets * bucket_size)
|
||||
, positions((buckets / 2) + 1)
|
||||
{
|
||||
for (auto & cell : cells)
|
||||
cell.index.setNotExists();
|
||||
for (size_t bucket = 0; bucket < buckets; ++bucket)
|
||||
setPosition(bucket, 0);
|
||||
}
|
||||
|
||||
void set(K key, V val)
|
||||
{
|
||||
const size_t bucket = (hash(key) & bucket_mask);
|
||||
const size_t idx = getCellIndex(key, bucket);
|
||||
if (!cells[idx].index.exists())
|
||||
{
|
||||
incPosition(bucket);
|
||||
++sz;
|
||||
}
|
||||
|
||||
cells[idx].key = key;
|
||||
cells[idx].index = val;
|
||||
}
|
||||
|
||||
template <typename = std::enable_if<!std::is_same_v<EmptyDeleter, Deleter>>>
|
||||
void setWithDelete(K key, V val)
|
||||
{
|
||||
const size_t bucket = (hash(key) & bucket_mask);
|
||||
const size_t idx = getCellIndex(key, bucket);
|
||||
if (!cells[idx].index.exists())
|
||||
{
|
||||
incPosition(bucket);
|
||||
++sz;
|
||||
}
|
||||
else
|
||||
{
|
||||
deleter(cells[idx].key);
|
||||
}
|
||||
|
||||
cells[idx].key = key;
|
||||
cells[idx].index = val;
|
||||
}
|
||||
|
||||
bool get(K key, V & val) const
|
||||
{
|
||||
const size_t bucket = (hash(key) & bucket_mask);
|
||||
const size_t idx = getCellIndex(key, bucket);
|
||||
if (!cells[idx].index.exists() || cells[idx].key != key)
|
||||
return false;
|
||||
val = cells[idx].index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getKeyAndValue(K & key, V & val) const
|
||||
{
|
||||
const size_t bucket = (hash(key) & bucket_mask);
|
||||
const size_t idx = getCellIndex(key, bucket);
|
||||
if (!cells[idx].index.exists() || cells[idx].key != key)
|
||||
return false;
|
||||
key = cells[idx].key;
|
||||
val = cells[idx].index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool erase(K key)
|
||||
{
|
||||
const size_t bucket = (hash(key) & bucket_mask);
|
||||
const size_t idx = getCellIndex(key, bucket);
|
||||
if (!cells[idx].index.exists() || cells[idx].key != key)
|
||||
return false;
|
||||
|
||||
cells[idx].index.setNotExists();
|
||||
--sz;
|
||||
if constexpr (!std::is_same_v<EmptyDeleter, Deleter>)
|
||||
deleter(cells[idx].key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return sz;
|
||||
}
|
||||
|
||||
size_t capacity() const
|
||||
{
|
||||
return cells.size();
|
||||
}
|
||||
|
||||
auto keys() const
|
||||
{
|
||||
std::vector<K> res;
|
||||
for (const auto & cell : cells)
|
||||
{
|
||||
if (cell.index.exists())
|
||||
{
|
||||
res.push_back(cell.key);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Searches for the key in the bucket.
|
||||
/// Returns index of cell with provided key.
|
||||
size_t getCellIndex(const K key, const size_t bucket) const
|
||||
{
|
||||
const size_t pos = getPosition(bucket);
|
||||
for (int idx = 7; idx >= 0; --idx)
|
||||
{
|
||||
const size_t cur = ((pos + 1 + idx) & pos_mask);
|
||||
if (cells[bucket * bucket_size + cur].index.exists() &&
|
||||
cells[bucket * bucket_size + cur].key == key)
|
||||
{
|
||||
return bucket * bucket_size + cur;
|
||||
}
|
||||
}
|
||||
|
||||
return bucket * bucket_size + pos;
|
||||
}
|
||||
|
||||
/// Returns current position for write in the bucket.
|
||||
size_t getPosition(const size_t bucket) const
|
||||
{
|
||||
const size_t idx = (bucket >> 1);
|
||||
if ((bucket & 1) == 0)
|
||||
return ((positions[idx] >> 4) & pos_mask);
|
||||
return (positions[idx] & pos_mask);
|
||||
}
|
||||
|
||||
/// Sets current posiotion in the bucket.
|
||||
void setPosition(const size_t bucket, const size_t pos)
|
||||
{
|
||||
const size_t idx = bucket >> 1;
|
||||
if ((bucket & 1) == 0)
|
||||
positions[idx] = ((pos << 4) | (positions[idx] & ((1 << 4) - 1)));
|
||||
else
|
||||
positions[idx] = (pos | (positions[idx] & (((1 << 4) - 1) << 4)));
|
||||
}
|
||||
|
||||
void incPosition(const size_t bucket)
|
||||
{
|
||||
setPosition(bucket, (getPosition(bucket) + 1) & pos_mask);
|
||||
}
|
||||
|
||||
static constexpr size_t bucket_size = 8;
|
||||
static constexpr size_t pos_size = 3;
|
||||
static constexpr size_t pos_mask = (1 << pos_size) - 1;
|
||||
|
||||
Hasher hash;
|
||||
Deleter deleter;
|
||||
|
||||
size_t buckets;
|
||||
size_t bucket_mask;
|
||||
|
||||
std::vector<Cell> cells;
|
||||
std::vector<char> positions;
|
||||
size_t sz = 0;
|
||||
};
|
||||
|
||||
}
|
@ -34,7 +34,6 @@ void registerDictionarySourceMongoDB(DictionarySourceFactory & factory)
|
||||
|
||||
}
|
||||
|
||||
|
||||
#include <common/logger_useful.h>
|
||||
#include <Poco/MongoDB/Array.h>
|
||||
#include <Poco/MongoDB/Connection.h>
|
||||
@ -52,7 +51,7 @@ void registerDictionarySourceMongoDB(DictionarySourceFactory & factory)
|
||||
#include <IO/WriteHelpers.h>
|
||||
#include <Common/FieldVisitors.h>
|
||||
#include <ext/enumerate.h>
|
||||
#include "MongoDBBlockInputStream.h"
|
||||
#include <DataStreams/MongoDBBlockInputStream.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -67,107 +66,6 @@ namespace ErrorCodes
|
||||
static const UInt64 max_block_size = 8192;
|
||||
|
||||
|
||||
#if POCO_VERSION < 0x01070800
|
||||
/// See https://pocoproject.org/forum/viewtopic.php?f=10&t=6326&p=11426&hilit=mongodb+auth#p11485
|
||||
static void
|
||||
authenticate(Poco::MongoDB::Connection & connection, const std::string & database, const std::string & user, const std::string & password)
|
||||
{
|
||||
Poco::MongoDB::Database db(database);
|
||||
|
||||
/// Challenge-response authentication.
|
||||
std::string nonce;
|
||||
|
||||
/// First step: request nonce.
|
||||
{
|
||||
auto command = db.createCommand();
|
||||
command->setNumberToReturn(1);
|
||||
command->selector().add<Int32>("getnonce", 1);
|
||||
|
||||
Poco::MongoDB::ResponseMessage response;
|
||||
connection.sendRequest(*command, response);
|
||||
|
||||
if (response.documents().empty())
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned empty response for 'getnonce' command",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
|
||||
auto doc = response.documents()[0];
|
||||
try
|
||||
{
|
||||
double ok = doc->get<double>("ok", 0);
|
||||
if (ok != 1)
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'getnonce' command that"
|
||||
" has field 'ok' missing or having wrong value",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
|
||||
nonce = doc->get<std::string>("nonce", "");
|
||||
if (nonce.empty())
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'getnonce' command that"
|
||||
" has field 'nonce' missing or empty",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
catch (Poco::NotFoundException & e)
|
||||
{
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'getnonce' command that has missing required field: "
|
||||
+ e.displayText(),
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
}
|
||||
|
||||
/// Second step: use nonce to calculate digest and send it back to the server.
|
||||
/// Digest is hex_md5(n.nonce + username + hex_md5(username + ":mongo:" + password))
|
||||
{
|
||||
std::string first = user + ":mongo:" + password;
|
||||
|
||||
Poco::MD5Engine md5;
|
||||
md5.update(first);
|
||||
std::string digest_first(Poco::DigestEngine::digestToHex(md5.digest()));
|
||||
std::string second = nonce + user + digest_first;
|
||||
md5.reset();
|
||||
md5.update(second);
|
||||
std::string digest_second(Poco::DigestEngine::digestToHex(md5.digest()));
|
||||
|
||||
auto command = db.createCommand();
|
||||
command->setNumberToReturn(1);
|
||||
command->selector()
|
||||
.add<Int32>("authenticate", 1)
|
||||
.add<std::string>("user", user)
|
||||
.add<std::string>("nonce", nonce)
|
||||
.add<std::string>("key", digest_second);
|
||||
|
||||
Poco::MongoDB::ResponseMessage response;
|
||||
connection.sendRequest(*command, response);
|
||||
|
||||
if (response.empty())
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned empty response for 'authenticate' command",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
|
||||
auto doc = response.documents()[0];
|
||||
try
|
||||
{
|
||||
double ok = doc->get<double>("ok", 0);
|
||||
if (ok != 1)
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'authenticate' command that"
|
||||
" has field 'ok' missing or having wrong value",
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
catch (Poco::NotFoundException & e)
|
||||
{
|
||||
throw Exception(
|
||||
"Cannot authenticate in MongoDB: server returned response for 'authenticate' command that has missing required field: "
|
||||
+ e.displayText(),
|
||||
ErrorCodes::MONGODB_CANNOT_AUTHENTICATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
MongoDBDictionarySource::MongoDBDictionarySource(
|
||||
const DictionaryStructure & dict_struct_,
|
||||
const std::string & uri_,
|
||||
@ -242,32 +140,13 @@ MongoDBDictionarySource::MongoDBDictionarySource(const MongoDBDictionarySource &
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
MongoDBDictionarySource::~MongoDBDictionarySource() = default;
|
||||
|
||||
|
||||
static std::unique_ptr<Poco::MongoDB::Cursor>
|
||||
createCursor(const std::string & database, const std::string & collection, const Block & sample_block_to_select)
|
||||
{
|
||||
auto cursor = std::make_unique<Poco::MongoDB::Cursor>(database, collection);
|
||||
|
||||
/// Looks like selecting _id column is implicit by default.
|
||||
if (!sample_block_to_select.has("_id"))
|
||||
cursor->query().returnFieldSelector().add("_id", 0);
|
||||
|
||||
for (const auto & column : sample_block_to_select)
|
||||
cursor->query().returnFieldSelector().add(column.name, 1);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
|
||||
BlockInputStreamPtr MongoDBDictionarySource::loadAll()
|
||||
{
|
||||
return std::make_shared<MongoDBBlockInputStream>(connection, createCursor(db, collection, sample_block), sample_block, max_block_size);
|
||||
}
|
||||
|
||||
|
||||
BlockInputStreamPtr MongoDBDictionarySource::loadIds(const std::vector<UInt64> & ids)
|
||||
{
|
||||
if (!dict_struct.id)
|
||||
@ -349,7 +228,6 @@ BlockInputStreamPtr MongoDBDictionarySource::loadKeys(const Columns & key_column
|
||||
return std::make_shared<MongoDBBlockInputStream>(connection, std::move(cursor), sample_block, max_block_size);
|
||||
}
|
||||
|
||||
|
||||
std::string MongoDBDictionarySource::toString() const
|
||||
{
|
||||
return "MongoDB: " + db + '.' + collection + ',' + (user.empty() ? " " : " " + user + '@') + host + ':' + DB::toString(port);
|
||||
|
@ -15,10 +15,10 @@ namespace Util
|
||||
namespace MongoDB
|
||||
{
|
||||
class Connection;
|
||||
class Cursor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
namespace ErrorCodes
|
||||
|
@ -73,7 +73,7 @@ namespace DB
|
||||
ErrorCodes::INVALID_CONFIG_PARAMETER};
|
||||
|
||||
if (dict_struct.key->size() != 2)
|
||||
throw Exception{"Redis source with storage type \'hash_map\' requiers 2 keys",
|
||||
throw Exception{"Redis source with storage type \'hash_map\' requires 2 keys",
|
||||
ErrorCodes::INVALID_CONFIG_PARAMETER};
|
||||
// suppose key[0] is primary key, key[1] is secondary key
|
||||
}
|
||||
|
1699
src/Dictionaries/SSDCacheDictionary.cpp
Normal file
1699
src/Dictionaries/SSDCacheDictionary.cpp
Normal file
File diff suppressed because it is too large
Load Diff
471
src/Dictionaries/SSDCacheDictionary.h
Normal file
471
src/Dictionaries/SSDCacheDictionary.h
Normal file
@ -0,0 +1,471 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
|
||||
#include "DictionaryStructure.h"
|
||||
#include "IDictionary.h"
|
||||
#include "IDictionarySource.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <Columns/ColumnDecimal.h>
|
||||
#include <Columns/ColumnString.h>
|
||||
#include <Common/ArenaWithFreeLists.h>
|
||||
#include <Common/CurrentMetrics.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <Compression/CompressedWriteBuffer.h>
|
||||
#include <Core/Block.h>
|
||||
#include <Dictionaries/BucketCache.h>
|
||||
#include <IO/HashingWriteBuffer.h>
|
||||
#include <IO/WriteBufferAIO.h>
|
||||
#include <list>
|
||||
#include <pcg_random.hpp>
|
||||
#include <Poco/Logger.h>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
using AttributeValueVariant = std::variant<
|
||||
UInt8,
|
||||
UInt16,
|
||||
UInt32,
|
||||
UInt64,
|
||||
UInt128,
|
||||
Int8,
|
||||
Int16,
|
||||
Int32,
|
||||
Int64,
|
||||
Decimal32,
|
||||
Decimal64,
|
||||
Decimal128,
|
||||
Float32,
|
||||
Float64,
|
||||
String>;
|
||||
|
||||
|
||||
/*
|
||||
Class for operations with cache file and index.
|
||||
Supports GET/SET operations.
|
||||
*/
|
||||
class SSDCachePartition
|
||||
{
|
||||
public:
|
||||
struct Index final
|
||||
{
|
||||
bool inMemory() const;
|
||||
void setInMemory(bool in_memory);
|
||||
|
||||
bool exists() const;
|
||||
void setNotExists();
|
||||
|
||||
size_t getAddressInBlock() const;
|
||||
void setAddressInBlock(size_t address_in_block);
|
||||
|
||||
size_t getBlockId() const;
|
||||
void setBlockId(size_t block_id);
|
||||
|
||||
bool operator< (const Index & rhs) const { return index < rhs.index; }
|
||||
|
||||
/// Stores `is_in_memory` flag, block id, address in uncompressed block
|
||||
uint64_t index = 0;
|
||||
};
|
||||
|
||||
struct Metadata final
|
||||
{
|
||||
using time_point_t = std::chrono::system_clock::time_point;
|
||||
using time_point_rep_t = time_point_t::rep;
|
||||
using time_point_urep_t = std::make_unsigned_t<time_point_rep_t>;
|
||||
|
||||
time_point_t expiresAt() const;
|
||||
void setExpiresAt(const time_point_t & t);
|
||||
|
||||
bool isDefault() const;
|
||||
void setDefault();
|
||||
|
||||
/// Stores both expiration time and `is_default` flag in the most significant bit
|
||||
time_point_urep_t data = 0;
|
||||
};
|
||||
|
||||
using Offset = size_t;
|
||||
using Offsets = std::vector<Offset>;
|
||||
using Key = IDictionary::Key;
|
||||
|
||||
SSDCachePartition(
|
||||
const AttributeUnderlyingType & key_structure,
|
||||
const std::vector<AttributeUnderlyingType> & attributes_structure,
|
||||
const std::string & dir_path,
|
||||
size_t file_id,
|
||||
size_t max_size,
|
||||
size_t block_size,
|
||||
size_t read_buffer_size,
|
||||
size_t write_buffer_size,
|
||||
size_t max_stored_keys);
|
||||
|
||||
~SSDCachePartition();
|
||||
|
||||
template <typename T>
|
||||
using ResultArrayType = std::conditional_t<IsDecimalNumber<T>, DecimalPaddedPODArray<T>, PaddedPODArray<T>>;
|
||||
|
||||
template <typename Out, typename GetDefault>
|
||||
void getValue(size_t attribute_index, const PaddedPODArray<UInt64> & ids,
|
||||
ResultArrayType<Out> & out, std::vector<bool> & found, GetDefault & get_default,
|
||||
std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void getString(size_t attribute_index, const PaddedPODArray<UInt64> & ids,
|
||||
StringRefs & refs, ArenaWithFreeLists & arena, std::vector<bool> & found,
|
||||
std::vector<size_t> & default_ids, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void has(const PaddedPODArray<UInt64> & ids, ResultArrayType<UInt8> & out,
|
||||
std::vector<bool> & found, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
struct Attribute
|
||||
{
|
||||
template <typename T>
|
||||
using Container = std::vector<T>;
|
||||
|
||||
AttributeUnderlyingType type;
|
||||
std::variant<
|
||||
Container<UInt8>,
|
||||
Container<UInt16>,
|
||||
Container<UInt32>,
|
||||
Container<UInt64>,
|
||||
Container<UInt128>,
|
||||
Container<Int8>,
|
||||
Container<Int16>,
|
||||
Container<Int32>,
|
||||
Container<Int64>,
|
||||
Container<Decimal32>,
|
||||
Container<Decimal64>,
|
||||
Container<Decimal128>,
|
||||
Container<Float32>,
|
||||
Container<Float64>,
|
||||
Container<String>> values;
|
||||
};
|
||||
using Attributes = std::vector<Attribute>;
|
||||
|
||||
size_t appendBlock(const Attribute & new_keys, const Attributes & new_attributes,
|
||||
const PaddedPODArray<Metadata> & metadata, size_t begin);
|
||||
|
||||
size_t appendDefaults(const Attribute & new_keys, const PaddedPODArray<Metadata> & metadata, size_t begin);
|
||||
|
||||
void flush();
|
||||
|
||||
void remove();
|
||||
|
||||
size_t getId() const;
|
||||
|
||||
PaddedPODArray<Key> getCachedIds(std::chrono::system_clock::time_point now) const;
|
||||
|
||||
double getLoadFactor() const;
|
||||
|
||||
size_t getElementCount() const;
|
||||
|
||||
size_t getBytesAllocated() const;
|
||||
|
||||
private:
|
||||
void clearOldestBlocks();
|
||||
|
||||
template <typename SetFunc>
|
||||
void getImpl(const PaddedPODArray<UInt64> & ids, SetFunc & set, std::vector<bool> & found) const;
|
||||
|
||||
template <typename SetFunc>
|
||||
void getValueFromMemory(const PaddedPODArray<Index> & indices, SetFunc & set) const;
|
||||
|
||||
template <typename SetFunc>
|
||||
void getValueFromStorage(const PaddedPODArray<Index> & indices, SetFunc & set) const;
|
||||
|
||||
void ignoreFromBufferToAttributeIndex(size_t attribute_index, ReadBuffer & buf) const;
|
||||
|
||||
const size_t file_id;
|
||||
const size_t max_size;
|
||||
const size_t block_size;
|
||||
const size_t read_buffer_size;
|
||||
const size_t write_buffer_size;
|
||||
const size_t max_stored_keys;
|
||||
const std::string path;
|
||||
|
||||
mutable std::shared_mutex rw_lock;
|
||||
|
||||
int fd = -1;
|
||||
|
||||
mutable BucketCacheIndex<UInt64, Index, Int64Hasher> key_to_index;
|
||||
|
||||
Attribute keys_buffer;
|
||||
const std::vector<AttributeUnderlyingType> attributes_structure;
|
||||
|
||||
std::optional<Memory<>> memory;
|
||||
std::optional<WriteBuffer> write_buffer;
|
||||
uint32_t keys_in_block = 0;
|
||||
|
||||
size_t current_memory_block_id = 0;
|
||||
size_t current_file_block_id = 0;
|
||||
};
|
||||
|
||||
using SSDCachePartitionPtr = std::shared_ptr<SSDCachePartition>;
|
||||
|
||||
|
||||
/*
|
||||
Class for managing SSDCachePartition and getting data from source.
|
||||
*/
|
||||
class SSDCacheStorage
|
||||
{
|
||||
public:
|
||||
using AttributeTypes = std::vector<AttributeUnderlyingType>;
|
||||
using Key = SSDCachePartition::Key;
|
||||
|
||||
SSDCacheStorage(
|
||||
const AttributeTypes & attributes_structure,
|
||||
const std::string & path,
|
||||
size_t max_partitions_count,
|
||||
size_t file_size,
|
||||
size_t block_size,
|
||||
size_t read_buffer_size,
|
||||
size_t write_buffer_size,
|
||||
size_t max_stored_keys);
|
||||
|
||||
~SSDCacheStorage();
|
||||
|
||||
template <typename T>
|
||||
using ResultArrayType = SSDCachePartition::ResultArrayType<T>;
|
||||
|
||||
template <typename Out, typename GetDefault>
|
||||
void getValue(size_t attribute_index, const PaddedPODArray<UInt64> & ids,
|
||||
ResultArrayType<Out> & out, std::unordered_map<Key, std::vector<size_t>> & not_found,
|
||||
GetDefault & get_default, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void getString(size_t attribute_index, const PaddedPODArray<UInt64> & ids,
|
||||
StringRefs & refs, ArenaWithFreeLists & arena, std::unordered_map<Key, std::vector<size_t>> & not_found,
|
||||
std::vector<size_t> & default_ids, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void has(const PaddedPODArray<UInt64> & ids, ResultArrayType<UInt8> & out,
|
||||
std::unordered_map<Key, std::vector<size_t>> & not_found, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
template <typename PresentIdHandler, typename AbsentIdHandler>
|
||||
void update(DictionarySourcePtr & source_ptr, const std::vector<Key> & requested_ids,
|
||||
PresentIdHandler && on_updated, AbsentIdHandler && on_id_not_found,
|
||||
DictionaryLifetime lifetime);
|
||||
|
||||
PaddedPODArray<Key> getCachedIds() const;
|
||||
|
||||
std::exception_ptr getLastException() const { return last_update_exception; }
|
||||
|
||||
const std::string & getPath() const { return path; }
|
||||
|
||||
size_t getQueryCount() const { return query_count.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t getHitCount() const { return hit_count.load(std::memory_order_acquire); }
|
||||
|
||||
size_t getElementCount() const;
|
||||
|
||||
double getLoadFactor() const;
|
||||
|
||||
size_t getBytesAllocated() const;
|
||||
|
||||
private:
|
||||
void collectGarbage();
|
||||
|
||||
const AttributeTypes attributes_structure;
|
||||
|
||||
const std::string path;
|
||||
const size_t max_partitions_count;
|
||||
const size_t file_size;
|
||||
const size_t block_size;
|
||||
const size_t read_buffer_size;
|
||||
const size_t write_buffer_size;
|
||||
const size_t max_stored_keys;
|
||||
|
||||
mutable std::shared_mutex rw_lock;
|
||||
std::list<SSDCachePartitionPtr> partitions;
|
||||
std::list<SSDCachePartitionPtr> partition_delete_queue;
|
||||
|
||||
Poco::Logger * const log;
|
||||
|
||||
mutable pcg64 rnd_engine;
|
||||
|
||||
mutable std::exception_ptr last_update_exception;
|
||||
mutable size_t update_error_count = 0;
|
||||
mutable std::chrono::system_clock::time_point backoff_end_time;
|
||||
|
||||
mutable std::atomic<size_t> hit_count{0};
|
||||
mutable std::atomic<size_t> query_count{0};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Dictionary interface
|
||||
*/
|
||||
class SSDCacheDictionary final : public IDictionary
|
||||
{
|
||||
public:
|
||||
SSDCacheDictionary(
|
||||
const std::string & name_,
|
||||
const DictionaryStructure & dict_struct_,
|
||||
DictionarySourcePtr source_ptr_,
|
||||
DictionaryLifetime dict_lifetime_,
|
||||
const std::string & path,
|
||||
size_t max_partitions_count_,
|
||||
size_t file_size_,
|
||||
size_t block_size_,
|
||||
size_t read_buffer_size_,
|
||||
size_t write_buffer_size_,
|
||||
size_t max_stored_keys_);
|
||||
|
||||
const std::string & getDatabase() const override { return name; }
|
||||
const std::string & getName() const override { return name; }
|
||||
const std::string & getFullName() const override { return getName(); }
|
||||
|
||||
std::string getTypeName() const override { return "SSDCache"; }
|
||||
|
||||
size_t getBytesAllocated() const override { return storage.getBytesAllocated(); }
|
||||
|
||||
size_t getQueryCount() const override { return storage.getQueryCount(); }
|
||||
|
||||
double getHitRate() const override
|
||||
{
|
||||
return static_cast<double>(storage.getHitCount()) / storage.getQueryCount();
|
||||
}
|
||||
|
||||
size_t getElementCount() const override { return storage.getElementCount(); }
|
||||
|
||||
double getLoadFactor() const override { return storage.getLoadFactor(); }
|
||||
|
||||
bool supportUpdates() const override { return false; }
|
||||
|
||||
std::shared_ptr<const IExternalLoadable> clone() const override
|
||||
{
|
||||
return std::make_shared<SSDCacheDictionary>(name, dict_struct, source_ptr->clone(), dict_lifetime, path,
|
||||
max_partitions_count, file_size, block_size, read_buffer_size, write_buffer_size, max_stored_keys);
|
||||
}
|
||||
|
||||
const IDictionarySource * getSource() const override { return source_ptr.get(); }
|
||||
|
||||
const DictionaryLifetime & getLifetime() const override { return dict_lifetime; }
|
||||
|
||||
const DictionaryStructure & getStructure() const override { return dict_struct; }
|
||||
|
||||
bool isInjective(const std::string & attribute_name) const override
|
||||
{
|
||||
return dict_struct.attributes[getAttributeIndex(attribute_name)].injective;
|
||||
}
|
||||
|
||||
bool hasHierarchy() const override { return false; }
|
||||
|
||||
void toParent(const PaddedPODArray<Key> &, PaddedPODArray<Key> &) const override { }
|
||||
|
||||
std::exception_ptr getLastException() const override { return storage.getLastException(); }
|
||||
|
||||
template <typename T>
|
||||
using ResultArrayType = SSDCacheStorage::ResultArrayType<T>;
|
||||
|
||||
#define DECLARE(TYPE) \
|
||||
void get##TYPE(const std::string & attribute_name, const PaddedPODArray<Key> & ids, ResultArrayType<TYPE> & out) const;
|
||||
DECLARE(UInt8)
|
||||
DECLARE(UInt16)
|
||||
DECLARE(UInt32)
|
||||
DECLARE(UInt64)
|
||||
DECLARE(UInt128)
|
||||
DECLARE(Int8)
|
||||
DECLARE(Int16)
|
||||
DECLARE(Int32)
|
||||
DECLARE(Int64)
|
||||
DECLARE(Float32)
|
||||
DECLARE(Float64)
|
||||
DECLARE(Decimal32)
|
||||
DECLARE(Decimal64)
|
||||
DECLARE(Decimal128)
|
||||
#undef DECLARE
|
||||
|
||||
void getString(const std::string & attribute_name, const PaddedPODArray<Key> & ids, ColumnString * out) const;
|
||||
|
||||
#define DECLARE(TYPE) \
|
||||
void get##TYPE( \
|
||||
const std::string & attribute_name, \
|
||||
const PaddedPODArray<Key> & ids, \
|
||||
const PaddedPODArray<TYPE> & def, \
|
||||
ResultArrayType<TYPE> & out) const;
|
||||
DECLARE(UInt8)
|
||||
DECLARE(UInt16)
|
||||
DECLARE(UInt32)
|
||||
DECLARE(UInt64)
|
||||
DECLARE(UInt128)
|
||||
DECLARE(Int8)
|
||||
DECLARE(Int16)
|
||||
DECLARE(Int32)
|
||||
DECLARE(Int64)
|
||||
DECLARE(Float32)
|
||||
DECLARE(Float64)
|
||||
DECLARE(Decimal32)
|
||||
DECLARE(Decimal64)
|
||||
DECLARE(Decimal128)
|
||||
#undef DECLARE
|
||||
|
||||
void
|
||||
getString(const std::string & attribute_name, const PaddedPODArray<Key> & ids, const ColumnString * def, ColumnString * out)
|
||||
const;
|
||||
|
||||
#define DECLARE(TYPE) \
|
||||
void get##TYPE(const std::string & attribute_name, const PaddedPODArray<Key> & ids, const TYPE def, ResultArrayType<TYPE> & out) const;
|
||||
DECLARE(UInt8)
|
||||
DECLARE(UInt16)
|
||||
DECLARE(UInt32)
|
||||
DECLARE(UInt64)
|
||||
DECLARE(UInt128)
|
||||
DECLARE(Int8)
|
||||
DECLARE(Int16)
|
||||
DECLARE(Int32)
|
||||
DECLARE(Int64)
|
||||
DECLARE(Float32)
|
||||
DECLARE(Float64)
|
||||
DECLARE(Decimal32)
|
||||
DECLARE(Decimal64)
|
||||
DECLARE(Decimal128)
|
||||
#undef DECLARE
|
||||
|
||||
void getString(const std::string & attribute_name, const PaddedPODArray<Key> & ids, const String & def, ColumnString * out) const;
|
||||
|
||||
void has(const PaddedPODArray<Key> & ids, PaddedPODArray<UInt8> & out) const override;
|
||||
|
||||
BlockInputStreamPtr getBlockInputStream(const Names & column_names, size_t max_block_size) const override;
|
||||
|
||||
private:
|
||||
size_t getAttributeIndex(const std::string & attr_name) const;
|
||||
|
||||
template <typename T>
|
||||
AttributeValueVariant createAttributeNullValueWithTypeImpl(const Field & null_value);
|
||||
AttributeValueVariant createAttributeNullValueWithType(AttributeUnderlyingType type, const Field & null_value);
|
||||
void createAttributes();
|
||||
|
||||
template <typename AttributeType, typename OutputType, typename DefaultGetter>
|
||||
void getItemsNumberImpl(
|
||||
size_t attribute_index, const PaddedPODArray<Key> & ids, ResultArrayType<OutputType> & out, DefaultGetter && get_default) const;
|
||||
|
||||
template <typename DefaultGetter>
|
||||
void getItemsStringImpl(size_t attribute_index, const PaddedPODArray<Key> & ids,
|
||||
ColumnString * out, DefaultGetter && get_default) const;
|
||||
|
||||
const std::string name;
|
||||
const DictionaryStructure dict_struct;
|
||||
mutable DictionarySourcePtr source_ptr;
|
||||
const DictionaryLifetime dict_lifetime;
|
||||
|
||||
const std::string path;
|
||||
const size_t max_partitions_count;
|
||||
const size_t file_size;
|
||||
const size_t block_size;
|
||||
const size_t read_buffer_size;
|
||||
const size_t write_buffer_size;
|
||||
const size_t max_stored_keys;
|
||||
|
||||
std::map<std::string, size_t> attribute_index_by_name;
|
||||
std::vector<AttributeValueVariant> null_values;
|
||||
mutable SSDCacheStorage storage;
|
||||
Poco::Logger * const log;
|
||||
|
||||
mutable size_t bytes_allocated = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
1798
src/Dictionaries/SSDComplexKeyCacheDictionary.cpp
Normal file
1798
src/Dictionaries/SSDComplexKeyCacheDictionary.cpp
Normal file
File diff suppressed because it is too large
Load Diff
706
src/Dictionaries/SSDComplexKeyCacheDictionary.h
Normal file
706
src/Dictionaries/SSDComplexKeyCacheDictionary.h
Normal file
@ -0,0 +1,706 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
|
||||
#include "DictionaryStructure.h"
|
||||
#include "IDictionary.h"
|
||||
#include "IDictionarySource.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <Columns/ColumnDecimal.h>
|
||||
#include <Columns/ColumnString.h>
|
||||
#include <Common/Arena.h>
|
||||
#include <Common/ArenaWithFreeLists.h>
|
||||
#include <Common/CurrentMetrics.h>
|
||||
#include <common/logger_useful.h>
|
||||
#include <Common/SmallObjectPool.h>
|
||||
#include <Compression/CompressedWriteBuffer.h>
|
||||
#include <Core/Block.h>
|
||||
#include <Dictionaries/BucketCache.h>
|
||||
#include <ext/scope_guard.h>
|
||||
#include <IO/HashingWriteBuffer.h>
|
||||
#include <IO/WriteBufferAIO.h>
|
||||
#include <list>
|
||||
#include <pcg_random.hpp>
|
||||
#include <Poco/Logger.h>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
class KeyRef
|
||||
{
|
||||
public:
|
||||
explicit KeyRef(char * data) : ptr(data) {}
|
||||
|
||||
KeyRef() : ptr(nullptr) {}
|
||||
|
||||
inline UInt16 size() const
|
||||
{
|
||||
UInt16 res;
|
||||
memcpy(&res, ptr, sizeof(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
inline size_t fullSize() const
|
||||
{
|
||||
return static_cast<size_t>(size()) + sizeof(UInt16);
|
||||
}
|
||||
|
||||
inline bool isNull() const
|
||||
{
|
||||
return ptr == nullptr;
|
||||
}
|
||||
|
||||
inline char * data() const
|
||||
{
|
||||
return ptr + sizeof(UInt16);
|
||||
}
|
||||
|
||||
inline char * fullData() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline char * fullData()
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline const StringRef getRef() const
|
||||
{
|
||||
return StringRef(data(), size());
|
||||
}
|
||||
|
||||
inline bool operator==(const KeyRef & other) const
|
||||
{
|
||||
return getRef() == other.getRef();
|
||||
}
|
||||
|
||||
inline bool operator!=(const KeyRef & other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
inline bool operator<(const KeyRef & other) const
|
||||
{
|
||||
return getRef() < other.getRef();
|
||||
}
|
||||
|
||||
private:
|
||||
char * ptr;
|
||||
};
|
||||
|
||||
using KeyRefs = std::vector<KeyRef>;
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct hash<DB::KeyRef>
|
||||
{
|
||||
size_t operator() (DB::KeyRef key_ref) const
|
||||
{
|
||||
return hasher(key_ref.getRef());
|
||||
}
|
||||
|
||||
std::hash<StringRef> hasher;
|
||||
};
|
||||
}
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
using AttributeValueVariant = std::variant<
|
||||
UInt8,
|
||||
UInt16,
|
||||
UInt32,
|
||||
UInt64,
|
||||
UInt128,
|
||||
Int8,
|
||||
Int16,
|
||||
Int32,
|
||||
Int64,
|
||||
Decimal32,
|
||||
Decimal64,
|
||||
Decimal128,
|
||||
Float32,
|
||||
Float64,
|
||||
String>;
|
||||
|
||||
/*
|
||||
The pool for storing complex keys.
|
||||
*/
|
||||
template <typename A>
|
||||
class ComplexKeysPoolImpl
|
||||
{
|
||||
public:
|
||||
KeyRef allocKey(const size_t row, const Columns & key_columns, StringRefs & keys)
|
||||
{
|
||||
const auto keys_size = key_columns.size();
|
||||
UInt16 sum_keys_size{};
|
||||
|
||||
for (size_t j = 0; j < keys_size; ++j)
|
||||
{
|
||||
keys[j] = key_columns[j]->getDataAt(row);
|
||||
sum_keys_size += keys[j].size;
|
||||
if (!key_columns[j]->valuesHaveFixedSize()) // String
|
||||
sum_keys_size += sizeof(size_t) + 1;
|
||||
}
|
||||
|
||||
auto place = arena.alloc(sum_keys_size + sizeof(sum_keys_size));
|
||||
|
||||
auto key_start = place;
|
||||
memcpy(key_start, &sum_keys_size, sizeof(sum_keys_size));
|
||||
key_start += sizeof(sum_keys_size);
|
||||
for (size_t j = 0; j < keys_size; ++j)
|
||||
{
|
||||
if (!key_columns[j]->valuesHaveFixedSize()) // String
|
||||
{
|
||||
auto key_size = keys[j].size + 1;
|
||||
memcpy(key_start, &key_size, sizeof(size_t));
|
||||
key_start += sizeof(size_t);
|
||||
memcpy(key_start, keys[j].data, keys[j].size);
|
||||
key_start += keys[j].size;
|
||||
*key_start = '\0';
|
||||
++key_start;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(key_start, keys[j].data, keys[j].size);
|
||||
key_start += keys[j].size;
|
||||
}
|
||||
}
|
||||
|
||||
return KeyRef(place);
|
||||
}
|
||||
|
||||
KeyRef copyKeyFrom(const KeyRef & key)
|
||||
{
|
||||
char * data = arena.alloc(key.fullSize());
|
||||
memcpy(data, key.fullData(), key.fullSize());
|
||||
return KeyRef(data);
|
||||
}
|
||||
|
||||
void freeKey(const KeyRef & key)
|
||||
{
|
||||
if constexpr (std::is_same_v<A, ArenaWithFreeLists>)
|
||||
arena.free(key.fullData(), key.fullSize());
|
||||
}
|
||||
|
||||
void rollback(const KeyRef & key)
|
||||
{
|
||||
if constexpr (std::is_same_v<A, Arena>)
|
||||
arena.rollback(key.fullSize());
|
||||
}
|
||||
|
||||
void writeKey(const KeyRef & key, WriteBuffer & buf)
|
||||
{
|
||||
buf.write(key.fullData(), key.fullSize());
|
||||
}
|
||||
|
||||
void readKey(KeyRef & key, ReadBuffer & buf)
|
||||
{
|
||||
UInt16 sz;
|
||||
readBinary(sz, buf);
|
||||
char * data = nullptr;
|
||||
if constexpr (std::is_same_v<A, SmallObjectPool>)
|
||||
data = arena.alloc();
|
||||
else
|
||||
data = arena.alloc(sz + sizeof(sz));
|
||||
memcpy(data, &sz, sizeof(sz));
|
||||
buf.read(data + sizeof(sz), sz);
|
||||
key = KeyRef(data);
|
||||
}
|
||||
|
||||
void ignoreKey(ReadBuffer & buf) const
|
||||
{
|
||||
UInt16 sz;
|
||||
readBinary(sz, buf);
|
||||
buf.ignore(sz);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return arena.size();
|
||||
}
|
||||
|
||||
private:
|
||||
A arena;
|
||||
};
|
||||
|
||||
using TemporalComplexKeysPool = ComplexKeysPoolImpl<Arena>;
|
||||
using ComplexKeysPool = ComplexKeysPoolImpl<ArenaWithFreeLists>;
|
||||
|
||||
struct KeyDeleter
|
||||
{
|
||||
KeyDeleter(ComplexKeysPool & keys_pool_) : keys_pool(keys_pool_) {}
|
||||
|
||||
void operator()(const KeyRef key) const
|
||||
{
|
||||
keys_pool.freeKey(key);
|
||||
}
|
||||
|
||||
ComplexKeysPool & keys_pool;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Class for operations with cache file and index.
|
||||
Supports GET/SET operations.
|
||||
*/
|
||||
class SSDComplexKeyCachePartition
|
||||
{
|
||||
public:
|
||||
struct Index final
|
||||
{
|
||||
bool inMemory() const;
|
||||
void setInMemory(const bool in_memory);
|
||||
|
||||
bool exists() const;
|
||||
void setNotExists();
|
||||
|
||||
size_t getAddressInBlock() const;
|
||||
void setAddressInBlock(const size_t address_in_block);
|
||||
|
||||
size_t getBlockId() const;
|
||||
void setBlockId(const size_t block_id);
|
||||
|
||||
bool operator< (const Index & rhs) const { return index < rhs.index; }
|
||||
|
||||
/// Stores `is_in_memory` flag, block id, address in uncompressed block
|
||||
uint64_t index = 0;
|
||||
};
|
||||
|
||||
struct Metadata final
|
||||
{
|
||||
using time_point_t = std::chrono::system_clock::time_point;
|
||||
using time_point_rep_t = time_point_t::rep;
|
||||
using time_point_urep_t = std::make_unsigned_t<time_point_rep_t>;
|
||||
|
||||
time_point_t expiresAt() const;
|
||||
void setExpiresAt(const time_point_t & t);
|
||||
|
||||
bool isDefault() const;
|
||||
void setDefault();
|
||||
|
||||
/// Stores both expiration time and `is_default` flag in the most significant bit
|
||||
time_point_urep_t data = 0;
|
||||
};
|
||||
|
||||
using Offset = size_t;
|
||||
using Offsets = std::vector<Offset>;
|
||||
|
||||
|
||||
SSDComplexKeyCachePartition(
|
||||
const AttributeUnderlyingType & key_structure,
|
||||
const std::vector<AttributeUnderlyingType> & attributes_structure,
|
||||
const std::string & dir_path,
|
||||
const size_t file_id,
|
||||
const size_t max_size,
|
||||
const size_t block_size,
|
||||
const size_t read_buffer_size,
|
||||
const size_t write_buffer_size,
|
||||
const size_t max_stored_keys);
|
||||
|
||||
~SSDComplexKeyCachePartition();
|
||||
|
||||
template <typename T>
|
||||
using ResultArrayType = std::conditional_t<IsDecimalNumber<T>, DecimalPaddedPODArray<T>, PaddedPODArray<T>>;
|
||||
|
||||
template <typename Out, typename GetDefault>
|
||||
void getValue(const size_t attribute_index,
|
||||
const Columns & key_columns, const DataTypes & key_types,
|
||||
ResultArrayType<Out> & out, std::vector<bool> & found, GetDefault & get_default,
|
||||
std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void getString(const size_t attribute_index,
|
||||
const Columns & key_columns, const DataTypes & key_types,
|
||||
StringRefs & refs, ArenaWithFreeLists & arena, std::vector<bool> & found,
|
||||
std::vector<size_t> & default_ids, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void has(const Columns & key_columns, const DataTypes & key_types,
|
||||
ResultArrayType<UInt8> & out, std::vector<bool> & found,
|
||||
std::chrono::system_clock::time_point now) const;
|
||||
|
||||
struct Attribute
|
||||
{
|
||||
template <typename T>
|
||||
using Container = std::vector<T>;
|
||||
|
||||
AttributeUnderlyingType type;
|
||||
std::variant<
|
||||
Container<UInt8>,
|
||||
Container<UInt16>,
|
||||
Container<UInt32>,
|
||||
Container<UInt64>,
|
||||
Container<UInt128>,
|
||||
Container<Int8>,
|
||||
Container<Int16>,
|
||||
Container<Int32>,
|
||||
Container<Int64>,
|
||||
Container<Decimal32>,
|
||||
Container<Decimal64>,
|
||||
Container<Decimal128>,
|
||||
Container<Float32>,
|
||||
Container<Float64>,
|
||||
Container<String>> values;
|
||||
};
|
||||
using Attributes = std::vector<Attribute>;
|
||||
|
||||
size_t appendBlock(
|
||||
const Columns & key_columns,
|
||||
const DataTypes & key_types,
|
||||
const Attributes & new_attributes,
|
||||
const PaddedPODArray<Metadata> & metadata,
|
||||
const size_t begin);
|
||||
|
||||
size_t appendDefaults(
|
||||
const KeyRefs & keys,
|
||||
const PaddedPODArray<Metadata> & metadata,
|
||||
const size_t begin);
|
||||
|
||||
void clearOldestBlocks();
|
||||
|
||||
void flush();
|
||||
|
||||
void remove();
|
||||
|
||||
size_t getId() const;
|
||||
|
||||
double getLoadFactor() const;
|
||||
|
||||
size_t getElementCount() const;
|
||||
|
||||
size_t getBytesAllocated() const;
|
||||
|
||||
private:
|
||||
size_t append(
|
||||
const KeyRefs & keys,
|
||||
const Attributes & new_attributes,
|
||||
const PaddedPODArray<Metadata> & metadata,
|
||||
const size_t begin);
|
||||
|
||||
template <typename SetFunc>
|
||||
void getImpl(const Columns & key_columns, const DataTypes & key_types,
|
||||
SetFunc & set, std::vector<bool> & found) const;
|
||||
|
||||
template <typename SetFunc>
|
||||
void getValueFromMemory(const PaddedPODArray<Index> & indices, SetFunc & set) const;
|
||||
|
||||
template <typename SetFunc>
|
||||
void getValueFromStorage(const PaddedPODArray<Index> & indices, SetFunc & set) const;
|
||||
|
||||
void ignoreFromBufferToAttributeIndex(const size_t attribute_index, ReadBuffer & buf) const;
|
||||
|
||||
const size_t file_id;
|
||||
const size_t max_size;
|
||||
const size_t block_size;
|
||||
const size_t read_buffer_size;
|
||||
const size_t write_buffer_size;
|
||||
const size_t max_stored_keys;
|
||||
const std::string path;
|
||||
|
||||
mutable std::shared_mutex rw_lock;
|
||||
|
||||
int fd = -1;
|
||||
|
||||
ComplexKeysPool keys_pool;
|
||||
mutable BucketCacheIndex<KeyRef, Index, std::hash<KeyRef>, KeyDeleter> key_to_index;
|
||||
|
||||
std::optional<TemporalComplexKeysPool> keys_buffer_pool;
|
||||
KeyRefs keys_buffer;
|
||||
|
||||
const std::vector<AttributeUnderlyingType> attributes_structure;
|
||||
|
||||
std::optional<Memory<>> memory;
|
||||
std::optional<WriteBuffer> write_buffer;
|
||||
uint32_t keys_in_block = 0;
|
||||
|
||||
size_t current_memory_block_id = 0;
|
||||
size_t current_file_block_id = 0;
|
||||
};
|
||||
|
||||
using SSDComplexKeyCachePartitionPtr = std::shared_ptr<SSDComplexKeyCachePartition>;
|
||||
|
||||
|
||||
/*
|
||||
Class for managing SSDCachePartition and getting data from source.
|
||||
*/
|
||||
class SSDComplexKeyCacheStorage
|
||||
{
|
||||
public:
|
||||
using AttributeTypes = std::vector<AttributeUnderlyingType>;
|
||||
|
||||
SSDComplexKeyCacheStorage(
|
||||
const AttributeTypes & attributes_structure,
|
||||
const std::string & path,
|
||||
const size_t max_partitions_count,
|
||||
const size_t file_size,
|
||||
const size_t block_size,
|
||||
const size_t read_buffer_size,
|
||||
const size_t write_buffer_size,
|
||||
const size_t max_stored_keys);
|
||||
|
||||
~SSDComplexKeyCacheStorage();
|
||||
|
||||
template <typename T>
|
||||
using ResultArrayType = SSDComplexKeyCachePartition::ResultArrayType<T>;
|
||||
|
||||
template <typename Out, typename GetDefault>
|
||||
void getValue(const size_t attribute_index, const Columns & key_columns, const DataTypes & key_types,
|
||||
ResultArrayType<Out> & out, std::unordered_map<KeyRef, std::vector<size_t>> & not_found,
|
||||
TemporalComplexKeysPool & not_found_pool,
|
||||
GetDefault & get_default, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void getString(const size_t attribute_index, const Columns & key_columns, const DataTypes & key_types,
|
||||
StringRefs & refs, ArenaWithFreeLists & arena, std::unordered_map<KeyRef, std::vector<size_t>> & not_found,
|
||||
TemporalComplexKeysPool & not_found_pool,
|
||||
std::vector<size_t> & default_ids, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
void has(const Columns & key_columns, const DataTypes & key_types, ResultArrayType<UInt8> & out,
|
||||
std::unordered_map<KeyRef, std::vector<size_t>> & not_found,
|
||||
TemporalComplexKeysPool & not_found_pool, std::chrono::system_clock::time_point now) const;
|
||||
|
||||
template <typename PresentIdHandler, typename AbsentIdHandler>
|
||||
void update(DictionarySourcePtr & source_ptr,
|
||||
const Columns & key_columns, const DataTypes & key_types,
|
||||
const KeyRefs & required_keys, const std::vector<size_t> & required_rows,
|
||||
TemporalComplexKeysPool & tmp_keys_pool,
|
||||
PresentIdHandler && on_updated, AbsentIdHandler && on_key_not_found,
|
||||
const DictionaryLifetime lifetime);
|
||||
|
||||
std::exception_ptr getLastException() const { return last_update_exception; }
|
||||
|
||||
const std::string & getPath() const { return path; }
|
||||
|
||||
size_t getQueryCount() const { return query_count.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t getHitCount() const { return hit_count.load(std::memory_order_acquire); }
|
||||
|
||||
size_t getElementCount() const;
|
||||
|
||||
double getLoadFactor() const;
|
||||
|
||||
private:
|
||||
void collectGarbage();
|
||||
|
||||
const AttributeTypes attributes_structure;
|
||||
|
||||
const std::string path;
|
||||
const size_t max_partitions_count;
|
||||
const size_t file_size;
|
||||
const size_t block_size;
|
||||
const size_t read_buffer_size;
|
||||
const size_t write_buffer_size;
|
||||
const size_t max_stored_keys;
|
||||
|
||||
mutable std::shared_mutex rw_lock;
|
||||
std::list<SSDComplexKeyCachePartitionPtr> partitions;
|
||||
std::list<SSDComplexKeyCachePartitionPtr> partition_delete_queue;
|
||||
|
||||
Poco::Logger * const log;
|
||||
|
||||
mutable pcg64 rnd_engine;
|
||||
|
||||
mutable std::exception_ptr last_update_exception;
|
||||
mutable size_t update_error_count = 0;
|
||||
mutable std::chrono::system_clock::time_point backoff_end_time;
|
||||
|
||||
mutable std::atomic<size_t> hit_count{0};
|
||||
mutable std::atomic<size_t> query_count{0};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Dictionary interface
|
||||
*/
|
||||
class SSDComplexKeyCacheDictionary final : public IDictionaryBase
|
||||
{
|
||||
public:
|
||||
SSDComplexKeyCacheDictionary(
|
||||
const std::string & name_,
|
||||
const DictionaryStructure & dict_struct_,
|
||||
DictionarySourcePtr source_ptr_,
|
||||
const DictionaryLifetime dict_lifetime_,
|
||||
const std::string & path,
|
||||
const size_t max_partitions_count_,
|
||||
const size_t file_size_,
|
||||
const size_t block_size_,
|
||||
const size_t read_buffer_size_,
|
||||
const size_t write_buffer_size_,
|
||||
const size_t max_stored_keys_);
|
||||
|
||||
const std::string & getDatabase() const override { return name; }
|
||||
const std::string & getName() const override { return name; }
|
||||
const std::string & getFullName() const override { return getName(); }
|
||||
|
||||
std::string getKeyDescription() const { return dict_struct.getKeyDescription(); }
|
||||
|
||||
std::string getTypeName() const override { return "SSDComplexKeyCache"; }
|
||||
|
||||
size_t getBytesAllocated() const override { return 0; } // TODO: ?
|
||||
|
||||
size_t getQueryCount() const override { return storage.getQueryCount(); }
|
||||
|
||||
double getHitRate() const override
|
||||
{
|
||||
return static_cast<double>(storage.getHitCount()) / storage.getQueryCount();
|
||||
}
|
||||
|
||||
size_t getElementCount() const override { return storage.getElementCount(); }
|
||||
|
||||
double getLoadFactor() const override { return storage.getLoadFactor(); }
|
||||
|
||||
bool supportUpdates() const override { return false; }
|
||||
|
||||
std::shared_ptr<const IExternalLoadable> clone() const override
|
||||
{
|
||||
return std::make_shared<SSDComplexKeyCacheDictionary>(name, dict_struct, source_ptr->clone(), dict_lifetime, path,
|
||||
max_partitions_count, file_size, block_size, read_buffer_size, write_buffer_size, max_stored_keys);
|
||||
}
|
||||
|
||||
const IDictionarySource * getSource() const override { return source_ptr.get(); }
|
||||
|
||||
const DictionaryLifetime & getLifetime() const override { return dict_lifetime; }
|
||||
|
||||
const DictionaryStructure & getStructure() const override { return dict_struct; }
|
||||
|
||||
bool isInjective(const std::string & attribute_name) const override
|
||||
{
|
||||
return dict_struct.attributes[getAttributeIndex(attribute_name)].injective;
|
||||
}
|
||||
|
||||
std::exception_ptr getLastException() const override { return storage.getLastException(); }
|
||||
|
||||
template <typename T>
|
||||
using ResultArrayType = SSDComplexKeyCacheStorage::ResultArrayType<T>;
|
||||
|
||||
#define DECLARE(TYPE) \
|
||||
void get##TYPE( \
|
||||
const std::string & attribute_name, \
|
||||
const Columns & key_columns, \
|
||||
const DataTypes & key_types, \
|
||||
ResultArrayType<TYPE> & out) const;
|
||||
DECLARE(UInt8)
|
||||
DECLARE(UInt16)
|
||||
DECLARE(UInt32)
|
||||
DECLARE(UInt64)
|
||||
DECLARE(UInt128)
|
||||
DECLARE(Int8)
|
||||
DECLARE(Int16)
|
||||
DECLARE(Int32)
|
||||
DECLARE(Int64)
|
||||
DECLARE(Float32)
|
||||
DECLARE(Float64)
|
||||
DECLARE(Decimal32)
|
||||
DECLARE(Decimal64)
|
||||
DECLARE(Decimal128)
|
||||
#undef DECLARE
|
||||
|
||||
void getString(const std::string & attribute_name, const Columns & key_columns,
|
||||
const DataTypes & key_types, ColumnString * out) const;
|
||||
|
||||
#define DECLARE(TYPE) \
|
||||
void get##TYPE( \
|
||||
const std::string & attribute_name, \
|
||||
const Columns & key_columns, \
|
||||
const DataTypes & key_types, \
|
||||
const PaddedPODArray<TYPE> & def, \
|
||||
ResultArrayType<TYPE> & out) const;
|
||||
DECLARE(UInt8)
|
||||
DECLARE(UInt16)
|
||||
DECLARE(UInt32)
|
||||
DECLARE(UInt64)
|
||||
DECLARE(UInt128)
|
||||
DECLARE(Int8)
|
||||
DECLARE(Int16)
|
||||
DECLARE(Int32)
|
||||
DECLARE(Int64)
|
||||
DECLARE(Float32)
|
||||
DECLARE(Float64)
|
||||
DECLARE(Decimal32)
|
||||
DECLARE(Decimal64)
|
||||
DECLARE(Decimal128)
|
||||
#undef DECLARE
|
||||
|
||||
void getString(const std::string & attribute_name, const Columns & key_columns,
|
||||
const DataTypes & key_types, const ColumnString * const def, ColumnString * const out) const;
|
||||
|
||||
#define DECLARE(TYPE) \
|
||||
void get##TYPE( \
|
||||
const std::string & attribute_name, \
|
||||
const Columns & key_columns, \
|
||||
const DataTypes & key_types, \
|
||||
const TYPE def, \
|
||||
ResultArrayType<TYPE> & out) const;
|
||||
DECLARE(UInt8)
|
||||
DECLARE(UInt16)
|
||||
DECLARE(UInt32)
|
||||
DECLARE(UInt64)
|
||||
DECLARE(UInt128)
|
||||
DECLARE(Int8)
|
||||
DECLARE(Int16)
|
||||
DECLARE(Int32)
|
||||
DECLARE(Int64)
|
||||
DECLARE(Float32)
|
||||
DECLARE(Float64)
|
||||
DECLARE(Decimal32)
|
||||
DECLARE(Decimal64)
|
||||
DECLARE(Decimal128)
|
||||
#undef DECLARE
|
||||
|
||||
void getString(const std::string & attribute_name, const Columns & key_columns,
|
||||
const DataTypes & key_types, const String & def, ColumnString * const out) const;
|
||||
|
||||
void has(const Columns & key_columns, const DataTypes & key_types, PaddedPODArray<UInt8> & out) const;
|
||||
|
||||
BlockInputStreamPtr getBlockInputStream(const Names & column_names, size_t max_block_size) const override;
|
||||
|
||||
private:
|
||||
size_t getAttributeIndex(const std::string & attr_name) const;
|
||||
|
||||
template <typename T>
|
||||
AttributeValueVariant createAttributeNullValueWithTypeImpl(const Field & null_value);
|
||||
AttributeValueVariant createAttributeNullValueWithType(const AttributeUnderlyingType type, const Field & null_value);
|
||||
void createAttributes();
|
||||
|
||||
template <typename AttributeType, typename OutputType, typename DefaultGetter>
|
||||
void getItemsNumberImpl(
|
||||
const size_t attribute_index,
|
||||
const Columns & key_columns, const DataTypes & key_types,
|
||||
ResultArrayType<OutputType> & out, DefaultGetter && get_default) const;
|
||||
|
||||
template <typename DefaultGetter>
|
||||
void getItemsStringImpl(
|
||||
const size_t attribute_index,
|
||||
const Columns & key_columns, const DataTypes & key_types,
|
||||
ColumnString * out, DefaultGetter && get_default) const;
|
||||
|
||||
const std::string name;
|
||||
const DictionaryStructure dict_struct;
|
||||
mutable DictionarySourcePtr source_ptr;
|
||||
const DictionaryLifetime dict_lifetime;
|
||||
|
||||
const std::string path;
|
||||
const size_t max_partitions_count;
|
||||
const size_t file_size;
|
||||
const size_t block_size;
|
||||
const size_t read_buffer_size;
|
||||
const size_t write_buffer_size;
|
||||
const size_t max_stored_keys;
|
||||
|
||||
std::map<std::string, size_t> attribute_index_by_name;
|
||||
std::vector<AttributeValueVariant> null_values;
|
||||
mutable SSDComplexKeyCacheStorage storage;
|
||||
Poco::Logger * const log;
|
||||
|
||||
mutable size_t bytes_allocated = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -20,6 +20,7 @@ namespace DB
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int BAD_ARGUMENTS;
|
||||
extern const int INCORRECT_DICTIONARY_DEFINITION;
|
||||
}
|
||||
|
||||
@ -97,13 +98,22 @@ void buildLayoutConfiguration(
|
||||
root->appendChild(layout_element);
|
||||
AutoPtr<Element> layout_type_element(doc->createElement(layout->layout_type));
|
||||
layout_element->appendChild(layout_type_element);
|
||||
if (layout->parameter.has_value())
|
||||
for (const auto & param : layout->parameters)
|
||||
{
|
||||
const auto & param = layout->parameter;
|
||||
AutoPtr<Element> layout_type_parameter_element(doc->createElement(param->first));
|
||||
const ASTLiteral & literal = param->second->as<const ASTLiteral &>();
|
||||
AutoPtr<Text> value(doc->createTextNode(toString(literal.value.get<UInt64>())));
|
||||
layout_type_parameter_element->appendChild(value);
|
||||
AutoPtr<Element> layout_type_parameter_element(doc->createElement(param.first));
|
||||
const ASTLiteral & literal = param.second->as<const ASTLiteral &>();
|
||||
Field::dispatch([&](auto & value)
|
||||
{
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, UInt64> || std::is_same_v<std::decay_t<decltype(value)>, String>)
|
||||
{
|
||||
AutoPtr<Text> value_to_append(doc->createTextNode(toString(value)));
|
||||
layout_type_parameter_element->appendChild(value_to_append);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw DB::Exception{"Wrong type of layout argument.", ErrorCodes::BAD_ARGUMENTS};
|
||||
}
|
||||
}, literal.value);
|
||||
layout_type_element->appendChild(layout_type_parameter_element);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,10 @@ void registerDictionaries()
|
||||
registerDictionaryFlat(factory);
|
||||
registerDictionaryHashed(factory);
|
||||
registerDictionaryCache(factory);
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
registerDictionarySSDCache(factory);
|
||||
registerDictionarySSDComplexKeyCache(factory);
|
||||
#endif
|
||||
registerDictionaryPolygon(factory);
|
||||
registerDictionaryDirect(factory);
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ void registerDictionaryTrie(DictionaryFactory & factory);
|
||||
void registerDictionaryFlat(DictionaryFactory & factory);
|
||||
void registerDictionaryHashed(DictionaryFactory & factory);
|
||||
void registerDictionaryCache(DictionaryFactory & factory);
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
void registerDictionarySSDCache(DictionaryFactory & factory);
|
||||
void registerDictionarySSDComplexKeyCache(DictionaryFactory & factory);
|
||||
#endif
|
||||
void registerDictionaryPolygon(DictionaryFactory & factory);
|
||||
void registerDictionaryDirect(DictionaryFactory & factory);
|
||||
|
||||
|
@ -53,7 +53,6 @@ SRCS(
|
||||
HTTPDictionarySource.cpp
|
||||
LibraryDictionarySource.cpp
|
||||
LibraryDictionarySourceExternal.cpp
|
||||
MongoDBBlockInputStream.cpp
|
||||
MongoDBDictionarySource.cpp
|
||||
MySQLDictionarySource.cpp
|
||||
PolygonDictionary.cpp
|
||||
@ -62,6 +61,8 @@ SRCS(
|
||||
RedisBlockInputStream.cpp
|
||||
RedisDictionarySource.cpp
|
||||
registerDictionaries.cpp
|
||||
SSDCacheDictionary.cpp
|
||||
SSDComplexKeyCacheDictionary.cpp
|
||||
writeParenthesisedString.cpp
|
||||
XDBCDictionarySource.cpp
|
||||
|
||||
|
@ -7,7 +7,7 @@ VolumePtr createVolumeFromReservation(const ReservationPtr & reservation, Volume
|
||||
{
|
||||
if (other_volume->getType() == VolumeType::JBOD || other_volume->getType() == VolumeType::SINGLE_DISK)
|
||||
{
|
||||
/// Since reservation on JBOD chosies one of disks and makes reservation there, volume
|
||||
/// Since reservation on JBOD choices one of disks and makes reservation there, volume
|
||||
/// for such type of reservation will be with one disk.
|
||||
return std::make_shared<SingleDiskVolume>(other_volume->getName(), reservation->getDisk());
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ static FormatSettings getInputFormatSetting(const Settings & settings, const Con
|
||||
context.getRemoteHostFilter().checkURL(avro_schema_registry_url);
|
||||
}
|
||||
format_settings.avro.schema_registry_url = settings.format_avro_schema_registry_url.toString();
|
||||
format_settings.avro.allow_missing_fields = settings.input_format_avro_allow_missing_fields;
|
||||
|
||||
return format_settings;
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ struct FormatSettings
|
||||
String schema_registry_url;
|
||||
String output_codec;
|
||||
UInt64 output_sync_interval = 16 * 1024;
|
||||
bool allow_missing_fields = false;
|
||||
};
|
||||
|
||||
Avro avro;
|
||||
|
@ -44,6 +44,13 @@ inline bool divisionLeadsToFPE(A a, B b)
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename A, typename B>
|
||||
inline auto checkedDivision(A a, B b)
|
||||
{
|
||||
throwIfDivisionLeadsToFPE(a, b);
|
||||
return a / b;
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@ -56,14 +63,13 @@ struct DivideIntegralImpl
|
||||
template <typename Result = ResultType>
|
||||
static inline Result apply(A a, B b)
|
||||
{
|
||||
throwIfDivisionLeadsToFPE(a, b);
|
||||
|
||||
/// Otherwise overflow may occur due to integer promotion. Example: int8_t(-1) / uint64_t(2).
|
||||
/// NOTE: overflow is still possible when dividing large signed number to large unsigned number or vice-versa. But it's less harmful.
|
||||
if constexpr (is_integral_v<A> && is_integral_v<B> && (is_signed_v<A> || is_signed_v<B>))
|
||||
return std::make_signed_t<A>(a) / std::make_signed_t<B>(b);
|
||||
return checkedDivision(std::make_signed_t<A>(a),
|
||||
sizeof(A) > sizeof(B) ? std::make_signed_t<A>(b) : std::make_signed_t<B>(b));
|
||||
else
|
||||
return a / b;
|
||||
return checkedDivision(a, b);
|
||||
}
|
||||
|
||||
#if USE_EMBEDDED_COMPILER
|
||||
|
@ -324,13 +324,16 @@ struct DateTimeAddIntervalImpl
|
||||
const IColumn & delta_column = *block.getByPosition(arguments[1]).column;
|
||||
|
||||
if (const auto * delta_const_column = typeid_cast<const ColumnConst *>(&delta_column))
|
||||
op.vectorConstant(sources->getData(), col_to->getData(), delta_const_column->getField().get<Int64>(), time_zone);
|
||||
op.vectorConstant(sources->getData(), col_to->getData(), delta_const_column->getInt(0), time_zone);
|
||||
else
|
||||
op.vectorVector(sources->getData(), col_to->getData(), delta_column, time_zone);
|
||||
}
|
||||
else if (const auto * sources_const = checkAndGetColumnConst<FromColumnType>(source_col.get()))
|
||||
{
|
||||
op.constantVector(sources_const->template getValue<FromValueType>(), col_to->getData(), *block.getByPosition(arguments[1]).column, time_zone);
|
||||
op.constantVector(
|
||||
sources_const->template getValue<FromValueType>(),
|
||||
col_to->getData(),
|
||||
*block.getByPosition(arguments[1]).column, time_zone);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -390,6 +393,7 @@ public:
|
||||
{
|
||||
if (!WhichDataType(arguments[0].type).isDateTime()
|
||||
|| !WhichDataType(arguments[2].type).isString())
|
||||
{
|
||||
throw Exception(
|
||||
"Function " + getName() + " supports 2 or 3 arguments. The 1st argument "
|
||||
"must be of type Date or DateTime. The 2nd argument must be number. "
|
||||
@ -397,6 +401,7 @@ public:
|
||||
"a constant string with timezone name. The timezone argument is allowed "
|
||||
"only when the 1st argument has the type DateTime",
|
||||
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
||||
}
|
||||
}
|
||||
|
||||
switch (arguments[0].type->getTypeId())
|
||||
@ -416,7 +421,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Helper templates to deduce return type based on argument type, since some overloads may promote or denote types, e.g. addSeconds(Date, 1) => DateTime
|
||||
/// Helper templates to deduce return type based on argument type, since some overloads may promote or denote types,
|
||||
/// e.g. addSeconds(Date, 1) => DateTime
|
||||
template <typename FieldType>
|
||||
using TransformExecuteReturnType = decltype(std::declval<Transform>().execute(FieldType(), 0, std::declval<DateLUTImpl>()));
|
||||
|
||||
@ -463,15 +469,18 @@ public:
|
||||
|
||||
if (which.isDate())
|
||||
{
|
||||
DateTimeAddIntervalImpl<DataTypeDate, TransformResultDataType<DataTypeDate>, Transform>::execute(Transform{}, block, arguments, result);
|
||||
DateTimeAddIntervalImpl<DataTypeDate, TransformResultDataType<DataTypeDate>, Transform>::execute(
|
||||
Transform{}, block, arguments, result);
|
||||
}
|
||||
else if (which.isDateTime())
|
||||
{
|
||||
DateTimeAddIntervalImpl<DataTypeDateTime, TransformResultDataType<DataTypeDateTime>, Transform>::execute(Transform{}, block, arguments, result);
|
||||
DateTimeAddIntervalImpl<DataTypeDateTime, TransformResultDataType<DataTypeDateTime>, Transform>::execute(
|
||||
Transform{}, block, arguments, result);
|
||||
}
|
||||
else if (const auto * datetime64_type = assert_cast<const DataTypeDateTime64 *>(from_type))
|
||||
{
|
||||
DateTimeAddIntervalImpl<DataTypeDateTime64, TransformResultDataType<DataTypeDateTime64>, Transform>::execute(Transform{datetime64_type->getScale()}, block, arguments, result);
|
||||
DateTimeAddIntervalImpl<DataTypeDateTime64, TransformResultDataType<DataTypeDateTime64>, Transform>::execute(
|
||||
Transform{datetime64_type->getScale()}, block, arguments, result);
|
||||
}
|
||||
else
|
||||
throw Exception("Illegal type " + block.getByPosition(arguments[0]).type->getName() + " of first argument of function " + getName(),
|
||||
|
@ -1084,7 +1084,7 @@ public:
|
||||
bool both_represented_by_number = arguments[0]->isValueRepresentedByNumber() && arguments[1]->isValueRepresentedByNumber();
|
||||
bool has_date = left.isDate() || right.isDate();
|
||||
|
||||
if (!((both_represented_by_number && !has_date) /// Do not allow compare date and number.
|
||||
if (!((both_represented_by_number && !has_date) /// Do not allow to compare date and number.
|
||||
|| (left.isStringOrFixedString() || right.isStringOrFixedString()) /// Everything can be compared with string by conversion.
|
||||
/// You can compare the date, datetime, or datatime64 and an enumeration with a constant string.
|
||||
|| (left.isDateOrDateTime() && right.isDateOrDateTime() && left.idx == right.idx) /// only date vs date, or datetime vs datetime
|
||||
|
@ -931,7 +931,7 @@ public:
|
||||
// toUnixTimestamp(value[, timezone : String])
|
||||
|| std::is_same_v<Name, NameToUnixTimestamp>
|
||||
// toDate(value[, timezone : String])
|
||||
|| std::is_same_v<ToDataType, DataTypeDate> // TODO: shall we allow timestamp argument for toDate? DateTime knows nothing about timezones and this arument is ignored below.
|
||||
|| std::is_same_v<ToDataType, DataTypeDate> // TODO: shall we allow timestamp argument for toDate? DateTime knows nothing about timezones and this argument is ignored below.
|
||||
// toDateTime(value[, timezone: String])
|
||||
|| std::is_same_v<ToDataType, DataTypeDateTime>
|
||||
// toDateTime64(value, scale : Integer[, timezone: String])
|
||||
|
@ -29,6 +29,10 @@
|
||||
#include <Dictionaries/FlatDictionary.h>
|
||||
#include <Dictionaries/HashedDictionary.h>
|
||||
#include <Dictionaries/CacheDictionary.h>
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#include <Dictionaries/SSDCacheDictionary.h>
|
||||
#include <Dictionaries/SSDComplexKeyCacheDictionary.h>
|
||||
#endif
|
||||
#include <Dictionaries/ComplexKeyHashedDictionary.h>
|
||||
#include <Dictionaries/ComplexKeyCacheDictionary.h>
|
||||
#include <Dictionaries/ComplexKeyDirectDictionary.h>
|
||||
@ -171,16 +175,22 @@ private:
|
||||
auto dict = helper.getDictionary(block.getByPosition(arguments[0]));
|
||||
|
||||
if (!executeDispatchSimple<FlatDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchSimple<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchSimple<HashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchSimple<CacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatchSimple<SSDCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
!executeDispatchComplex<ComplexKeyHashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyDirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatchComplex<SSDComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
!executeDispatchComplex<TrieDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
!executeDispatchComplex<SimplePolygonDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchSimple<DirectDictionary>(block, arguments, result, dict))
|
||||
!executeDispatchComplex<SimplePolygonDictionary>(block, arguments, result, dict))
|
||||
throw Exception{"Unsupported dictionary type " + dict->getTypeName(), ErrorCodes::UNKNOWN_TYPE};
|
||||
}
|
||||
|
||||
@ -321,12 +331,18 @@ private:
|
||||
auto dict = helper.getDictionary(block.getByPosition(arguments[0]));
|
||||
|
||||
if (!executeDispatch<FlatDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<HashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<CacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatch<SSDCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
!executeDispatchComplex<ComplexKeyHashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyDirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatchComplex<SSDComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
!executeDispatchComplex<TrieDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
@ -499,12 +515,18 @@ private:
|
||||
auto dict = helper.getDictionary(block.getByPosition(arguments[0]));
|
||||
|
||||
if (!executeDispatch<FlatDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<HashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<CacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatch<SSDCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
!executeDispatchComplex<ComplexKeyHashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyDirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatchComplex<SSDComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
!executeDispatchComplex<TrieDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
@ -833,12 +855,18 @@ private:
|
||||
auto dict = helper.getDictionary(block.getByPosition(arguments[0]));
|
||||
|
||||
if (!executeDispatch<FlatDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<HashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<CacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatch<SSDCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
!executeDispatchComplex<ComplexKeyHashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyDirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatchComplex<SSDComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
!executeDispatchComplex<TrieDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
@ -1088,12 +1116,18 @@ private:
|
||||
auto dict = helper.getDictionary(block.getByPosition(arguments[0]));
|
||||
|
||||
if (!executeDispatch<FlatDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<HashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<DirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatch<CacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatch<SSDCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
!executeDispatchComplex<ComplexKeyHashedDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyDirectDictionary>(block, arguments, result, dict) &&
|
||||
!executeDispatchComplex<ComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
!executeDispatchComplex<SSDComplexKeyCacheDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
#if !defined(ARCADIA_BUILD)
|
||||
!executeDispatchComplex<TrieDictionary>(block, arguments, result, dict) &&
|
||||
#endif
|
||||
|
@ -349,7 +349,7 @@ struct MurmurHash3Impl128
|
||||
|
||||
/// http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/478a4add975b/src/share/classes/java/lang/String.java#l1452
|
||||
/// Care should be taken to do all calculation in unsigned integers (to avoid undefined behaviour on overflow)
|
||||
/// but obtain the same result as it is done in singed integers with two's complement arithmetic.
|
||||
/// but obtain the same result as it is done in signed integers with two's complement arithmetic.
|
||||
struct JavaHashImpl
|
||||
{
|
||||
static constexpr auto name = "javaHash";
|
||||
|
@ -221,9 +221,9 @@ private:
|
||||
{
|
||||
const auto & column_const = assert_cast<const ColumnConst &>(*column.column);
|
||||
if (isString(column.type))
|
||||
moves.emplace_back(MoveType::ConstKey, column_const.getField().get<String>());
|
||||
moves.emplace_back(MoveType::ConstKey, column_const.getValue<String>());
|
||||
else
|
||||
moves.emplace_back(MoveType::ConstIndex, column_const.getField().get<Int64>());
|
||||
moves.emplace_back(MoveType::ConstIndex, column_const.getInt(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -138,7 +138,7 @@ struct NgramDistanceImpl
|
||||
|
||||
/// This is not a really true case insensitive utf8. We zero the 5-th bit of every byte.
|
||||
/// And first bit of first byte if there are two bytes.
|
||||
/// For ASCII it works https://catonmat.net/ascii-case-conversion-trick. For most cyrrilic letters also does.
|
||||
/// For ASCII it works https://catonmat.net/ascii-case-conversion-trick. For most cyrillic letters also does.
|
||||
/// For others, we don't care now. Lowering UTF is not a cheap operation.
|
||||
if constexpr (case_insensitive)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <cassert>
|
||||
#include <Functions/GeoHash.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
@ -260,15 +261,21 @@ void geohashDecode(const char * encoded_string, size_t encoded_len, Float64 * lo
|
||||
*latitude = decodeCoordinate(lat_encoded, LAT_MIN, LAT_MAX, singleCoordBitsPrecision(precision, LATITUDE));
|
||||
}
|
||||
|
||||
GeohashesInBoxPreparedArgs geohashesInBoxPrepare(Float64 longitude_min,
|
||||
Float64 latitude_min,
|
||||
Float64 longitude_max,
|
||||
Float64 latitude_max,
|
||||
uint8_t precision)
|
||||
GeohashesInBoxPreparedArgs geohashesInBoxPrepare(
|
||||
Float64 longitude_min,
|
||||
Float64 latitude_min,
|
||||
Float64 longitude_max,
|
||||
Float64 latitude_max,
|
||||
uint8_t precision)
|
||||
{
|
||||
precision = geohashPrecision(precision);
|
||||
|
||||
if (longitude_max < longitude_min || latitude_max < latitude_min)
|
||||
if (longitude_max < longitude_min
|
||||
|| latitude_max < latitude_min
|
||||
|| std::isnan(longitude_min)
|
||||
|| std::isnan(longitude_max)
|
||||
|| std::isnan(latitude_min)
|
||||
|| std::isnan(latitude_max))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
@ -281,51 +288,50 @@ GeohashesInBoxPreparedArgs geohashesInBoxPrepare(Float64 longitude_min,
|
||||
const auto lon_step = getSpan(precision, LONGITUDE);
|
||||
const auto lat_step = getSpan(precision, LATITUDE);
|
||||
|
||||
// align max to the right(or up) border of geohash grid cell to ensure that cell is in result.
|
||||
/// Align max to the right (or up) border of geohash grid cell to ensure that cell is in result.
|
||||
Float64 lon_min = floor(longitude_min / lon_step) * lon_step;
|
||||
Float64 lat_min = floor(latitude_min / lat_step) * lat_step;
|
||||
Float64 lon_max = ceil(longitude_max / lon_step) * lon_step;
|
||||
Float64 lat_max = ceil(latitude_max / lat_step) * lat_step;
|
||||
|
||||
const auto lon_span = lon_max - lon_min;
|
||||
const auto lat_span = lat_max - lat_min;
|
||||
// in case of a very small (or zero) span, produce at least 1 item.
|
||||
const auto items_count = std::max(size_t{1}, static_cast<size_t>(ceil(lon_span/lon_step * lat_span/lat_step)));
|
||||
UInt32 lon_items = (lon_max - lon_min) / lon_step;
|
||||
UInt32 lat_items = (lat_max - lat_min) / lat_step;
|
||||
|
||||
return GeohashesInBoxPreparedArgs{
|
||||
items_count,
|
||||
precision,
|
||||
lon_min,
|
||||
lat_min,
|
||||
lon_max,
|
||||
lat_max,
|
||||
lon_step,
|
||||
lat_step
|
||||
return GeohashesInBoxPreparedArgs
|
||||
{
|
||||
std::max<UInt64>(1, UInt64(lon_items) * lat_items),
|
||||
lon_items,
|
||||
lat_items,
|
||||
lon_min,
|
||||
lat_min,
|
||||
lon_step,
|
||||
lat_step,
|
||||
precision
|
||||
};
|
||||
}
|
||||
|
||||
UInt64 geohashesInBox(const GeohashesInBoxPreparedArgs & args, char * out)
|
||||
{
|
||||
if (args.items_count == 0
|
||||
|| args.precision == 0
|
||||
|| args.precision > MAX_PRECISION
|
||||
|| args.latitude_min > args.latitude_max
|
||||
|| args.longitude_min > args.longitude_max
|
||||
|| args.longitude_step <= 0
|
||||
|| args.latitude_step <= 0)
|
||||
if (args.precision == 0
|
||||
|| args.precision > MAX_PRECISION
|
||||
|| args.longitude_step <= 0
|
||||
|| args.latitude_step <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
UInt64 items = 0;
|
||||
for (auto lon = args.longitude_min; lon < args.longitude_max; lon += args.longitude_step) // NOLINT
|
||||
for (size_t i = 0; i < args.longitude_items; ++i)
|
||||
{
|
||||
for (auto lat = args.latitude_min; lat < args.latitude_max; lat += args.latitude_step) // NOLINT
|
||||
for (size_t j = 0; j < args.latitude_items; ++j)
|
||||
{
|
||||
assert(items <= args.items_count);
|
||||
size_t length = geohashEncodeImpl(
|
||||
args.longitude_min + args.longitude_step * i,
|
||||
args.latitude_min + args.latitude_step * j,
|
||||
args.precision,
|
||||
out);
|
||||
|
||||
size_t l = geohashEncodeImpl(lon, lat, args.precision, out);
|
||||
out += l;
|
||||
out += length;
|
||||
*out = '\0';
|
||||
++out;
|
||||
|
||||
@ -335,8 +341,8 @@ UInt64 geohashesInBox(const GeohashesInBoxPreparedArgs & args, char * out)
|
||||
|
||||
if (items == 0)
|
||||
{
|
||||
size_t l = geohashEncodeImpl(args.longitude_min, args.latitude_min, args.precision, out);
|
||||
out += l;
|
||||
size_t length = geohashEncodeImpl(args.longitude_min, args.latitude_min, args.precision, out);
|
||||
out += length;
|
||||
*out = '\0';
|
||||
++out;
|
||||
|
||||
|
@ -23,15 +23,17 @@ std::vector<std::pair<Float64, Float64>> geohashCoverBox(
|
||||
struct GeohashesInBoxPreparedArgs
|
||||
{
|
||||
UInt64 items_count = 0;
|
||||
uint8_t precision = 0;
|
||||
|
||||
UInt32 longitude_items = 0;
|
||||
UInt32 latitude_items = 0;
|
||||
|
||||
Float64 longitude_min = 0.0;
|
||||
Float64 latitude_min = 0.0;
|
||||
Float64 longitude_max = 0.0;
|
||||
Float64 latitude_max = 0.0;
|
||||
|
||||
Float64 longitude_step = 0.0;
|
||||
Float64 latitude_step = 0.0;
|
||||
|
||||
uint8_t precision = 0;
|
||||
};
|
||||
|
||||
GeohashesInBoxPreparedArgs geohashesInBoxPrepare(
|
||||
|
@ -177,7 +177,7 @@ namespace detail
|
||||
* /// There could be as many implementation for every target as you want.
|
||||
* selector.registerImplementation<TargetArch::Default, MyDefaultImpl>();
|
||||
* #if USE_MULTITARGET_CODE
|
||||
* selector.registreImplementation<TargetArch::AVX2, TargetSpecific::AVX2::MyAVX2Impl>();
|
||||
* selector.registerImplementation<TargetArch::AVX2, TargetSpecific::AVX2::MyAVX2Impl>();
|
||||
* #endif
|
||||
* }
|
||||
*
|
||||
|
@ -143,7 +143,7 @@ namespace MultiRegexps
|
||||
patterns.push_back(ref.data);
|
||||
/* Flags below are the pattern matching flags.
|
||||
* HS_FLAG_DOTALL is a compile flag where matching a . will not exclude newlines. This is a good
|
||||
* performance practice accrording to Hyperscan API. https://intel.github.io/hyperscan/dev-reference/performance.html#dot-all-mode
|
||||
* performance practice according to Hyperscan API. https://intel.github.io/hyperscan/dev-reference/performance.html#dot-all-mode
|
||||
* HS_FLAG_ALLOWEMPTY is a compile flag where empty strings are allowed to match.
|
||||
* HS_FLAG_UTF8 is a flag where UTF8 literals are matched.
|
||||
* HS_FLAG_SINGLEMATCH is a compile flag where each pattern match will be returned only once. it is a good performance practice
|
||||
|
@ -13,7 +13,10 @@ namespace DB
|
||||
class Block;
|
||||
|
||||
/// Determine working timezone either from optional argument with time zone name or from time zone in DateTime type of argument.
|
||||
std::string extractTimeZoneNameFromFunctionArguments(const ColumnsWithTypeAndName & arguments, size_t time_zone_arg_num, size_t datetime_arg_num);
|
||||
const DateLUTImpl & extractTimeZoneFromFunctionArguments(Block & block, const ColumnNumbers & arguments, size_t time_zone_arg_num, size_t datetime_arg_num);
|
||||
std::string extractTimeZoneNameFromFunctionArguments(
|
||||
const ColumnsWithTypeAndName & arguments, size_t time_zone_arg_num, size_t datetime_arg_num);
|
||||
|
||||
const DateLUTImpl & extractTimeZoneFromFunctionArguments(
|
||||
Block & block, const ColumnNumbers & arguments, size_t time_zone_arg_num, size_t datetime_arg_num);
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
@ -121,9 +122,7 @@ public:
|
||||
geohashesInBox(prepared_args, out);
|
||||
|
||||
for (UInt64 i = 1; i <= prepared_args.items_count ; ++i)
|
||||
{
|
||||
res_strings_offsets.push_back(starting_offset + (prepared_args.precision + 1) * i);
|
||||
}
|
||||
res_offsets.push_back((res_offsets.empty() ? 0 : res_offsets.back()) + prepared_args.items_count);
|
||||
}
|
||||
if (!res_strings_offsets.empty() && res_strings_offsets.back() != res_strings_chars.size())
|
||||
|
@ -43,7 +43,7 @@ public:
|
||||
{
|
||||
if (arguments.size() != 1 || !isString(arguments[0].type) || !arguments[0].column || !isColumnConst(*arguments[0].column))
|
||||
throw Exception("Function " + getName() + " accepts one const string argument", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
||||
auto scalar_name = assert_cast<const ColumnConst &>(*arguments[0].column).getField().get<String>();
|
||||
auto scalar_name = assert_cast<const ColumnConst &>(*arguments[0].column).getValue<String>();
|
||||
scalar = context.getScalar(scalar_name).getByPosition(0);
|
||||
return scalar.type;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ namespace ErrorCodes
|
||||
{
|
||||
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
extern const int ARGUMENT_OUT_OF_BOUND;
|
||||
}
|
||||
|
||||
// Implements function, giving value for column within range of given
|
||||
@ -111,6 +112,10 @@ public:
|
||||
|
||||
Int64 offset = offset_column->getInt(0);
|
||||
|
||||
/// Protection from possible overflow.
|
||||
if (unlikely(offset > (1 << 30) || offset < -(1 << 30)))
|
||||
throw Exception(ErrorCodes::ARGUMENT_OUT_OF_BOUND, "Too large offset: {} in function {}", offset, getName());
|
||||
|
||||
auto result_column = result_type->createColumn();
|
||||
|
||||
auto insert_range_from = [&](bool is_const, const ColumnPtr & src, Int64 begin, Int64 size)
|
||||
@ -145,7 +150,9 @@ public:
|
||||
if (offset == 0)
|
||||
{
|
||||
/// Degenerate case, just copy source column as is.
|
||||
block.getByPosition(result).column = source_is_constant ? ColumnConst::create(source_column_casted, input_rows_count) : source_column_casted;
|
||||
block.getByPosition(result).column = source_is_constant
|
||||
? ColumnConst::create(source_column_casted, input_rows_count)
|
||||
: source_column_casted;
|
||||
}
|
||||
else if (offset > 0)
|
||||
{
|
||||
@ -166,7 +173,13 @@ public:
|
||||
|
||||
for (size_t row = 0; row < input_rows_count; ++row)
|
||||
{
|
||||
Int64 src_idx = row + offset_column->getInt(offset_is_constant ? 0 : row);
|
||||
Int64 offset = offset_column->getInt(offset_is_constant ? 0 : row);
|
||||
|
||||
/// Protection from possible overflow.
|
||||
if (unlikely(offset > (1 << 30) || offset < -(1 << 30)))
|
||||
throw Exception(ErrorCodes::ARGUMENT_OUT_OF_BOUND, "Too large offset: {} in function {}", offset, getName());
|
||||
|
||||
Int64 src_idx = row + offset;
|
||||
|
||||
if (src_idx >= 0 && src_idx < Int64(input_rows_count))
|
||||
result_column->insertFrom(*source_column_casted, source_is_constant ? 0 : src_idx);
|
||||
|
@ -85,7 +85,7 @@ public:
|
||||
/// We do not sleep if the block is empty.
|
||||
if (size > 0)
|
||||
{
|
||||
/// When sleeping, the query cannot be cancelled. For abitily to cancel query, we limit sleep time.
|
||||
/// When sleeping, the query cannot be cancelled. For ability to cancel query, we limit sleep time.
|
||||
if (seconds > 3.0) /// The choice is arbitrary
|
||||
throw Exception("The maximum sleep time is 3 seconds. Requested: " + toString(seconds), ErrorCodes::TOO_SLOW);
|
||||
|
||||
|
@ -325,6 +325,7 @@ void AsynchronousMetrics::update()
|
||||
saveAllArenasMetric<size_t>(new_values, "muzzy_purged");
|
||||
#endif
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
// Try to add processor frequencies, ignoring errors.
|
||||
try
|
||||
{
|
||||
@ -366,6 +367,7 @@ void AsynchronousMetrics::update()
|
||||
{
|
||||
tryLogCurrentException(__PRETTY_FUNCTION__);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Add more metrics as you wish.
|
||||
|
||||
|
@ -1935,7 +1935,7 @@ void Context::reloadConfig() const
|
||||
{
|
||||
/// Use mutex if callback may be changed after startup.
|
||||
if (!shared->config_reload_callback)
|
||||
throw Exception("Can't reload config beacuse config_reload_callback is not set.", ErrorCodes::LOGICAL_ERROR);
|
||||
throw Exception("Can't reload config because config_reload_callback is not set.", ErrorCodes::LOGICAL_ERROR);
|
||||
|
||||
shared->config_reload_callback();
|
||||
}
|
||||
|
@ -46,7 +46,7 @@
|
||||
#include <Processors/QueryPlan/LimitByStep.h>
|
||||
#include <Processors/QueryPlan/LimitStep.h>
|
||||
#include <Processors/QueryPlan/MergingAggregatedStep.h>
|
||||
#include <Processors/QueryPlan/AddingDelayedStreamStep.h>
|
||||
#include <Processors/QueryPlan/AddingDelayedSourceStep.h>
|
||||
#include <Processors/QueryPlan/AggregatingStep.h>
|
||||
#include <Processors/QueryPlan/CreatingSetsStep.h>
|
||||
#include <Processors/QueryPlan/TotalsHavingStep.h>
|
||||
@ -886,7 +886,7 @@ void InterpreterSelectQuery::executeImpl(QueryPlan & query_plan, const BlockInpu
|
||||
if (auto stream = join->createStreamWithNonJoinedRows(join_result_sample, settings.max_block_size))
|
||||
{
|
||||
auto source = std::make_shared<SourceFromInputStream>(std::move(stream));
|
||||
auto add_non_joined_rows_step = std::make_unique<AddingDelayedStreamStep>(
|
||||
auto add_non_joined_rows_step = std::make_unique<AddingDelayedSourceStep>(
|
||||
query_plan.getCurrentDataStream(), std::move(source));
|
||||
|
||||
add_non_joined_rows_step->setStepDescription("Add non-joined rows after JOIN");
|
||||
|
@ -151,7 +151,7 @@ struct ColumnAliasesMatcher
|
||||
|
||||
void replaceIdentifiersWithAliases()
|
||||
{
|
||||
String hide_prefix = "--"; /// @note restriction: user should not use alises like `--table.column`
|
||||
String hide_prefix = "--"; /// @note restriction: user should not use aliases like `--table.column`
|
||||
|
||||
for (auto & [identifier, is_public] : compound_identifiers)
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ class ASTSelectQuery;
|
||||
class Context;
|
||||
|
||||
/// AST transformer. It replaces multiple joins to (subselect + join) track.
|
||||
/// 'select * from t1 join t2 on ... join t3 on ... join t4 on ...' would be rewriten with
|
||||
/// 'select * from t1 join t2 on ... join t3 on ... join t4 on ...' would be rewritten with
|
||||
/// 'select * from (select * from t1 join t2 on ...) join t3 on ...) join t4 on ...'
|
||||
class JoinToSubqueryTransformMatcher
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ using StorageMetadataPtr = std::shared_ptr<const StorageInMemoryMetadata>;
|
||||
class JoinedTables
|
||||
{
|
||||
public:
|
||||
JoinedTables(Context && contex, const ASTSelectQuery & select_query);
|
||||
JoinedTables(Context && context, const ASTSelectQuery & select_query);
|
||||
|
||||
void reset(const ASTSelectQuery & select_query)
|
||||
{
|
||||
|
@ -107,11 +107,9 @@ void sortBlock(Block & block, const SortDescription & description, UInt64 limit)
|
||||
if (!block)
|
||||
return;
|
||||
|
||||
|
||||
/// If only one column to sort by
|
||||
if (description.size() == 1)
|
||||
{
|
||||
|
||||
IColumn::Permutation perm;
|
||||
bool reverse = description[0].direction == -1;
|
||||
|
||||
@ -181,20 +179,20 @@ void sortBlock(Block & block, const SortDescription & description, UInt64 limit)
|
||||
{
|
||||
EqualRanges ranges;
|
||||
ranges.emplace_back(0, perm.size());
|
||||
for (const auto& column : columns_with_sort_desc)
|
||||
for (const auto & column : columns_with_sort_desc)
|
||||
{
|
||||
while (!ranges.empty() && limit && limit <= ranges.back().first)
|
||||
ranges.pop_back();
|
||||
|
||||
|
||||
if (ranges.empty())
|
||||
break;
|
||||
|
||||
|
||||
if (isCollationRequired(column.description))
|
||||
{
|
||||
const ColumnString & column_string = assert_cast<const ColumnString &>(*column.column);
|
||||
column_string.updatePermutationWithCollation(*column.description.collator, column.description.direction < 0, limit, column.description.nulls_direction, perm, ranges);
|
||||
column_string.updatePermutationWithCollation(
|
||||
*column.description.collator,
|
||||
column.description.direction < 0, limit, column.description.nulls_direction, perm, ranges);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -207,7 +205,7 @@ void sortBlock(Block & block, const SortDescription & description, UInt64 limit)
|
||||
{
|
||||
EqualRanges ranges;
|
||||
ranges.emplace_back(0, perm.size());
|
||||
for (const auto& column : columns_with_sort_desc)
|
||||
for (const auto & column : columns_with_sort_desc)
|
||||
{
|
||||
while (!ranges.empty() && limit && limit <= ranges.back().first)
|
||||
{
|
||||
|
@ -67,10 +67,12 @@ ASTPtr ASTDictionaryLayout::clone() const
|
||||
auto res = std::make_shared<ASTDictionaryLayout>(*this);
|
||||
res->children.clear();
|
||||
res->layout_type = layout_type;
|
||||
if (parameter.has_value())
|
||||
res->parameters.clear();
|
||||
res->has_brackets = has_brackets;
|
||||
for (const auto & parameter : parameters)
|
||||
{
|
||||
res->parameter.emplace(parameter->first, nullptr);
|
||||
res->set(res->parameter->second, parameter->second->clone());
|
||||
res->parameters.emplace_back(parameter.first, nullptr);
|
||||
res->set(res->parameters.back().second, parameter.second->clone());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -91,14 +93,17 @@ void ASTDictionaryLayout::formatImpl(const FormatSettings & settings,
|
||||
if (has_brackets)
|
||||
settings.ostr << "(";
|
||||
|
||||
if (parameter)
|
||||
bool first = true;
|
||||
for (const auto & parameter : parameters)
|
||||
{
|
||||
settings.ostr << (settings.hilite ? hilite_keyword : "")
|
||||
<< Poco::toUpper(parameter->first)
|
||||
settings.ostr << (first ? "" : " ")
|
||||
<< (settings.hilite ? hilite_keyword : "")
|
||||
<< Poco::toUpper(parameter.first)
|
||||
<< (settings.hilite ? hilite_none : "")
|
||||
<< " ";
|
||||
|
||||
parameter->second->formatImpl(settings, state, frame);
|
||||
parameter.second->formatImpl(settings, state, frame);
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (has_brackets)
|
||||
|
@ -35,8 +35,8 @@ class ASTDictionaryLayout : public IAST
|
||||
public:
|
||||
/// flat, cache, hashed, etc.
|
||||
String layout_type;
|
||||
/// optional parameter (size_in_cells)
|
||||
std::optional<KeyValue> parameter;
|
||||
/// parameters (size_in_cells, ...)
|
||||
std::vector<KeyValue> parameters;
|
||||
/// has brackets after layout type
|
||||
bool has_brackets = true;
|
||||
|
||||
|
@ -17,7 +17,7 @@ void ASTLiteral::updateTreeHashImpl(SipHash & hash_state) const
|
||||
}
|
||||
|
||||
/// Writes 'tuple' word before tuple literals for backward compatibility reasons.
|
||||
/// TODO: remove, when versions lower than 20.3 will be rearely used.
|
||||
/// TODO: remove, when versions lower than 20.3 will be rarely used.
|
||||
class FieldVisitorToColumnName : public StaticVisitor<String>
|
||||
{
|
||||
public:
|
||||
|
@ -10,7 +10,7 @@ namespace DB
|
||||
{
|
||||
|
||||
/// If output stream set dumps node with indents and some additional info. Do nothing otherwise.
|
||||
/// Allow to print kay-value pairs inside of tree dump.
|
||||
/// Allow to print key-value pairs inside of tree dump.
|
||||
class DumpASTNode
|
||||
{
|
||||
public:
|
||||
|
@ -1097,7 +1097,7 @@ const char * ParserAlias::restricted_keywords[] =
|
||||
"ASOF",
|
||||
"SEMI",
|
||||
"ANTI",
|
||||
"ONLY", /// YQL synonym for ANTI
|
||||
"ONLY", /// YQL synonim for ANTI. Note: YQL is the name of one of Yandex proprietary languages, completely unrelated to ClickHouse.
|
||||
"ON",
|
||||
"USING",
|
||||
"PREWHERE",
|
||||
|
@ -126,25 +126,21 @@ bool ParserDictionaryLayout::parseImpl(Pos & pos, ASTPtr & node, Expected & expe
|
||||
res->has_brackets = func.has_brackets;
|
||||
const ASTExpressionList & type_expr_list = func.elements->as<const ASTExpressionList &>();
|
||||
|
||||
/// there are no layout with more than 1 parameter
|
||||
if (type_expr_list.children.size() > 1)
|
||||
return false;
|
||||
|
||||
/// if layout has params than brackets must be specified
|
||||
if (!type_expr_list.children.empty() && !res->has_brackets)
|
||||
return false;
|
||||
|
||||
if (type_expr_list.children.size() == 1)
|
||||
for (const auto & child : type_expr_list.children)
|
||||
{
|
||||
const ASTPair * pair = dynamic_cast<const ASTPair *>(type_expr_list.children.at(0).get());
|
||||
const ASTPair * pair = dynamic_cast<const ASTPair *>(child.get());
|
||||
if (pair == nullptr)
|
||||
return false;
|
||||
|
||||
const ASTLiteral * literal = dynamic_cast<const ASTLiteral *>(pair->second.get());
|
||||
if (literal == nullptr || literal->value.getType() != Field::Types::UInt64)
|
||||
if (literal == nullptr || (literal->value.getType() != Field::Types::UInt64 && literal->value.getType() != Field::Types::String))
|
||||
return false;
|
||||
res->parameter.emplace(pair->first, nullptr);
|
||||
res->set(res->parameter->second, literal->clone());
|
||||
res->parameters.emplace_back(pair->first, nullptr);
|
||||
res->set(res->parameters.back().second, literal->clone());
|
||||
}
|
||||
|
||||
node = res;
|
||||
|
@ -35,7 +35,7 @@ protected:
|
||||
private:
|
||||
std::unique_ptr<QueryPipeline> pipeline;
|
||||
/// One of executors is used.
|
||||
std::unique_ptr<PullingPipelineExecutor> executor; /// for singe thread.
|
||||
std::unique_ptr<PullingPipelineExecutor> executor; /// for single thread.
|
||||
std::unique_ptr<PullingAsyncPipelineExecutor> async_executor; /// for many threads.
|
||||
bool is_execution_started = false;
|
||||
|
||||
|
@ -547,7 +547,7 @@ AvroDeserializer::Action AvroDeserializer::createAction(const Block & header, co
|
||||
}
|
||||
}
|
||||
|
||||
AvroDeserializer::AvroDeserializer(const Block & header, avro::ValidSchema schema)
|
||||
AvroDeserializer::AvroDeserializer(const Block & header, avro::ValidSchema schema, const FormatSettings & format_settings)
|
||||
{
|
||||
const auto & schema_root = schema.root();
|
||||
if (schema_root->type() != avro::AVRO_RECORD)
|
||||
@ -557,12 +557,15 @@ AvroDeserializer::AvroDeserializer(const Block & header, avro::ValidSchema schem
|
||||
|
||||
column_found.resize(header.columns());
|
||||
row_action = createAction(header, schema_root);
|
||||
|
||||
for (size_t i = 0; i < header.columns(); ++i)
|
||||
// fail on missing fields when allow_missing_fields = false
|
||||
if (!format_settings.avro.allow_missing_fields)
|
||||
{
|
||||
if (!column_found[i])
|
||||
for (size_t i = 0; i < header.columns(); ++i)
|
||||
{
|
||||
throw Exception("Field " + header.getByPosition(i).name + " not found in Avro schema", ErrorCodes::THERE_IS_NO_COLUMN);
|
||||
if (!column_found[i])
|
||||
{
|
||||
throw Exception("Field " + header.getByPosition(i).name + " not found in Avro schema", ErrorCodes::THERE_IS_NO_COLUMN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -581,10 +584,10 @@ void AvroDeserializer::deserializeRow(MutableColumns & columns, avro::Decoder &
|
||||
}
|
||||
|
||||
|
||||
AvroRowInputFormat::AvroRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_)
|
||||
AvroRowInputFormat::AvroRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_, const FormatSettings & format_settings_)
|
||||
: IRowInputFormat(header_, in_, params_)
|
||||
, file_reader(std::make_unique<InputStreamReadBufferAdapter>(in_))
|
||||
, deserializer(output.getHeader(), file_reader.dataSchema())
|
||||
, deserializer(output.getHeader(), file_reader.dataSchema(), format_settings_)
|
||||
{
|
||||
file_reader.init();
|
||||
}
|
||||
@ -712,6 +715,7 @@ AvroConfluentRowInputFormat::AvroConfluentRowInputFormat(
|
||||
, schema_registry(getConfluentSchemaRegistry(format_settings_))
|
||||
, input_stream(std::make_unique<InputStreamReadBufferAdapter>(in))
|
||||
, decoder(avro::binaryDecoder())
|
||||
, format_settings(format_settings_)
|
||||
|
||||
{
|
||||
decoder->init(*input_stream);
|
||||
@ -736,7 +740,7 @@ const AvroDeserializer & AvroConfluentRowInputFormat::getOrCreateDeserializer(Sc
|
||||
if (it == deserializer_cache.end())
|
||||
{
|
||||
auto schema = schema_registry->getSchema(schema_id);
|
||||
AvroDeserializer deserializer(output.getHeader(), schema);
|
||||
AvroDeserializer deserializer(output.getHeader(), schema, format_settings);
|
||||
it = deserializer_cache.emplace(schema_id, deserializer).first;
|
||||
}
|
||||
return it->second;
|
||||
@ -748,9 +752,9 @@ void registerInputFormatProcessorAvro(FormatFactory & factory)
|
||||
ReadBuffer & buf,
|
||||
const Block & sample,
|
||||
const RowInputFormatParams & params,
|
||||
const FormatSettings &)
|
||||
const FormatSettings & settings)
|
||||
{
|
||||
return std::make_shared<AvroRowInputFormat>(sample, buf, params);
|
||||
return std::make_shared<AvroRowInputFormat>(sample, buf, params, settings);
|
||||
});
|
||||
|
||||
factory.registerInputFormatProcessor("AvroConfluent",[](
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user