Merge branch 'master' into sleep-each-row-max-time

This commit is contained in:
Alexey Milovidov 2023-07-22 02:25:03 +02:00
commit 91fd286165
466 changed files with 9595 additions and 3422 deletions

View File

@ -448,7 +448,7 @@ inline char * find_last_not_symbols_or_null(char * begin, char * end)
/// See https://github.com/boostorg/algorithm/issues/63 /// See https://github.com/boostorg/algorithm/issues/63
/// And https://bugs.llvm.org/show_bug.cgi?id=41141 /// And https://bugs.llvm.org/show_bug.cgi?id=41141
template <char... symbols, typename To> template <char... symbols, typename To>
inline void splitInto(To & to, const std::string & what, bool token_compress = false) inline To & splitInto(To & to, std::string_view what, bool token_compress = false)
{ {
const char * pos = what.data(); const char * pos = what.data();
const char * end = pos + what.size(); const char * end = pos + what.size();
@ -464,4 +464,6 @@ inline void splitInto(To & to, const std::string & what, bool token_compress = f
else else
pos = delimiter_or_end; pos = delimiter_or_end;
} }
return to;
} }

9
base/base/move_extend.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
/// Extend @p to by moving elements from @p from to @p to end
/// @return @p to iterator to first of moved elements.
template <class To, class From>
typename To::iterator moveExtend(To & to, From && from)
{
return to.insert(to.end(), std::make_move_iterator(from.begin()), std::make_move_iterator(from.end()));
}

View File

@ -306,7 +306,7 @@ namespace Net
DEFAULT_KEEP_ALIVE_TIMEOUT = 8 DEFAULT_KEEP_ALIVE_TIMEOUT = 8
}; };
void reconnect(); virtual void reconnect();
/// Connects the underlying socket to the HTTP server. /// Connects the underlying socket to the HTTP server.
int write(const char * buffer, std::streamsize length); int write(const char * buffer, std::streamsize length);

2
contrib/NuRaft vendored

@ -1 +1 @@
Subproject commit 491eaf592d950e0e37accbe8b3f217e068c9fecf Subproject commit eb1572129c71beb2156dcdaadc3fb136954aed96

View File

@ -4,6 +4,8 @@ services:
kafka_zookeeper: kafka_zookeeper:
image: zookeeper:3.4.9 image: zookeeper:3.4.9
hostname: kafka_zookeeper hostname: kafka_zookeeper
ports:
- 2181:2181
environment: environment:
ZOO_MY_ID: 1 ZOO_MY_ID: 1
ZOO_PORT: 2181 ZOO_PORT: 2181
@ -15,15 +17,14 @@ services:
image: confluentinc/cp-kafka:5.2.0 image: confluentinc/cp-kafka:5.2.0
hostname: kafka1 hostname: kafka1
ports: ports:
- ${KAFKA_EXTERNAL_PORT:-8081}:${KAFKA_EXTERNAL_PORT:-8081} - ${KAFKA_EXTERNAL_PORT}:${KAFKA_EXTERNAL_PORT}
environment: environment:
KAFKA_ADVERTISED_LISTENERS: INSIDE://localhost:${KAFKA_EXTERNAL_PORT},OUTSIDE://kafka1:19092 KAFKA_ADVERTISED_LISTENERS: INSIDE://localhost:${KAFKA_EXTERNAL_PORT},OUTSIDE://kafka1:19092
KAFKA_ADVERTISED_HOST_NAME: kafka1 KAFKA_ADVERTISED_HOST_NAME: kafka1
KAFKA_LISTENERS: INSIDE://0.0.0.0:${KAFKA_EXTERNAL_PORT},OUTSIDE://0.0.0.0:19092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
KAFKA_BROKER_ID: 1 KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: "kafka_zookeeper:2181" KAFKA_ZOOKEEPER_CONNECT: kafka_zookeeper:2181
KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
depends_on: depends_on:
@ -35,13 +36,38 @@ services:
image: confluentinc/cp-schema-registry:5.2.0 image: confluentinc/cp-schema-registry:5.2.0
hostname: schema-registry hostname: schema-registry
ports: ports:
- ${SCHEMA_REGISTRY_EXTERNAL_PORT:-12313}:${SCHEMA_REGISTRY_INTERNAL_PORT:-12313} - ${SCHEMA_REGISTRY_EXTERNAL_PORT}:${SCHEMA_REGISTRY_EXTERNAL_PORT}
environment: environment:
SCHEMA_REGISTRY_HOST_NAME: schema-registry SCHEMA_REGISTRY_HOST_NAME: schema-registry
SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092 SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:${SCHEMA_REGISTRY_EXTERNAL_PORT}
SCHEMA_REGISTRY_SCHEMA_REGISTRY_GROUP_ID: noauth
depends_on: depends_on:
- kafka_zookeeper - kafka_zookeeper
- kafka1 - kafka1
restart: always
security_opt:
- label:disable
schema-registry-auth:
image: confluentinc/cp-schema-registry:5.2.0
hostname: schema-registry-auth
ports:
- ${SCHEMA_REGISTRY_AUTH_EXTERNAL_PORT}:${SCHEMA_REGISTRY_AUTH_EXTERNAL_PORT}
environment:
SCHEMA_REGISTRY_HOST_NAME: schema-registry-auth
SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:${SCHEMA_REGISTRY_AUTH_EXTERNAL_PORT}
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
SCHEMA_REGISTRY_AUTHENTICATION_METHOD: BASIC
SCHEMA_REGISTRY_AUTHENTICATION_ROLES: user
SCHEMA_REGISTRY_AUTHENTICATION_REALM: RealmFooBar
SCHEMA_REGISTRY_OPTS: "-Djava.security.auth.login.config=/etc/schema-registry/secrets/schema_registry_jaas.conf"
SCHEMA_REGISTRY_SCHEMA_REGISTRY_GROUP_ID: auth
volumes:
- ${SCHEMA_REGISTRY_DIR:-}/secrets:/etc/schema-registry/secrets
depends_on:
- kafka_zookeeper
- kafka1
restart: always
security_opt: security_opt:
- label:disable - label:disable

View File

@ -76,6 +76,7 @@ The supported formats are:
| [RowBinary](#rowbinary) | ✔ | ✔ | | [RowBinary](#rowbinary) | ✔ | ✔ |
| [RowBinaryWithNames](#rowbinarywithnamesandtypes) | ✔ | ✔ | | [RowBinaryWithNames](#rowbinarywithnamesandtypes) | ✔ | ✔ |
| [RowBinaryWithNamesAndTypes](#rowbinarywithnamesandtypes) | ✔ | ✔ | | [RowBinaryWithNamesAndTypes](#rowbinarywithnamesandtypes) | ✔ | ✔ |
| [RowBinaryWithDefaults](#rowbinarywithdefaults) | ✔ | ✔ |
| [Native](#native) | ✔ | ✔ | | [Native](#native) | ✔ | ✔ |
| [Null](#null) | ✗ | ✔ | | [Null](#null) | ✗ | ✔ |
| [XML](#xml) | ✗ | ✔ | | [XML](#xml) | ✗ | ✔ |
@ -472,6 +473,7 @@ The CSV format supports the output of totals and extremes the same way as `TabSe
- [input_format_csv_trim_whitespaces](/docs/en/operations/settings/settings-formats.md/#input_format_csv_trim_whitespaces) - trim spaces and tabs in non-quoted CSV strings. Default value - `true`. - [input_format_csv_trim_whitespaces](/docs/en/operations/settings/settings-formats.md/#input_format_csv_trim_whitespaces) - trim spaces and tabs in non-quoted CSV strings. Default value - `true`.
- [input_format_csv_allow_whitespace_or_tab_as_delimiter](/docs/en/operations/settings/settings-formats.md/# input_format_csv_allow_whitespace_or_tab_as_delimiter) - Allow to use whitespace or tab as field delimiter in CSV strings. Default value - `false`. - [input_format_csv_allow_whitespace_or_tab_as_delimiter](/docs/en/operations/settings/settings-formats.md/# input_format_csv_allow_whitespace_or_tab_as_delimiter) - Allow to use whitespace or tab as field delimiter in CSV strings. Default value - `false`.
- [input_format_csv_allow_variable_number_of_columns](/docs/en/operations/settings/settings-formats.md/#input_format_csv_allow_variable_number_of_columns) - ignore extra columns in CSV input (if file has more columns than expected) and treat missing fields in CSV input as default values. Default value - `false`. - [input_format_csv_allow_variable_number_of_columns](/docs/en/operations/settings/settings-formats.md/#input_format_csv_allow_variable_number_of_columns) - ignore extra columns in CSV input (if file has more columns than expected) and treat missing fields in CSV input as default values. Default value - `false`.
- [input_format_csv_use_default_on_bad_values](/docs/en/operations/settings/settings-formats.md/#input_format_csv_use_default_on_bad_values) - Allow to set default value to column when CSV field deserialization failed on bad value. Default value - `false`.
## CSVWithNames {#csvwithnames} ## CSVWithNames {#csvwithnames}
@ -1515,6 +1517,23 @@ If setting [input_format_with_types_use_header](/docs/en/operations/settings/set
the types from input data will be compared with the types of the corresponding columns from the table. Otherwise, the second row will be skipped. the types from input data will be compared with the types of the corresponding columns from the table. Otherwise, the second row will be skipped.
::: :::
## RowBinaryWithDefaults {#rowbinarywithdefaults}
Similar to [RowBinary](#rowbinary), but with an extra byte before each column that indicates if default value should be used.
Examples:
```sql
:) select * from format('RowBinaryWithDefaults', 'x UInt32 default 42, y UInt32', x'010001000000')
┌──x─┬─y─┐
│ 42 │ 1 │
└────┴───┘
```
For column `x` there is only one byte `01` that indicates that default value should be used and no other data after this byte is provided.
For column `y` data starts with byte `00` that indicates that column has actual value that should be read from the subsequent data `01000000`.
## RowBinary format settings {#row-binary-format-settings} ## RowBinary format settings {#row-binary-format-settings}
- [format_binary_max_string_size](/docs/en/operations/settings/settings-formats.md/#format_binary_max_string_size) - The maximum allowed size for String in RowBinary format. Default value - `1GiB`. - [format_binary_max_string_size](/docs/en/operations/settings/settings-formats.md/#format_binary_max_string_size) - The maximum allowed size for String in RowBinary format. Default value - `1GiB`.

View File

@ -30,7 +30,7 @@ description: In order to effectively mitigate possible human errors, you should
``` ```
:::note ALL :::note ALL
`ALL` is only applicable to the `RESTORE` command prior to version 23.4 of Clickhouse. Prior to version 23.4 of ClickHouse, `ALL` was only applicable to the `RESTORE` command.
::: :::
## Background ## Background

View File

@ -989,6 +989,28 @@ Result
a b a b
``` ```
### input_format_csv_use_default_on_bad_values {#input_format_csv_use_default_on_bad_values}
Allow to set default value to column when CSV field deserialization failed on bad value
Default value: `false`.
**Examples**
Query
```bash
./clickhouse local -q "create table test_tbl (x String, y UInt32, z Date) engine=MergeTree order by x"
echo 'a,b,c' | ./clickhouse local -q "INSERT INTO test_tbl SETTINGS input_format_csv_use_default_on_bad_values=true FORMAT CSV"
./clickhouse local -q "select * from test_tbl"
```
Result
```text
a 0 1971-01-01
```
## Values format settings {#values-format-settings} ## Values format settings {#values-format-settings}
### input_format_values_interpret_expressions {#input_format_values_interpret_expressions} ### input_format_values_interpret_expressions {#input_format_values_interpret_expressions}
@ -1325,6 +1347,17 @@ Default value: 0.
Sets [Confluent Schema Registry](https://docs.confluent.io/current/schema-registry/index.html) URL to use with [AvroConfluent](../../interfaces/formats.md/#data-format-avro-confluent) format. Sets [Confluent Schema Registry](https://docs.confluent.io/current/schema-registry/index.html) URL to use with [AvroConfluent](../../interfaces/formats.md/#data-format-avro-confluent) format.
Format:
``` text
http://[user:password@]machine[:port]"
```
Examples:
``` text
http://registry.example.com:8081
http://admin:secret@registry.example.com:8081
```
Default value: `Empty`. Default value: `Empty`.
### output_format_avro_codec {#output_format_avro_codec} ### output_format_avro_codec {#output_format_avro_codec}

View File

@ -537,6 +537,8 @@ Possible values:
The first phase of a grace join reads the right table and splits it into N buckets depending on the hash value of key columns (initially, N is `grace_hash_join_initial_buckets`). This is done in a way to ensure that each bucket can be processed independently. Rows from the first bucket are added to an in-memory hash table while the others are saved to disk. If the hash table grows beyond the memory limit (e.g., as set by [`max_bytes_in_join`](/docs/en/operations/settings/query-complexity.md/#settings-max_bytes_in_join)), the number of buckets is increased and the assigned bucket for each row. Any rows which dont belong to the current bucket are flushed and reassigned. The first phase of a grace join reads the right table and splits it into N buckets depending on the hash value of key columns (initially, N is `grace_hash_join_initial_buckets`). This is done in a way to ensure that each bucket can be processed independently. Rows from the first bucket are added to an in-memory hash table while the others are saved to disk. If the hash table grows beyond the memory limit (e.g., as set by [`max_bytes_in_join`](/docs/en/operations/settings/query-complexity.md/#settings-max_bytes_in_join)), the number of buckets is increased and the assigned bucket for each row. Any rows which dont belong to the current bucket are flushed and reassigned.
Supports `INNER/LEFT/RIGHT/FULL ALL/ANY JOIN`.
- hash - hash
[Hash join algorithm](https://en.wikipedia.org/wiki/Hash_join) is used. The most generic implementation that supports all combinations of kind and strictness and multiple join keys that are combined with `OR` in the `JOIN ON` section. [Hash join algorithm](https://en.wikipedia.org/wiki/Hash_join) is used. The most generic implementation that supports all combinations of kind and strictness and multiple join keys that are combined with `OR` in the `JOIN ON` section.

View File

@ -6,9 +6,20 @@ sidebar_label: Arithmetic
# Arithmetic Functions # Arithmetic Functions
The result type of all arithmetic functions is the smallest type which can represent all possible results. Size promotion happens for integers up to 32 bit, e.g. `UInt8 + UInt16 = UInt32`. If one of the inters has 64 or more bits, the result is of the same type as the bigger of the input integers, e.g. `UInt16 + UInt128 = UInt128`. While this introduces a risk of overflows around the value range boundary, it ensures that calculations are performed quickly using the maximum native integer width of 64 bit. Arithmetic functions work for any two operands of type `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Int8`, `Int16`, `Int32`, `Int64`, `Float32`, or `Float64`.
The result of addition or multiplication of two integers is unsigned unless one of the integers is signed. Before performing the operation, both operands are casted to the result type. The result type is determined as follows (unless specified
differently in the function documentation below):
- If both operands are up to 32 bits wide, the size of the result type will be the size of the next bigger type following the bigger of the
two operands (integer size promotion). For example, `UInt8 + UInt16 = UInt32` or `Float32 * Float32 = Float64`.
- If one of the operands has 64 or more bits, the size of the result type will be the same size as the bigger of the two operands. For
example, `UInt32 + UInt128 = UInt128` or `Float32 * Float64 = Float64`.
- If one of the operands is signed, the result type will also be signed, otherwise it will be signed. For example, `UInt32 * Int32 = Int64`.
These rules make sure that the result type will be the smallest type which can represent all possible results. While this introduces a risk
of overflows around the value range boundary, it ensures that calculations are performed quickly using the maximum native integer width of
64 bit. This behavior also guarantees compatibility with many other databases which provide 64 bit integers (BIGINT) as the biggest integer
type.
Example: Example:
@ -22,8 +33,6 @@ SELECT toTypeName(0), toTypeName(0 + 0), toTypeName(0 + 0 + 0), toTypeName(0 + 0
└───────────────┴────────────────────────┴─────────────────────────────────┴──────────────────────────────────────────┘ └───────────────┴────────────────────────┴─────────────────────────────────┴──────────────────────────────────────────┘
``` ```
Arithmetic functions work for any pair of `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Int8`, `Int16`, `Int32`, `Int64`, `Float32`, or `Float64` values.
Overflows are produced the same way as in C++. Overflows are produced the same way as in C++.
## plus ## plus
@ -68,7 +77,7 @@ Alias: `a \* b` (operator)
## divide ## divide
Calculates the quotient of two values `a` and `b`. The result is always a floating-point value. If you need integer division, you can use the `intDiv` function. Calculates the quotient of two values `a` and `b`. The result type is always [Float64](../../sql-reference/data-types/float.md). Integer division is provided by the `intDiv` function.
Division by 0 returns `inf`, `-inf`, or `nan`. Division by 0 returns `inf`, `-inf`, or `nan`.
@ -84,7 +93,7 @@ Alias: `a / b` (operator)
Performs an integer division of two values `a` by `b`, i.e. computes the quotient rounded down to the next smallest integer. Performs an integer division of two values `a` by `b`, i.e. computes the quotient rounded down to the next smallest integer.
The result has the same type as the dividend (the first parameter). The result has the same width as the dividend (the first parameter).
An exception is thrown when dividing by zero, when the quotient does not fit in the range of the dividend, or when dividing a minimal negative number by minus one. An exception is thrown when dividing by zero, when the quotient does not fit in the range of the dividend, or when dividing a minimal negative number by minus one.
@ -135,7 +144,7 @@ intDivOrZero(a, b)
Calculates the remainder of the division of two values `a` by `b`. Calculates the remainder of the division of two values `a` by `b`.
The result type is an integer if both inputs are integers. If one of the inputs is a floating-point number, the result is a floating-point number. The result type is an integer if both inputs are integers. If one of the inputs is a floating-point number, the result type is [Float64](../../sql-reference/data-types/float.md).
The remainder is computed like in C++. Truncated division is used for negative numbers. The remainder is computed like in C++. Truncated division is used for negative numbers.

View File

@ -722,7 +722,7 @@ SELECT toDate('2016-12-27') AS date, toYearWeek(date) AS yearWeek0, toYearWeek(d
## age ## age
Returns the `unit` component of the difference between `startdate` and `enddate`. The difference is calculated using a precision of 1 second. Returns the `unit` component of the difference between `startdate` and `enddate`. The difference is calculated using a precision of 1 microsecond.
E.g. the difference between `2021-12-29` and `2022-01-01` is 3 days for `day` unit, 0 months for `month` unit, 0 years for `year` unit. E.g. the difference between `2021-12-29` and `2022-01-01` is 3 days for `day` unit, 0 months for `month` unit, 0 years for `year` unit.
For an alternative to `age`, see function `date\_diff`. For an alternative to `age`, see function `date\_diff`.
@ -738,6 +738,8 @@ age('unit', startdate, enddate, [timezone])
- `unit` — The type of interval for result. [String](../../sql-reference/data-types/string.md). - `unit` — The type of interval for result. [String](../../sql-reference/data-types/string.md).
Possible values: Possible values:
- `microsecond` (possible abbreviations: `us`, `u`)
- `millisecond` (possible abbreviations: `ms`)
- `second` (possible abbreviations: `ss`, `s`) - `second` (possible abbreviations: `ss`, `s`)
- `minute` (possible abbreviations: `mi`, `n`) - `minute` (possible abbreviations: `mi`, `n`)
- `hour` (possible abbreviations: `hh`, `h`) - `hour` (possible abbreviations: `hh`, `h`)
@ -813,6 +815,8 @@ Aliases: `dateDiff`, `DATE_DIFF`, `timestampDiff`, `timestamp_diff`, `TIMESTAMP_
- `unit` — The type of interval for result. [String](../../sql-reference/data-types/string.md). - `unit` — The type of interval for result. [String](../../sql-reference/data-types/string.md).
Possible values: Possible values:
- `microsecond` (possible abbreviations: `us`, `u`)
- `millisecond` (possible abbreviations: `ms`)
- `second` (possible abbreviations: `ss`, `s`) - `second` (possible abbreviations: `ss`, `s`)
- `minute` (possible abbreviations: `mi`, `n`) - `minute` (possible abbreviations: `mi`, `n`)
- `hour` (possible abbreviations: `hh`, `h`) - `hour` (possible abbreviations: `hh`, `h`)
@ -1134,6 +1138,8 @@ Result:
Returns the current date and time at the moment of query analysis. The function is a constant expression. Returns the current date and time at the moment of query analysis. The function is a constant expression.
Alias: `current_timestamp`.
**Syntax** **Syntax**
``` sql ``` sql
@ -1264,6 +1270,8 @@ Result:
Accepts zero arguments and returns the current date at one of the moments of query analysis. Accepts zero arguments and returns the current date at one of the moments of query analysis.
The same as toDate(now()). The same as toDate(now()).
Aliases: `curdate`, `current_date`.
## yesterday ## yesterday
Accepts zero arguments and returns yesterdays date at one of the moments of query analysis. Accepts zero arguments and returns yesterdays date at one of the moments of query analysis.

View File

@ -51,7 +51,7 @@ Calculates the MD5 from a string and returns the resulting set of bytes as Fixed
If you do not need MD5 in particular, but you need a decent cryptographic 128-bit hash, use the sipHash128 function instead. If you do not need MD5 in particular, but you need a decent cryptographic 128-bit hash, use the sipHash128 function instead.
If you want to get the same result as output by the md5sum utility, use lower(hex(MD5(s))). If you want to get the same result as output by the md5sum utility, use lower(hex(MD5(s))).
## sipHash64 (#hash_functions-siphash64) ## sipHash64 {#hash_functions-siphash64}
Produces a 64-bit [SipHash](https://en.wikipedia.org/wiki/SipHash) hash value. Produces a 64-bit [SipHash](https://en.wikipedia.org/wiki/SipHash) hash value.
@ -63,9 +63,9 @@ This is a cryptographic hash function. It works at least three times faster than
The function [interprets](/docs/en/sql-reference/functions/type-conversion-functions.md/#type_conversion_functions-reinterpretAsString) all the input parameters as strings and calculates the hash value for each of them. It then combines the hashes by the following algorithm: The function [interprets](/docs/en/sql-reference/functions/type-conversion-functions.md/#type_conversion_functions-reinterpretAsString) all the input parameters as strings and calculates the hash value for each of them. It then combines the hashes by the following algorithm:
1. The first and the second hash value are concatenated to an array which is hashed. 1. The first and the second hash value are concatenated to an array which is hashed.
2. The previously calculated hash value and the hash of the third input parameter are hashed in a similar way. 2. The previously calculated hash value and the hash of the third input parameter are hashed in a similar way.
3. This calculation is repeated for all remaining hash values of the original input. 3. This calculation is repeated for all remaining hash values of the original input.
**Arguments** **Arguments**

View File

@ -1267,3 +1267,36 @@ Like [initcap](#initcap), assuming that the string contains valid UTF-8 encoded
Does not detect the language, e.g. for Turkish the result might not be exactly correct (i/İ vs. i/I). Does not detect the language, e.g. for Turkish the result might not be exactly correct (i/İ vs. i/I).
If the length of the UTF-8 byte sequence is different for upper and lower case of a code point, the result may be incorrect for this code point. If the length of the UTF-8 byte sequence is different for upper and lower case of a code point, the result may be incorrect for this code point.
## firstLine
Returns the first line from a multi-line string.
**Syntax**
```sql
firstLine(val)
```
**Arguments**
- `val` - Input value. [String](../data-types/string.md)
**Returned value**
- The first line of the input value or the whole value if there is no line
separators. [String](../data-types/string.md)
**Example**
```sql
select firstLine('foo\nbar\nbaz');
```
Result:
```result
┌─firstLine('foo\nbar\nbaz')─┐
│ foo │
└────────────────────────────┘
```

View File

@ -97,7 +97,7 @@ This is an experimental feature that may change in backwards-incompatible ways i
::: :::
```sql ```sql
CREATE LIVE VIEW [IF NOT EXISTS] [db.]table_name [WITH [TIMEOUT [value_in_sec] [AND]] [REFRESH [value_in_sec]]] AS SELECT ... CREATE LIVE VIEW [IF NOT EXISTS] [db.]table_name [WITH REFRESH [value_in_sec]] AS SELECT ...
``` ```
Live views store result of the corresponding [SELECT](../../../sql-reference/statements/select/index.md) query and are updated any time the result of the query changes. Query result as well as partial result needed to combine with new data are stored in memory providing increased performance for repeated queries. Live views can provide push notifications when query result changes using the [WATCH](../../../sql-reference/statements/watch.md) query. Live views store result of the corresponding [SELECT](../../../sql-reference/statements/select/index.md) query and are updated any time the result of the query changes. Query result as well as partial result needed to combine with new data are stored in memory providing increased performance for repeated queries. Live views can provide push notifications when query result changes using the [WATCH](../../../sql-reference/statements/watch.md) query.

View File

@ -205,7 +205,7 @@ The optional keyword `EXTENDED` currently has no effect, it only exists for MySQ
The optional keyword `FULL` causes the output to include the collation, comment and privilege columns. The optional keyword `FULL` causes the output to include the collation, comment and privilege columns.
`SHOW COLUMNS` produces a result table with the following structure: The statement produces a result table with the following structure:
- field - The name of the column (String) - field - The name of the column (String)
- type - The column data type (String) - type - The column data type (String)
- null - If the column data type is Nullable (UInt8) - null - If the column data type is Nullable (UInt8)
@ -272,6 +272,10 @@ SHOW DICTIONARIES FROM db LIKE '%reg%' LIMIT 2
Displays a list of primary and data skipping indexes of a table. Displays a list of primary and data skipping indexes of a table.
This statement mostly exists for compatibility with MySQL. System tables [system.tables](../../operations/system-tables/tables.md) (for
primary keys) and [system.data_skipping_indices](../../operations/system-tables/data_skipping_indices.md) (for data skipping indices)
provide equivalent information but in a fashion more native to ClickHouse.
```sql ```sql
SHOW [EXTENDED] {INDEX | INDEXES | INDICES | KEYS } {FROM | IN} <table> [{FROM | IN} <db>] [WHERE <expr>] [INTO OUTFILE <filename>] [FORMAT <format>] SHOW [EXTENDED] {INDEX | INDEXES | INDICES | KEYS } {FROM | IN} <table> [{FROM | IN} <db>] [WHERE <expr>] [INTO OUTFILE <filename>] [FORMAT <format>]
``` ```
@ -281,22 +285,22 @@ equivalent. If no database is specified, the query assumes the current database
The optional keyword `EXTENDED` currently has no effect, it only exists for MySQL compatibility. The optional keyword `EXTENDED` currently has no effect, it only exists for MySQL compatibility.
`SHOW INDEX` produces a result table with the following structure: The statement produces a result table with the following structure:
- table - The name of the table (String) - table - The name of the table. (String)
- non_unique - 0 if the index cannot contain duplicates, 1 otherwise (UInt8) - non_unique - Always `1` as ClickHouse does not support uniqueness constraints. (UInt8)
- key_name - The name of the index, `PRIMARY` if the index is a primary key index (String) - key_name - The name of the index, `PRIMARY` if the index is a primary key index. (String)
- seq_in_index - Currently unused - seq_in_index - For a primary key index, the position of the column starting from `1`. For a data skipping index: always `1`. (UInt8)
- column_name - Currently unused - column_name - For a primary key index, the name of the column. For a data skipping index: `''` (empty string), see field "expression". (String)
- collation - The sorting of the column in the index, `A` if ascending, `D` if descending, `NULL` if unsorted (Nullable(String)) - collation - The sorting of the column in the index: `A` if ascending, `D` if descending, `NULL` if unsorted. (Nullable(String))
- cardinality - Currently unused - cardinality - An estimation of the index cardinality (number of unique values in the index). Currently always 0. (UInt64)
- sub_part - Currently unused - sub_part - Always `NULL` because ClickHouse does not support index prefixes like MySQL. (Nullable(String))
- packed - Currently unused - packed - Always `NULL` because ClickHouse does not support packed indexes (like MySQL). (Nullable(String))
- null - Currently unused - null - Currently unused
- index_type - The index type, e.g. `primary`, `minmax`, `bloom_filter` etc. (String) - index_type - The index type, e.g. `PRIMARY`, `MINMAX`, `BLOOM_FILTER` etc. (String)
- comment - Currently unused - comment - Additional information about the index, currently always `''` (empty string). (String)
- index_comment - Currently unused - index_comment - `''` (empty string) because indexes in ClickHouse cannot have a `COMMENT` field (like in MySQL). (String)
- visible - If the index is visible to the optimizer, always `YES` (String) - visible - If the index is visible to the optimizer, always `YES`. (String)
- expression - The index expression (String) - expression - For a data skipping index, the index expression. For a primary key index: `''` (empty string). (String)
**Examples** **Examples**
@ -310,11 +314,12 @@ Result:
``` text ``` text
┌─table─┬─non_unique─┬─key_name─┬─seq_in_index─┬─column_name─┬─collation─┬─cardinality─┬─sub_part─┬─packed─┬─null─┬─index_type───┬─comment─┬─index_comment─┬─visible─┬─expression─┐ ┌─table─┬─non_unique─┬─key_name─┬─seq_in_index─┬─column_name─┬─collation─┬─cardinality─┬─sub_part─┬─packed─┬─null─┬─index_type───┬─comment─┬─index_comment─┬─visible─┬─expression─┐
│ tbl │ 0 │ blf_idx │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ bloom_filter │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ YES │ d, b │ │ tbl │ 1 │ blf_idx │ 1 │ 1 │ ᴺᵁᴸᴸ │ 0 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ BLOOM_FILTER │ │ │ YES │ d, b │
│ tbl │ 0 │ mm1_idx │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ minmax │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ YES │ a, c, d │ │ tbl │ 1 │ mm1_idx │ 1 │ 1 │ ᴺᵁᴸᴸ │ 0 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ MINMAX │ │ │ YES │ a, c, d │
│ tbl │ 0 │ mm2_idx │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ minmax │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ YES │ c, d, e │ │ tbl │ 1 │ mm2_idx │ 1 │ 1 │ ᴺᵁᴸᴸ │ 0 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ MINMAX │ │ │ YES │ c, d, e │
│ tbl │ 0 │ PRIMARY │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ A │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ primary │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ YES │ c, a │ │ tbl │ 1 │ PRIMARY │ 1 │ c │ A │ 0 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ PRIMARY │ │ │ YES │ │
│ tbl │ 0 │ set_idx │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ set │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ YES │ e │ │ tbl │ 1 │ PRIMARY │ 2 │ a │ A │ 0 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ PRIMARY │ │ │ YES │ │
│ tbl │ 1 │ set_idx │ 1 │ 1 │ ᴺᵁᴸᴸ │ 0 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ SET │ │ │ YES │ e │
└───────┴────────────┴──────────┴──────────────┴─────────────┴───────────┴─────────────┴──────────┴────────┴──────┴──────────────┴─────────┴───────────────┴─────────┴────────────┘ └───────┴────────────┴──────────┴──────────────┴─────────────┴───────────┴─────────────┴──────────┴────────┴──────┴──────────────┴─────────┴───────────────┴─────────┴────────────┘
``` ```

View File

@ -134,7 +134,7 @@ Multiple path components can have globs. For being processed file must exist and
- `*` — Substitutes any number of any characters except `/` including empty string. - `*` — Substitutes any number of any characters except `/` including empty string.
- `?` — Substitutes any single character. - `?` — Substitutes any single character.
- `{some_string,another_string,yet_another_one}` — Substitutes any of strings `'some_string', 'another_string', 'yet_another_one'`. - `{some_string,another_string,yet_another_one}` — Substitutes any of strings `'some_string', 'another_string', 'yet_another_one'`, including `/`.
- `{N..M}` — Substitutes any number in range from N to M including both borders. - `{N..M}` — Substitutes any number in range from N to M including both borders.
- `**` - Fetches all files inside the folder recursively. - `**` - Fetches all files inside the folder recursively.

View File

@ -68,7 +68,7 @@ $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/
$ rm -rf build $ rm -rf build
$ mkdir build $ mkdir build
$ cd build $ cd build
$ cmake -DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang -DCMAKE_CXX_COMPILER==$(brew --prefix llvm)/bin/clang++ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_JEMALLOC=OFF .. $ cmake -DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang -DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_JEMALLOC=OFF ..
$ cmake -DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang -DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_JEMALLOC=OFF .. $ cmake -DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang -DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_JEMALLOC=OFF ..
$ cmake --build . --config RelWithDebInfo $ cmake --build . --config RelWithDebInfo
$ cd .. $ cd ..

View File

@ -625,7 +625,7 @@ SELECT toDate('2016-12-27') AS date, toYearWeek(date) AS yearWeek0, toYearWeek(d
## age ## age
Вычисляет компонент `unit` разницы между `startdate` и `enddate`. Разница вычисляется с точностью в 1 секунду. Вычисляет компонент `unit` разницы между `startdate` и `enddate`. Разница вычисляется с точностью в 1 микросекунду.
Например, разница между `2021-12-29` и `2022-01-01` 3 дня для единицы `day`, 0 месяцев для единицы `month`, 0 лет для единицы `year`. Например, разница между `2021-12-29` и `2022-01-01` 3 дня для единицы `day`, 0 месяцев для единицы `month`, 0 лет для единицы `year`.
**Синтаксис** **Синтаксис**
@ -639,6 +639,8 @@ age('unit', startdate, enddate, [timezone])
- `unit` — единица измерения времени, в которой будет выражено возвращаемое значение функции. [String](../../sql-reference/data-types/string.md). - `unit` — единица измерения времени, в которой будет выражено возвращаемое значение функции. [String](../../sql-reference/data-types/string.md).
Возможные значения: Возможные значения:
- `microsecond` (возможные сокращения: `us`, `u`)
- `millisecond` (возможные сокращения: `ms`)
- `second` (возможные сокращения: `ss`, `s`) - `second` (возможные сокращения: `ss`, `s`)
- `minute` (возможные сокращения: `mi`, `n`) - `minute` (возможные сокращения: `mi`, `n`)
- `hour` (возможные сокращения: `hh`, `h`) - `hour` (возможные сокращения: `hh`, `h`)
@ -712,6 +714,8 @@ date_diff('unit', startdate, enddate, [timezone])
- `unit` — единица измерения времени, в которой будет выражено возвращаемое значение функции. [String](../../sql-reference/data-types/string.md). - `unit` — единица измерения времени, в которой будет выражено возвращаемое значение функции. [String](../../sql-reference/data-types/string.md).
Возможные значения: Возможные значения:
- `microsecond` (возможные сокращения: `us`, `u`)
- `millisecond` (возможные сокращения: `ms`)
- `second` (возможные сокращения: `ss`, `s`) - `second` (возможные сокращения: `ss`, `s`)
- `minute` (возможные сокращения: `mi`, `n`) - `minute` (возможные сокращения: `mi`, `n`)
- `hour` (возможные сокращения: `hh`, `h`) - `hour` (возможные сокращения: `hh`, `h`)

View File

@ -1124,3 +1124,39 @@ Do Nothing for 2 Minutes 2:00 &nbsp;
Не учитывает язык. То есть, для турецкого языка, результат может быть не совсем верным. Не учитывает язык. То есть, для турецкого языка, результат может быть не совсем верным.
Если длина UTF-8 последовательности байтов различна для верхнего и нижнего регистра кодовой точки, то для этой кодовой точки результат работы может быть некорректным. Если длина UTF-8 последовательности байтов различна для верхнего и нижнего регистра кодовой точки, то для этой кодовой точки результат работы может быть некорректным.
Если строка содержит набор байтов, не являющийся UTF-8, то поведение не определено. Если строка содержит набор байтов, не являющийся UTF-8, то поведение не определено.
## firstLine
Возвращает первую строку в многострочном тексте.
**Синтаксис**
```sql
firstLine(val)
```
**Аргументы**
- `val` - текст для обработки. [String](../data-types/string.md)
**Returned value**
- Первая строка текста или весь текст, если переносы строк отсутствуют.
Тип: [String](../data-types/string.md)
**Пример**
Запрос:
```sql
select firstLine('foo\nbar\nbaz');
```
Результат:
```result
┌─firstLine('foo\nbar\nbaz')─┐
│ foo │
└────────────────────────────┘
```

View File

@ -73,7 +73,7 @@ CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER] [TO[db.]na
Чтобы использовать `LIVE VIEW` и запросы `WATCH`, включите настройку [allow_experimental_live_view](../../../operations/settings/settings.md#allow-experimental-live-view). Чтобы использовать `LIVE VIEW` и запросы `WATCH`, включите настройку [allow_experimental_live_view](../../../operations/settings/settings.md#allow-experimental-live-view).
::: :::
```sql ```sql
CREATE LIVE VIEW [IF NOT EXISTS] [db.]table_name [WITH [TIMEOUT [value_in_sec] [AND]] [REFRESH [value_in_sec]]] AS SELECT ... CREATE LIVE VIEW [IF NOT EXISTS] [db.]table_name [WITH REFRESH [value_in_sec]] AS SELECT ...
``` ```
`LIVE VIEW` хранит результат запроса [SELECT](../../../sql-reference/statements/select/index.md), указанного при создании, и обновляется сразу же при изменении этого результата. Конечный результат запроса и промежуточные данные, из которых формируется результат, хранятся в оперативной памяти, и это обеспечивает высокую скорость обработки для повторяющихся запросов. LIVE-представления могут отправлять push-уведомления при изменении результата исходного запроса `SELECT`. Для этого используйте запрос [WATCH](../../../sql-reference/statements/watch.md). `LIVE VIEW` хранит результат запроса [SELECT](../../../sql-reference/statements/select/index.md), указанного при создании, и обновляется сразу же при изменении этого результата. Конечный результат запроса и промежуточные данные, из которых формируется результат, хранятся в оперативной памяти, и это обеспечивает высокую скорость обработки для повторяющихся запросов. LIVE-представления могут отправлять push-уведомления при изменении результата исходного запроса `SELECT`. Для этого используйте запрос [WATCH](../../../sql-reference/statements/watch.md).

View File

@ -79,7 +79,7 @@ SELECT * FROM file('test.csv', 'CSV', 'column1 UInt32, column2 UInt32, column3 U
- `*` — заменяет любое количество любых символов кроме `/`, включая отсутствие символов. - `*` — заменяет любое количество любых символов кроме `/`, включая отсутствие символов.
- `?` — заменяет ровно один любой символ. - `?` — заменяет ровно один любой символ.
- `{some_string,another_string,yet_another_one}` — заменяет любую из строк `'some_string', 'another_string', 'yet_another_one'`. - `{some_string,another_string,yet_another_one}` — заменяет любую из строк `'some_string', 'another_string', 'yet_another_one'`, причём строка может содержать `/`.
- `{N..M}` — заменяет любое число в интервале от `N` до `M` включительно (может содержать ведущие нули). - `{N..M}` — заменяет любое число в интервале от `N` до `M` включительно (может содержать ведущие нули).
Конструкция с `{}` аналогична табличной функции [remote](remote.md). Конструкция с `{}` аналогична табличной функции [remote](remote.md).

View File

@ -643,6 +643,8 @@ date_diff('unit', startdate, enddate, [timezone])
- `unit``value`对应的时间单位。类型为[String](../../sql-reference/data-types/string.md)。 - `unit``value`对应的时间单位。类型为[String](../../sql-reference/data-types/string.md)。
可能的值: 可能的值:
- `microsecond`
- `millisecond`
- `second` - `second`
- `minute` - `minute`
- `hour` - `hour`

View File

@ -72,7 +72,7 @@ ClickHouse 中的物化视图更像是插入触发器。 如果视图查询中
使用[allow_experimental_live_view](../../../operations/settings/settings.md#allow-experimental-live-view)设置启用实时视图和`WATCH`查询的使用。 输入命令`set allow_experimental_live_view = 1`。 使用[allow_experimental_live_view](../../../operations/settings/settings.md#allow-experimental-live-view)设置启用实时视图和`WATCH`查询的使用。 输入命令`set allow_experimental_live_view = 1`。
```sql ```sql
CREATE LIVE VIEW [IF NOT EXISTS] [db.]table_name [WITH [TIMEOUT [value_in_sec] [AND]] [REFRESH [value_in_sec]]] AS SELECT ... CREATE LIVE VIEW [IF NOT EXISTS] [db.]table_name [WITH REFRESH [value_in_sec]] AS SELECT ...
``` ```
实时视图存储相应[SELECT](../../../sql-reference/statements/select/index.md)查询的结果,并在查询结果更改时随时更新。 查询结果以及与新数据结合所需的部分结果存储在内存中,为重复查询提供更高的性能。当使用[WATCH](../../../sql-reference/statements/watch.md)查询更改查询结果时,实时视图可以提供推送通知。 实时视图存储相应[SELECT](../../../sql-reference/statements/select/index.md)查询的结果,并在查询结果更改时随时更新。 查询结果以及与新数据结合所需的部分结果存储在内存中,为重复查询提供更高的性能。当使用[WATCH](../../../sql-reference/statements/watch.md)查询更改查询结果时,实时视图可以提供推送通知。

View File

@ -1173,12 +1173,12 @@ void Client::processOptions(const OptionsDescription & options_description,
{ {
String traceparent = options["opentelemetry-traceparent"].as<std::string>(); String traceparent = options["opentelemetry-traceparent"].as<std::string>();
String error; String error;
if (!global_context->getClientInfo().client_trace_context.parseTraceparentHeader(traceparent, error)) if (!global_context->getClientTraceContext().parseTraceparentHeader(traceparent, error))
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot parse OpenTelemetry traceparent '{}': {}", traceparent, error); throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot parse OpenTelemetry traceparent '{}': {}", traceparent, error);
} }
if (options.count("opentelemetry-tracestate")) if (options.count("opentelemetry-tracestate"))
global_context->getClientInfo().client_trace_context.tracestate = options["opentelemetry-tracestate"].as<std::string>(); global_context->getClientTraceContext().tracestate = options["opentelemetry-tracestate"].as<std::string>();
} }
@ -1238,10 +1238,9 @@ void Client::processConfig()
global_context->getSettingsRef().max_insert_block_size); global_context->getSettingsRef().max_insert_block_size);
} }
ClientInfo & client_info = global_context->getClientInfo(); global_context->setQueryKindInitial();
client_info.setInitialQuery(); global_context->setQuotaClientKey(config().getString("quota_key", ""));
client_info.quota_key = config().getString("quota_key", ""); global_context->setQueryKind(query_kind);
client_info.query_kind = query_kind;
} }

View File

@ -34,6 +34,8 @@ add_dependencies(clickhouse-keeper-lib clickhouse_keeper_configs)
if (BUILD_STANDALONE_KEEPER) if (BUILD_STANDALONE_KEEPER)
# Straight list of all required sources # Straight list of all required sources
set(CLICKHOUSE_KEEPER_STANDALONE_SOURCES set(CLICKHOUSE_KEEPER_STANDALONE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/KeeperReconfiguration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/RaftServerConfig.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/ACLMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/ACLMap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/Changelog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/Changelog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/CoordinationSettings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../src/Coordination/CoordinationSettings.cpp

View File

@ -737,9 +737,8 @@ void LocalServer::processConfig()
for (const auto & [key, value] : prompt_substitutions) for (const auto & [key, value] : prompt_substitutions)
boost::replace_all(prompt_by_server_display_name, "{" + key + "}", value); boost::replace_all(prompt_by_server_display_name, "{" + key + "}", value);
ClientInfo & client_info = global_context->getClientInfo(); global_context->setQueryKindInitial();
client_info.setInitialQuery(); global_context->setQueryKind(query_kind);
client_info.query_kind = query_kind;
} }

View File

@ -887,6 +887,7 @@ try
#endif #endif
global_context->setRemoteHostFilter(config()); global_context->setRemoteHostFilter(config());
global_context->setHTTPHeaderFilter(config());
std::string path_str = getCanonicalPath(config().getString("path", DBMS_DEFAULT_PATH)); std::string path_str = getCanonicalPath(config().getString("path", DBMS_DEFAULT_PATH));
fs::path path = path_str; fs::path path = path_str;
@ -1200,6 +1201,7 @@ try
} }
global_context->setRemoteHostFilter(*config); global_context->setRemoteHostFilter(*config);
global_context->setHTTPHeaderFilter(*config);
global_context->setMaxTableSizeToDrop(server_settings_.max_table_size_to_drop); global_context->setMaxTableSizeToDrop(server_settings_.max_table_size_to_drop);
global_context->setMaxPartitionSizeToDrop(server_settings_.max_partition_size_to_drop); global_context->setMaxPartitionSizeToDrop(server_settings_.max_partition_size_to_drop);
@ -1599,13 +1601,7 @@ try
global_context->setSystemZooKeeperLogAfterInitializationIfNeeded(); global_context->setSystemZooKeeperLogAfterInitializationIfNeeded();
/// Build loggers before tables startup to make log messages from tables /// Build loggers before tables startup to make log messages from tables
/// attach available in system.text_log /// attach available in system.text_log
{ buildLoggers(config(), logger());
String level_str = config().getString("text_log.level", "");
int level = level_str.empty() ? INT_MAX : Poco::Logger::parseLevel(level_str);
setTextLog(global_context->getTextLog(), level);
buildLoggers(config(), logger());
}
/// After the system database is created, attach virtual system tables (in addition to query_log and part_log) /// After the system database is created, attach virtual system tables (in addition to query_log and part_log)
attachSystemTablesServer(global_context, *database_catalog.getSystemDatabase(), has_zookeeper); attachSystemTablesServer(global_context, *database_catalog.getSystemDatabase(), has_zookeeper);
attachInformationSchema(global_context, *database_catalog.getDatabase(DatabaseCatalog::INFORMATION_SCHEMA)); attachInformationSchema(global_context, *database_catalog.getDatabase(DatabaseCatalog::INFORMATION_SCHEMA));

View File

@ -866,6 +866,14 @@
--> -->
<!--</remote_url_allow_hosts>--> <!--</remote_url_allow_hosts>-->
<!-- The list of HTTP headers forbidden to use in HTTP-related storage engines and table functions.
If this section is not present in configuration, all headers are allowed.
-->
<!-- <http_forbid_headers>
<header>exact_header</header>
<header_regexp>(?i)(case_insensitive_header)</header_regexp>
</http_forbid_headers> -->
<!-- If element has 'incl' attribute, then for it's value will be used corresponding substitution from another file. <!-- If element has 'incl' attribute, then for it's value will be used corresponding substitution from another file.
By default, path to file with substitutions is /etc/metrika.xml. It could be changed in config in 'include_from' element. By default, path to file with substitutions is /etc/metrika.xml. It could be changed in config in 'include_from' element.
Values for substitutions are specified in /clickhouse/name_of_substitution elements in that file. Values for substitutions are specified in /clickhouse/name_of_substitution elements in that file.

View File

@ -88,3 +88,4 @@ endfunction()
add_rust_subdirectory (BLAKE3) add_rust_subdirectory (BLAKE3)
add_rust_subdirectory (skim) add_rust_subdirectory (skim)
add_rust_subdirectory (prql)

3
rust/prql/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
clickhouse_import_crate(MANIFEST_PATH Cargo.toml)
target_include_directories(_ch_rust_prql INTERFACE include)
add_library(ch_rust::prql ALIAS _ch_rust_prql)

569
rust/prql/Cargo.lock generated Normal file
View File

@ -0,0 +1,569 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "_ch_rust_prql"
version = "0.1.0"
dependencies = [
"prql-compiler",
"serde_json",
]
[[package]]
name = "addr2line"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
dependencies = [
"backtrace",
]
[[package]]
name = "ariadne"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "367fd0ad87307588d087544707bc5fbf4805ded96c7db922b70d368fa1cb5702"
dependencies = [
"unicode-width",
"yansi",
]
[[package]]
name = "backtrace"
version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d"
dependencies = [
"hashbrown 0.12.3",
"stacker",
]
[[package]]
name = "csv"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "enum-as-inner"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "equivalent"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "indexmap"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "object"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "proc-macro2"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
dependencies = [
"unicode-ident",
]
[[package]]
name = "prql-compiler"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c99b52154002ac7f286dd2293c2f8d4e30526c1d396b14deef5ada1deef3c9ff"
dependencies = [
"anyhow",
"ariadne",
"chumsky",
"csv",
"enum-as-inner",
"itertools",
"lazy_static",
"log",
"once_cell",
"regex",
"semver",
"serde",
"serde_json",
"serde_yaml",
"sqlformat",
"sqlparser",
"strum",
"strum_macros",
]
[[package]]
name = "psm"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
dependencies = [
"cc",
]
[[package]]
name = "quote"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89089e897c013b3deb627116ae56a6955a72b8bed395c9526af31c9fe528b484"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa250384981ea14565685dea16a9ccc4d1c541a13f82b9c168572264d1df8c56"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustversion"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
[[package]]
name = "ryu"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
[[package]]
name = "semver"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.166"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.166"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.23",
]
[[package]]
name = "serde_json"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "452e67b9c20c37fa79df53201dc03839651086ed9bbe92b3ca585ca9fdaa7d85"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sqlformat"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e"
dependencies = [
"itertools",
"nom",
"unicode_categories",
]
[[package]]
name = "sqlparser"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "355dc4d4b6207ca8a3434fc587db0a8016130a574dbcdbfb93d7f7b5bc5b211a"
dependencies = [
"log",
"serde",
]
[[package]]
name = "stacker"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"winapi",
]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unsafe-libyaml"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

20
rust/prql/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "_ch_rust_prql"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
prql-compiler = "0.8.1"
serde_json = "1.0"
[lib]
crate-type = ["staticlib"]
[profile.release]
debug = true
[profile.release-thinlto]
inherits = "release"
lto = true

18
rust/prql/include/prql.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
extern "C" {
/// Converts a PRQL query to an SQL query.
/// @param query is a pointer to the beginning of the PRQL query.
/// @param size is the size of the PRQL query.
/// @param out is a pointer to a uint8_t pointer which will be set to the beginning of the null terminated SQL query or the error message.
/// @param out_size is the size of the string pointed by `out`.
/// @returns zero in case of success, non-zero in case of failure.
int64_t prql_to_sql(const uint8_t * query, uint64_t size, uint8_t ** out, uint64_t * out_size);
/// Frees the passed in pointer which's memory was allocated by Rust allocators previously.
void prql_free_pointer(uint8_t * ptr_to_free);
} // extern "C"

56
rust/prql/src/lib.rs Normal file
View File

@ -0,0 +1,56 @@
use prql_compiler::sql::Dialect;
use prql_compiler::{Options, Target};
use std::ffi::{c_char, CString};
use std::slice;
fn set_output(result: String, out: *mut *mut u8, out_size: *mut u64) {
assert!(!out_size.is_null());
let out_size_ptr = unsafe { &mut *out_size };
*out_size_ptr = (result.len() + 1).try_into().unwrap();
assert!(!out.is_null());
let out_ptr = unsafe { &mut *out };
*out_ptr = CString::new(result).unwrap().into_raw() as *mut u8;
}
#[no_mangle]
pub unsafe extern "C" fn prql_to_sql(
query: *const u8,
size: u64,
out: *mut *mut u8,
out_size: *mut u64,
) -> i64 {
let query_vec = unsafe { slice::from_raw_parts(query, size.try_into().unwrap()) }.to_vec();
let maybe_prql_query = String::from_utf8(query_vec);
if maybe_prql_query.is_err() {
set_output(
String::from("The PRQL query must be UTF-8 encoded!"),
out,
out_size,
);
return 1;
}
let prql_query = maybe_prql_query.unwrap();
let opts = &Options {
format: true,
target: Target::Sql(Some(Dialect::ClickHouse)),
signature_comment: false,
color: false,
};
let (is_err, res) = match prql_compiler::compile(&prql_query, &opts) {
Ok(sql_str) => (false, sql_str),
Err(err) => (true, err.to_string()),
};
set_output(res, out, out_size);
match is_err {
true => 1,
false => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn prql_free_pointer(ptr_to_free: *mut u8) {
std::mem::drop(CString::from_raw(ptr_to_free as *mut c_char));
}

204
rust/skim/Cargo.lock generated
View File

@ -42,17 +42,6 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -104,31 +93,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clap"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "codespan-reporting" name = "codespan-reporting"
version = "0.11.1" version = "0.11.1"
@ -214,9 +178,9 @@ dependencies = [
[[package]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.97" version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88abab2f5abbe4c56e8f1fb431b784d710b709888f35755a160e62e33fe38e8" checksum = "5032837c1384de3708043de9d4e97bb91290faca6c16529a28aa340592a78166"
dependencies = [ dependencies = [
"cc", "cc",
"cxxbridge-flags", "cxxbridge-flags",
@ -226,9 +190,9 @@ dependencies = [
[[package]] [[package]]
name = "cxx-build" name = "cxx-build"
version = "1.0.97" version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c0c11acd0e63bae27dcd2afced407063312771212b7a823b4fd72d633be30fb" checksum = "51368b3d0dbf356e10fcbfd455a038503a105ee556f7ee79b6bb8c53a7247456"
dependencies = [ dependencies = [
"cc", "cc",
"codespan-reporting", "codespan-reporting",
@ -236,24 +200,24 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"scratch", "scratch",
"syn 2.0.23", "syn 2.0.26",
] ]
[[package]] [[package]]
name = "cxxbridge-flags" name = "cxxbridge-flags"
version = "1.0.97" version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3816ed957c008ccd4728485511e3d9aaf7db419aa321e3d2c5a2f3411e36c8" checksum = "0d9062157072e4aafc8e56ceaf8325ce850c5ae37578c852a0d4de2cecdded13"
[[package]] [[package]]
name = "cxxbridge-macro" name = "cxxbridge-macro"
version = "1.0.97" version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26acccf6f445af85ea056362561a24ef56cdc15fcc685f03aec50b9c702cb6d" checksum = "cf01e8a540f5a4e0f284595834f81cf88572f244b768f051724537afa99a2545"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.23", "syn 2.0.26",
] ]
[[package]] [[package]]
@ -359,19 +323,6 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "env_logger"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -398,32 +349,11 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
@ -454,16 +384,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.64" version = "0.3.64"
@ -487,9 +407,9 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]] [[package]]
name = "link-cplusplus" name = "link-cplusplus"
version = "1.0.8" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@ -564,7 +484,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [ dependencies = [
"hermit-abi 0.3.1", "hermit-abi",
"libc", "libc",
] ]
@ -574,12 +494,6 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "os_str_bytes"
version = "6.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
version = "0.1.0" version = "0.1.0"
@ -588,18 +502,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.63" version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.29" version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -648,9 +562,21 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.8.4" version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -659,39 +585,33 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.2" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.12" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "scratch" name = "scratch"
version = "1.0.5" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.164" version = "1.0.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]] [[package]]
name = "skim" name = "skim"
@ -699,23 +619,19 @@ version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d28de0a6cb2cdd83a076f1de9d965b973ae08b244df1aa70b432946dda0f32" checksum = "e5d28de0a6cb2cdd83a076f1de9d965b973ae08b244df1aa70b432946dda0f32"
dependencies = [ dependencies = [
"atty",
"beef", "beef",
"bitflags", "bitflags",
"chrono", "chrono",
"clap",
"crossbeam", "crossbeam",
"defer-drop", "defer-drop",
"derive_builder", "derive_builder",
"env_logger",
"fuzzy-matcher", "fuzzy-matcher",
"lazy_static", "lazy_static",
"log", "log",
"nix 0.25.1", "nix 0.25.1",
"rayon", "rayon",
"regex", "regex",
"shlex", "time 0.3.23",
"time 0.3.22",
"timer", "timer",
"tuikit", "tuikit",
"unicode-width", "unicode-width",
@ -741,9 +657,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.23" version = "2.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -770,30 +686,24 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "textwrap"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.40" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.40" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.23", "syn 2.0.26",
] ]
[[package]] [[package]]
@ -819,9 +729,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.22" version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
dependencies = [ dependencies = [
"serde", "serde",
"time-core", "time-core",
@ -858,9 +768,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.9" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
@ -928,7 +838,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.23", "syn 2.0.26",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -950,7 +860,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.23", "syn 2.0.26",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]

View File

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
skim = "0.10.2" skim = { version = "0.10.2", default-features = false }
cxx = "1.0.83" cxx = "1.0.83"
term = "0.7.0" term = "0.7.0"

View File

@ -76,11 +76,13 @@ public:
auto x = cache.get(params); auto x = cache.get(params);
if (x) if (x)
{ {
if ((*x)->tryGetUser()) if ((*x)->getUserID() && !(*x)->tryGetUser())
cache.remove(params); /// The user has been dropped while it was in the cache.
else
return *x; return *x;
/// No user, probably the user has been dropped while it was in the cache.
cache.remove(params);
} }
/// TODO: There is no need to keep the `ContextAccessCache::mutex` locked while we're calculating access rights.
auto res = std::make_shared<ContextAccess>(access_control, params); auto res = std::make_shared<ContextAccess>(access_control, params);
res->initialize(); res->initialize();
cache.add(params, res); cache.add(params, res);
@ -713,35 +715,6 @@ int AccessControl::getBcryptWorkfactor() const
} }
std::shared_ptr<const ContextAccess> AccessControl::getContextAccess(
const UUID & user_id,
const std::vector<UUID> & current_roles,
bool use_default_roles,
const Settings & settings,
const String & current_database,
const ClientInfo & client_info) const
{
ContextAccessParams params;
params.user_id = user_id;
params.current_roles.insert(current_roles.begin(), current_roles.end());
params.use_default_roles = use_default_roles;
params.current_database = current_database;
params.readonly = settings.readonly;
params.allow_ddl = settings.allow_ddl;
params.allow_introspection = settings.allow_introspection_functions;
params.interface = client_info.interface;
params.http_method = client_info.http_method;
params.address = client_info.current_address.host();
params.quota_key = client_info.quota_key;
/// Extract the last entry from comma separated list of X-Forwarded-For addresses.
/// Only the last proxy can be trusted (if any).
params.forwarded_address = client_info.getLastForwardedFor();
return getContextAccess(params);
}
std::shared_ptr<const ContextAccess> AccessControl::getContextAccess(const ContextAccessParams & params) const std::shared_ptr<const ContextAccess> AccessControl::getContextAccess(const ContextAccessParams & params) const
{ {
return context_access_cache->getContextAccess(params); return context_access_cache->getContextAccess(params);

View File

@ -25,7 +25,7 @@ namespace Poco
namespace DB namespace DB
{ {
class ContextAccess; class ContextAccess;
struct ContextAccessParams; class ContextAccessParams;
struct User; struct User;
using UserPtr = std::shared_ptr<const User>; using UserPtr = std::shared_ptr<const User>;
class EnabledRoles; class EnabledRoles;
@ -181,14 +181,6 @@ public:
void setSettingsConstraintsReplacePrevious(bool enable) { settings_constraints_replace_previous = enable; } void setSettingsConstraintsReplacePrevious(bool enable) { settings_constraints_replace_previous = enable; }
bool doesSettingsConstraintsReplacePrevious() const { return settings_constraints_replace_previous; } bool doesSettingsConstraintsReplacePrevious() const { return settings_constraints_replace_previous; }
std::shared_ptr<const ContextAccess> getContextAccess(
const UUID & user_id,
const std::vector<UUID> & current_roles,
bool use_default_roles,
const Settings & settings,
const String & current_database,
const ClientInfo & client_info) const;
std::shared_ptr<const ContextAccess> getContextAccess(const ContextAccessParams & params) const; std::shared_ptr<const ContextAccess> getContextAccess(const ContextAccessParams & params) const;
std::shared_ptr<const EnabledRoles> getEnabledRoles( std::shared_ptr<const EnabledRoles> getEnabledRoles(

View File

@ -10,6 +10,7 @@
#include <Access/EnabledSettings.h> #include <Access/EnabledSettings.h>
#include <Access/SettingsProfilesInfo.h> #include <Access/SettingsProfilesInfo.h>
#include <Interpreters/DatabaseCatalog.h> #include <Interpreters/DatabaseCatalog.h>
#include <Interpreters/Context.h>
#include <Common/Exception.h> #include <Common/Exception.h>
#include <Common/quoteString.h> #include <Common/quoteString.h>
#include <Core/Settings.h> #include <Core/Settings.h>
@ -221,6 +222,12 @@ namespace
} }
std::shared_ptr<const ContextAccess> ContextAccess::fromContext(const ContextPtr & context)
{
return context->getAccess();
}
ContextAccess::ContextAccess(const AccessControl & access_control_, const Params & params_) ContextAccess::ContextAccess(const AccessControl & access_control_, const Params & params_)
: access_control(&access_control_) : access_control(&access_control_)
, params(params_) , params(params_)
@ -228,48 +235,44 @@ ContextAccess::ContextAccess(const AccessControl & access_control_, const Params
} }
ContextAccess::ContextAccess(FullAccess) ContextAccess::~ContextAccess() = default;
: is_full_access(true), access(std::make_shared<AccessRights>(AccessRights::getFullAccess())), access_with_implicit(access)
{
}
ContextAccess::~ContextAccess()
{
enabled_settings.reset();
enabled_quota.reset();
enabled_row_policies.reset();
access_with_implicit.reset();
access.reset();
roles_info.reset();
subscription_for_roles_changes.reset();
enabled_roles.reset();
subscription_for_user_change.reset();
user.reset();
}
void ContextAccess::initialize() void ContextAccess::initialize()
{ {
std::lock_guard lock{mutex}; std::lock_guard lock{mutex};
subscription_for_user_change = access_control->subscribeForChanges(
*params.user_id, [weak_ptr = weak_from_this()](const UUID &, const AccessEntityPtr & entity) if (params.full_access)
{ {
auto ptr = weak_ptr.lock(); access = std::make_shared<AccessRights>(AccessRights::getFullAccess());
if (!ptr) access_with_implicit = access;
return; return;
UserPtr changed_user = entity ? typeid_cast<UserPtr>(entity) : nullptr; }
std::lock_guard lock2{ptr->mutex};
ptr->setUser(changed_user); if (!params.user_id)
}); throw Exception(ErrorCodes::LOGICAL_ERROR, "No user in current context, it's a bug");
setUser(access_control->read<User>(*params.user_id));
subscription_for_user_change = access_control->subscribeForChanges(
*params.user_id,
[weak_ptr = weak_from_this()](const UUID &, const AccessEntityPtr & entity)
{
auto ptr = weak_ptr.lock();
if (!ptr)
return;
UserPtr changed_user = entity ? typeid_cast<UserPtr>(entity) : nullptr;
std::lock_guard lock2{ptr->mutex};
ptr->setUser(changed_user);
});
setUser(access_control->read<User>(*params.user_id));
} }
void ContextAccess::setUser(const UserPtr & user_) const void ContextAccess::setUser(const UserPtr & user_) const
{ {
user = user_; user = user_;
if (!user)
if (!user_)
{ {
/// User has been dropped. /// User has been dropped.
user_was_dropped = true; user_was_dropped = true;
@ -280,6 +283,7 @@ void ContextAccess::setUser(const UserPtr & user_) const
enabled_roles = nullptr; enabled_roles = nullptr;
roles_info = nullptr; roles_info = nullptr;
enabled_row_policies = nullptr; enabled_row_policies = nullptr;
row_policies_of_initial_user = nullptr;
enabled_quota = nullptr; enabled_quota = nullptr;
enabled_settings = nullptr; enabled_settings = nullptr;
return; return;
@ -294,10 +298,10 @@ void ContextAccess::setUser(const UserPtr & user_) const
current_roles = user->granted_roles.findGranted(user->default_roles); current_roles = user->granted_roles.findGranted(user->default_roles);
current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(user->default_roles); current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(user->default_roles);
} }
else else if (params.current_roles)
{ {
current_roles = user->granted_roles.findGranted(params.current_roles); current_roles = user->granted_roles.findGranted(*params.current_roles);
current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(params.current_roles); current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(*params.current_roles);
} }
subscription_for_roles_changes.reset(); subscription_for_roles_changes.reset();
@ -309,6 +313,11 @@ void ContextAccess::setUser(const UserPtr & user_) const
}); });
setRolesInfo(enabled_roles->getRolesInfo()); setRolesInfo(enabled_roles->getRolesInfo());
std::optional<UUID> initial_user_id;
if (!params.initial_user.empty())
initial_user_id = access_control->find<User>(params.initial_user);
row_policies_of_initial_user = initial_user_id ? access_control->tryGetDefaultRowPolicies(*initial_user_id) : nullptr;
} }
@ -316,12 +325,15 @@ void ContextAccess::setRolesInfo(const std::shared_ptr<const EnabledRolesInfo> &
{ {
assert(roles_info_); assert(roles_info_);
roles_info = roles_info_; roles_info = roles_info_;
enabled_row_policies = access_control->getEnabledRowPolicies(
*params.user_id, roles_info->enabled_roles); enabled_row_policies = access_control->getEnabledRowPolicies(*params.user_id, roles_info->enabled_roles);
enabled_quota = access_control->getEnabledQuota( enabled_quota = access_control->getEnabledQuota(
*params.user_id, user_name, roles_info->enabled_roles, params.address, params.forwarded_address, params.quota_key); *params.user_id, user_name, roles_info->enabled_roles, params.address, params.forwarded_address, params.quota_key);
enabled_settings = access_control->getEnabledSettings( enabled_settings = access_control->getEnabledSettings(
*params.user_id, user->settings, roles_info->enabled_roles, roles_info->settings_from_enabled_roles); *params.user_id, user->settings, roles_info->enabled_roles, roles_info->settings_from_enabled_roles);
calculateAccessRights(); calculateAccessRights();
} }
@ -381,21 +393,24 @@ std::shared_ptr<const EnabledRolesInfo> ContextAccess::getRolesInfo() const
return no_roles; return no_roles;
} }
std::shared_ptr<const EnabledRowPolicies> ContextAccess::getEnabledRowPolicies() const RowPolicyFilterPtr ContextAccess::getRowPolicyFilter(const String & database, const String & table_name, RowPolicyFilterType filter_type) const
{ {
std::lock_guard lock{mutex}; std::lock_guard lock{mutex};
if (enabled_row_policies)
return enabled_row_policies;
static const auto no_row_policies = std::make_shared<EnabledRowPolicies>();
return no_row_policies;
}
RowPolicyFilterPtr ContextAccess::getRowPolicyFilter(const String & database, const String & table_name, RowPolicyFilterType filter_type, RowPolicyFilterPtr combine_with_filter) const RowPolicyFilterPtr filter;
{
std::lock_guard lock{mutex};
if (enabled_row_policies) if (enabled_row_policies)
return enabled_row_policies->getFilter(database, table_name, filter_type, combine_with_filter); filter = enabled_row_policies->getFilter(database, table_name, filter_type);
return combine_with_filter;
if (row_policies_of_initial_user)
{
/// Find and set extra row policies to be used based on `client_info.initial_user`, if the initial user exists.
/// TODO: we need a better solution here. It seems we should pass the initial row policy
/// because a shard is allowed to not have the initial user or it might be another user
/// with the same name.
filter = row_policies_of_initial_user->getFilter(database, table_name, filter_type, filter);
}
return filter;
} }
std::shared_ptr<const EnabledQuota> ContextAccess::getQuota() const std::shared_ptr<const EnabledQuota> ContextAccess::getQuota() const
@ -417,14 +432,6 @@ std::optional<QuotaUsage> ContextAccess::getQuotaUsage() const
} }
std::shared_ptr<const ContextAccess> ContextAccess::getFullAccess()
{
static const std::shared_ptr<const ContextAccess> res =
[] { return std::shared_ptr<ContextAccess>(new ContextAccess{kFullAccess}); }();
return res;
}
SettingsChanges ContextAccess::getDefaultSettings() const SettingsChanges ContextAccess::getDefaultSettings() const
{ {
std::lock_guard lock{mutex}; std::lock_guard lock{mutex};
@ -478,7 +485,7 @@ bool ContextAccess::checkAccessImplHelper(AccessFlags flags, const Args &... arg
throw Exception(ErrorCodes::UNKNOWN_USER, "{}: User has been dropped", getUserName()); throw Exception(ErrorCodes::UNKNOWN_USER, "{}: User has been dropped", getUserName());
} }
if (is_full_access) if (params.full_access)
return true; return true;
auto access_granted = [&] auto access_granted = [&]
@ -706,7 +713,7 @@ bool ContextAccess::checkAdminOptionImplHelper(const Container & role_ids, const
return false; return false;
}; };
if (is_full_access) if (params.full_access)
return true; return true;
if (user_was_dropped) if (user_was_dropped)
@ -806,7 +813,7 @@ void ContextAccess::checkAdminOption(const std::vector<UUID> & role_ids, const s
void ContextAccess::checkGranteeIsAllowed(const UUID & grantee_id, const IAccessEntity & grantee) const void ContextAccess::checkGranteeIsAllowed(const UUID & grantee_id, const IAccessEntity & grantee) const
{ {
if (is_full_access) if (params.full_access)
return; return;
auto current_user = getUser(); auto current_user = getUser();
@ -816,7 +823,7 @@ void ContextAccess::checkGranteeIsAllowed(const UUID & grantee_id, const IAccess
void ContextAccess::checkGranteesAreAllowed(const std::vector<UUID> & grantee_ids) const void ContextAccess::checkGranteesAreAllowed(const std::vector<UUID> & grantee_ids) const
{ {
if (is_full_access) if (params.full_access)
return; return;
auto current_user = getUser(); auto current_user = getUser();

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <Access/AccessRights.h> #include <Access/AccessRights.h>
#include <Access/ContextAccessParams.h>
#include <Access/EnabledRowPolicies.h> #include <Access/EnabledRowPolicies.h>
#include <Interpreters/ClientInfo.h> #include <Interpreters/ClientInfo.h>
#include <Core/UUID.h> #include <Core/UUID.h>
@ -30,47 +31,18 @@ class AccessControl;
class IAST; class IAST;
struct IAccessEntity; struct IAccessEntity;
using ASTPtr = std::shared_ptr<IAST>; using ASTPtr = std::shared_ptr<IAST>;
class Context;
using ContextPtr = std::shared_ptr<const Context>;
struct ContextAccessParams
{
std::optional<UUID> user_id;
boost::container::flat_set<UUID> current_roles;
bool use_default_roles = false;
UInt64 readonly = 0;
bool allow_ddl = false;
bool allow_introspection = false;
String current_database;
ClientInfo::Interface interface = ClientInfo::Interface::TCP;
ClientInfo::HTTPMethod http_method = ClientInfo::HTTPMethod::UNKNOWN;
Poco::Net::IPAddress address;
String forwarded_address;
String quota_key;
auto toTuple() const
{
return std::tie(
user_id, current_roles, use_default_roles, readonly, allow_ddl, allow_introspection,
current_database, interface, http_method, address, forwarded_address, quota_key);
}
friend bool operator ==(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return lhs.toTuple() == rhs.toTuple(); }
friend bool operator !=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(lhs == rhs); }
friend bool operator <(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return lhs.toTuple() < rhs.toTuple(); }
friend bool operator >(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return rhs < lhs; }
friend bool operator <=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(rhs < lhs); }
friend bool operator >=(const ContextAccessParams & lhs, const ContextAccessParams & rhs) { return !(lhs < rhs); }
};
class ContextAccess : public std::enable_shared_from_this<ContextAccess> class ContextAccess : public std::enable_shared_from_this<ContextAccess>
{ {
public: public:
static std::shared_ptr<const ContextAccess> fromContext(const ContextPtr & context);
using Params = ContextAccessParams; using Params = ContextAccessParams;
const Params & getParams() const { return params; } const Params & getParams() const { return params; }
ContextAccess(const AccessControl & access_control_, const Params & params_);
/// Returns the current user. Throws if user is nullptr. /// Returns the current user. Throws if user is nullptr.
UserPtr getUser() const; UserPtr getUser() const;
/// Same as above, but can return nullptr. /// Same as above, but can return nullptr.
@ -81,12 +53,9 @@ public:
/// Returns information about current and enabled roles. /// Returns information about current and enabled roles.
std::shared_ptr<const EnabledRolesInfo> getRolesInfo() const; std::shared_ptr<const EnabledRolesInfo> getRolesInfo() const;
/// Returns information about enabled row policies.
std::shared_ptr<const EnabledRowPolicies> getEnabledRowPolicies() const;
/// Returns the row policy filter for a specified table. /// Returns the row policy filter for a specified table.
/// The function returns nullptr if there is no filter to apply. /// The function returns nullptr if there is no filter to apply.
RowPolicyFilterPtr getRowPolicyFilter(const String & database, const String & table_name, RowPolicyFilterType filter_type, RowPolicyFilterPtr combine_with_filter = {}) const; RowPolicyFilterPtr getRowPolicyFilter(const String & database, const String & table_name, RowPolicyFilterType filter_type) const;
/// Returns the quota to track resource consumption. /// Returns the quota to track resource consumption.
std::shared_ptr<const EnabledQuota> getQuota() const; std::shared_ptr<const EnabledQuota> getQuota() const;
@ -161,22 +130,12 @@ public:
/// Checks if grantees are allowed for the current user, throws an exception if not. /// Checks if grantees are allowed for the current user, throws an exception if not.
void checkGranteesAreAllowed(const std::vector<UUID> & grantee_ids) const; void checkGranteesAreAllowed(const std::vector<UUID> & grantee_ids) const;
/// Makes an instance of ContextAccess which provides full access to everything ContextAccess(const AccessControl & access_control_, const Params & params_);
/// without any limitations. This is used for the global context.
static std::shared_ptr<const ContextAccess> getFullAccess();
~ContextAccess(); ~ContextAccess();
private: private:
friend class AccessControl; friend class AccessControl;
struct FullAccess {};
static const FullAccess kFullAccess;
/// Makes an instance of ContextAccess which provides full access to everything
/// without any limitations. This is used for the global context.
explicit ContextAccess(FullAccess);
void initialize(); void initialize();
void setUser(const UserPtr & user_) const TSA_REQUIRES(mutex); void setUser(const UserPtr & user_) const TSA_REQUIRES(mutex);
void setRolesInfo(const std::shared_ptr<const EnabledRolesInfo> & roles_info_) const TSA_REQUIRES(mutex); void setRolesInfo(const std::shared_ptr<const EnabledRolesInfo> & roles_info_) const TSA_REQUIRES(mutex);
@ -223,7 +182,6 @@ private:
const AccessControl * access_control = nullptr; const AccessControl * access_control = nullptr;
const Params params; const Params params;
const bool is_full_access = false;
mutable std::atomic<bool> user_was_dropped = false; mutable std::atomic<bool> user_was_dropped = false;
mutable std::atomic<Poco::Logger *> trace_log = nullptr; mutable std::atomic<Poco::Logger *> trace_log = nullptr;
@ -237,6 +195,7 @@ private:
mutable std::shared_ptr<const AccessRights> access TSA_GUARDED_BY(mutex); mutable std::shared_ptr<const AccessRights> access TSA_GUARDED_BY(mutex);
mutable std::shared_ptr<const AccessRights> access_with_implicit TSA_GUARDED_BY(mutex); mutable std::shared_ptr<const AccessRights> access_with_implicit TSA_GUARDED_BY(mutex);
mutable std::shared_ptr<const EnabledRowPolicies> enabled_row_policies TSA_GUARDED_BY(mutex); mutable std::shared_ptr<const EnabledRowPolicies> enabled_row_policies TSA_GUARDED_BY(mutex);
mutable std::shared_ptr<const EnabledRowPolicies> row_policies_of_initial_user TSA_GUARDED_BY(mutex);
mutable std::shared_ptr<const EnabledQuota> enabled_quota TSA_GUARDED_BY(mutex); mutable std::shared_ptr<const EnabledQuota> enabled_quota TSA_GUARDED_BY(mutex);
mutable std::shared_ptr<const EnabledSettings> enabled_settings TSA_GUARDED_BY(mutex); mutable std::shared_ptr<const EnabledSettings> enabled_settings TSA_GUARDED_BY(mutex);

View File

@ -0,0 +1,177 @@
#include <Access/ContextAccessParams.h>
#include <Core/Settings.h>
#include <Common/typeid_cast.h>
namespace DB
{
ContextAccessParams::ContextAccessParams(
const std::optional<UUID> user_id_,
bool full_access_,
bool use_default_roles_,
const std::shared_ptr<const std::vector<UUID>> & current_roles_,
const Settings & settings_,
const String & current_database_,
const ClientInfo & client_info_)
: user_id(user_id_)
, full_access(full_access_)
, use_default_roles(use_default_roles_)
, current_roles(current_roles_)
, readonly(settings_.readonly)
, allow_ddl(settings_.allow_ddl)
, allow_introspection(settings_.allow_introspection_functions)
, current_database(current_database_)
, interface(client_info_.interface)
, http_method(client_info_.http_method)
, address(client_info_.current_address.host())
, forwarded_address(client_info_.getLastForwardedFor())
, quota_key(client_info_.quota_key)
, initial_user((client_info_.initial_user != client_info_.current_user) ? client_info_.initial_user : "")
{
}
String ContextAccessParams::toString() const
{
WriteBufferFromOwnString out;
auto separator = [&] { return out.stringView().empty() ? "" : ", "; };
if (user_id)
out << separator() << "user_id = " << *user_id;
if (full_access)
out << separator() << "full_access = " << full_access;
if (use_default_roles)
out << separator() << "use_default_roles = " << use_default_roles;
if (current_roles && !current_roles->empty())
{
out << separator() << "current_roles = [";
for (size_t i = 0; i != current_roles->size(); ++i)
{
if (i)
out << ", ";
out << (*current_roles)[i];
}
out << "]";
}
if (readonly)
out << separator() << "readonly = " << readonly;
if (allow_ddl)
out << separator() << "allow_ddl = " << allow_ddl;
if (allow_introspection)
out << separator() << "allow_introspection = " << allow_introspection;
if (!current_database.empty())
out << separator() << "current_database = " << current_database;
out << separator() << "interface = " << magic_enum::enum_name(interface);
if (http_method != ClientInfo::HTTPMethod::UNKNOWN)
out << separator() << "http_method = " << magic_enum::enum_name(http_method);
if (!address.isWildcard())
out << separator() << "address = " << address.toString();
if (!forwarded_address.empty())
out << separator() << "forwarded_address = " << forwarded_address;
if (!quota_key.empty())
out << separator() << "quota_key = " << quota_key;
if (!initial_user.empty())
out << separator() << "initial_user = " << initial_user;
return out.str();
}
bool operator ==(const ContextAccessParams & left, const ContextAccessParams & right)
{
auto check_equals = [](const auto & x, const auto & y)
{
if constexpr (::detail::is_shared_ptr_v<std::remove_cvref_t<decltype(x)>>)
{
if (!x)
return !y;
else if (!y)
return false;
else
return *x == *y;
}
else
{
return x == y;
}
};
#define CONTEXT_ACCESS_PARAMS_EQUALS(name) \
if (!check_equals(left.name, right.name)) \
return false;
CONTEXT_ACCESS_PARAMS_EQUALS(user_id)
CONTEXT_ACCESS_PARAMS_EQUALS(full_access)
CONTEXT_ACCESS_PARAMS_EQUALS(use_default_roles)
CONTEXT_ACCESS_PARAMS_EQUALS(current_roles)
CONTEXT_ACCESS_PARAMS_EQUALS(readonly)
CONTEXT_ACCESS_PARAMS_EQUALS(allow_ddl)
CONTEXT_ACCESS_PARAMS_EQUALS(allow_introspection)
CONTEXT_ACCESS_PARAMS_EQUALS(current_database)
CONTEXT_ACCESS_PARAMS_EQUALS(interface)
CONTEXT_ACCESS_PARAMS_EQUALS(http_method)
CONTEXT_ACCESS_PARAMS_EQUALS(address)
CONTEXT_ACCESS_PARAMS_EQUALS(forwarded_address)
CONTEXT_ACCESS_PARAMS_EQUALS(quota_key)
CONTEXT_ACCESS_PARAMS_EQUALS(initial_user)
#undef CONTEXT_ACCESS_PARAMS_EQUALS
return true; /// All fields are equal, operator == must return true.
}
bool operator <(const ContextAccessParams & left, const ContextAccessParams & right)
{
auto check_less = [](const auto & x, const auto & y)
{
if constexpr (::detail::is_shared_ptr_v<std::remove_cvref_t<decltype(x)>>)
{
if (!x)
return y ? -1 : 0;
else if (!y)
return 1;
else if (*x == *y)
return 0;
else if (*x < *y)
return -1;
else
return 1;
}
else
{
if (x == y)
return 0;
else if (x < y)
return -1;
else
return 1;
}
};
#define CONTEXT_ACCESS_PARAMS_LESS(name) \
if (auto cmp = check_less(left.name, right.name); cmp != 0) \
return cmp < 0;
CONTEXT_ACCESS_PARAMS_LESS(user_id)
CONTEXT_ACCESS_PARAMS_LESS(full_access)
CONTEXT_ACCESS_PARAMS_LESS(use_default_roles)
CONTEXT_ACCESS_PARAMS_LESS(current_roles)
CONTEXT_ACCESS_PARAMS_LESS(readonly)
CONTEXT_ACCESS_PARAMS_LESS(allow_ddl)
CONTEXT_ACCESS_PARAMS_LESS(allow_introspection)
CONTEXT_ACCESS_PARAMS_LESS(current_database)
CONTEXT_ACCESS_PARAMS_LESS(interface)
CONTEXT_ACCESS_PARAMS_LESS(http_method)
CONTEXT_ACCESS_PARAMS_LESS(address)
CONTEXT_ACCESS_PARAMS_LESS(forwarded_address)
CONTEXT_ACCESS_PARAMS_LESS(quota_key)
CONTEXT_ACCESS_PARAMS_LESS(initial_user)
#undef CONTEXT_ACCESS_PARAMS_LESS
return false; /// All fields are equal, operator < must return false.
}
bool ContextAccessParams::dependsOnSettingName(std::string_view setting_name)
{
return (setting_name == "readonly") || (setting_name == "allow_ddl") || (setting_name == "allow_introspection_functions");
}
}

View File

@ -0,0 +1,67 @@
#pragma once
#include <Interpreters/ClientInfo.h>
#include <Core/UUID.h>
#include <optional>
#include <vector>
namespace DB
{
struct Settings;
/// Parameters which are used to calculate access rights and some related stuff like roles or constraints.
class ContextAccessParams
{
public:
ContextAccessParams(
const std::optional<UUID> user_id_,
bool full_access_,
bool use_default_roles_,
const std::shared_ptr<const std::vector<UUID>> & current_roles_,
const Settings & settings_,
const String & current_database_,
const ClientInfo & client_info_);
const std::optional<UUID> user_id;
/// Full access to everything without any limitations.
/// This is used for the global context.
const bool full_access;
const bool use_default_roles;
const std::shared_ptr<const std::vector<UUID>> current_roles;
const UInt64 readonly;
const bool allow_ddl;
const bool allow_introspection;
const String current_database;
const ClientInfo::Interface interface;
const ClientInfo::HTTPMethod http_method;
const Poco::Net::IPAddress address;
/// The last entry from comma separated list of X-Forwarded-For addresses.
/// Only the last proxy can be trusted (if any).
const String forwarded_address;
const String quota_key;
/// Initial user is used to combine row policies with.
const String initial_user;
/// Outputs `ContextAccessParams` to string for logging.
String toString() const;
friend bool operator <(const ContextAccessParams & left, const ContextAccessParams & right);
friend bool operator ==(const ContextAccessParams & left, const ContextAccessParams & right);
friend bool operator !=(const ContextAccessParams & left, const ContextAccessParams & right) { return !(left == right); }
friend bool operator >(const ContextAccessParams & left, const ContextAccessParams & right) { return right < left; }
friend bool operator <=(const ContextAccessParams & left, const ContextAccessParams & right) { return !(right < left); }
friend bool operator >=(const ContextAccessParams & left, const ContextAccessParams & right) { return !(left < right); }
static bool dependsOnSettingName(std::string_view setting_name);
};
}

View File

@ -1,10 +1,25 @@
#include <AggregateFunctions/IAggregateFunction.h>
#include <AggregateFunctions/AggregateFunctionFactory.h> #include <AggregateFunctions/AggregateFunctionFactory.h>
#include <AggregateFunctions/AggregateFunctionGroupArrayMoving.h>
#include <AggregateFunctions/Helpers.h> #include <AggregateFunctions/Helpers.h>
#include <AggregateFunctions/FactoryHelpers.h> #include <AggregateFunctions/FactoryHelpers.h>
#include <DataTypes/DataTypeDate.h> #include <DataTypes/DataTypeDate.h>
#include <DataTypes/DataTypeDateTime.h>
#include <DataTypes/DataTypeDateTime64.h> #include <DataTypes/DataTypeDateTime64.h>
#include <DataTypes/DataTypeArray.h>
#include <DataTypes/DataTypesNumber.h>
#include <DataTypes/DataTypesDecimal.h>
#include <IO/WriteHelpers.h>
#include <IO/ReadHelpers.h>
#include <Columns/ColumnVector.h>
#include <Columns/ColumnArray.h>
#include <Common/ArenaAllocator.h>
#include <Common/assert_cast.h>
#include <type_traits>
#define AGGREGATE_FUNCTION_MOVING_MAX_ARRAY_SIZE 0xFFFFFF
namespace DB namespace DB
@ -13,11 +28,186 @@ struct Settings;
namespace ErrorCodes namespace ErrorCodes
{ {
extern const int TOO_LARGE_ARRAY_SIZE;
extern const int ILLEGAL_TYPE_OF_ARGUMENT; extern const int ILLEGAL_TYPE_OF_ARGUMENT;
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
extern const int BAD_ARGUMENTS; extern const int BAD_ARGUMENTS;
} }
template <typename T>
struct MovingData
{
/// For easy serialization.
static_assert(std::has_unique_object_representations_v<T> || std::is_floating_point_v<T>);
using Accumulator = T;
/// Switch to ordinary Allocator after 4096 bytes to avoid fragmentation and trash in Arena
using Allocator = MixedAlignedArenaAllocator<alignof(T), 4096>;
using Array = PODArray<T, 32, Allocator>;
Array value; /// Prefix sums.
T sum{};
void NO_SANITIZE_UNDEFINED add(T val, Arena * arena)
{
sum += val;
value.push_back(sum, arena);
}
};
template <typename T>
struct MovingSumData : public MovingData<T>
{
static constexpr auto name = "groupArrayMovingSum";
T NO_SANITIZE_UNDEFINED get(size_t idx, UInt64 window_size) const
{
if (idx < window_size)
return this->value[idx];
else
return this->value[idx] - this->value[idx - window_size];
}
};
template <typename T>
struct MovingAvgData : public MovingData<T>
{
static constexpr auto name = "groupArrayMovingAvg";
T NO_SANITIZE_UNDEFINED get(size_t idx, UInt64 window_size) const
{
if (idx < window_size)
return this->value[idx] / T(window_size);
else
return (this->value[idx] - this->value[idx - window_size]) / T(window_size);
}
};
template <typename T, typename LimitNumElements, typename Data>
class MovingImpl final
: public IAggregateFunctionDataHelper<Data, MovingImpl<T, LimitNumElements, Data>>
{
static constexpr bool limit_num_elems = LimitNumElements::value;
UInt64 window_size;
public:
using ResultT = typename Data::Accumulator;
using ColumnSource = ColumnVectorOrDecimal<T>;
/// Probably for overflow function in the future.
using ColumnResult = ColumnVectorOrDecimal<ResultT>;
explicit MovingImpl(const DataTypePtr & data_type_, UInt64 window_size_ = std::numeric_limits<UInt64>::max())
: IAggregateFunctionDataHelper<Data, MovingImpl<T, LimitNumElements, Data>>({data_type_}, {}, createResultType(data_type_))
, window_size(window_size_) {}
String getName() const override { return Data::name; }
static DataTypePtr createResultType(const DataTypePtr & argument)
{
return std::make_shared<DataTypeArray>(getReturnTypeElement(argument));
}
void NO_SANITIZE_UNDEFINED add(AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * arena) const override
{
auto value = static_cast<const ColumnSource &>(*columns[0]).getData()[row_num];
this->data(place).add(static_cast<ResultT>(value), arena);
}
void NO_SANITIZE_UNDEFINED merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena * arena) const override
{
auto & cur_elems = this->data(place);
auto & rhs_elems = this->data(rhs);
size_t cur_size = cur_elems.value.size();
if (rhs_elems.value.size())
cur_elems.value.insert(rhs_elems.value.begin(), rhs_elems.value.end(), arena);
for (size_t i = cur_size; i < cur_elems.value.size(); ++i)
{
cur_elems.value[i] += cur_elems.sum;
}
cur_elems.sum += rhs_elems.sum;
}
void serialize(ConstAggregateDataPtr __restrict place, WriteBuffer & buf, std::optional<size_t> /* version */) const override
{
const auto & value = this->data(place).value;
size_t size = value.size();
writeVarUInt(size, buf);
buf.write(reinterpret_cast<const char *>(value.data()), size * sizeof(value[0]));
}
void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional<size_t> /* version */, Arena * arena) const override
{
size_t size = 0;
readVarUInt(size, buf);
if (unlikely(size > AGGREGATE_FUNCTION_MOVING_MAX_ARRAY_SIZE))
throw Exception(ErrorCodes::TOO_LARGE_ARRAY_SIZE,
"Too large array size (maximum: {})", AGGREGATE_FUNCTION_MOVING_MAX_ARRAY_SIZE);
if (size > 0)
{
auto & value = this->data(place).value;
value.resize(size, arena);
buf.readStrict(reinterpret_cast<char *>(value.data()), size * sizeof(value[0]));
this->data(place).sum = value.back();
}
}
void insertResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena *) const override
{
const auto & data = this->data(place);
size_t size = data.value.size();
ColumnArray & arr_to = assert_cast<ColumnArray &>(to);
ColumnArray::Offsets & offsets_to = arr_to.getOffsets();
offsets_to.push_back(offsets_to.back() + size);
if (size)
{
typename ColumnResult::Container & data_to = assert_cast<ColumnResult &>(arr_to.getData()).getData();
for (size_t i = 0; i < size; ++i)
{
if (!limit_num_elems)
{
data_to.push_back(data.get(i, size));
}
else
{
data_to.push_back(data.get(i, window_size));
}
}
}
}
bool allocatesMemoryInArena() const override
{
return true;
}
private:
static auto getReturnTypeElement(const DataTypePtr & argument)
{
if constexpr (!is_decimal<ResultT>)
return std::make_shared<DataTypeNumber<ResultT>>();
else
{
using Res = DataTypeDecimal<ResultT>;
return std::make_shared<Res>(Res::maxPrecision(), getDecimalScale(*argument));
}
}
};
namespace namespace
{ {
@ -79,7 +269,7 @@ AggregateFunctionPtr createAggregateFunctionMoving(
if (type != Field::Types::Int64 && type != Field::Types::UInt64) if (type != Field::Types::Int64 && type != Field::Types::UInt64)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parameter for aggregate function {} should be positive integer", name); throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parameter for aggregate function {} should be positive integer", name);
if ((type == Field::Types::Int64 && parameters[0].get<Int64>() < 0) || if ((type == Field::Types::Int64 && parameters[0].get<Int64>() <= 0) ||
(type == Field::Types::UInt64 && parameters[0].get<UInt64>() == 0)) (type == Field::Types::UInt64 && parameters[0].get<UInt64>() == 0))
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parameter for aggregate function {} should be positive integer", name); throw Exception(ErrorCodes::BAD_ARGUMENTS, "Parameter for aggregate function {} should be positive integer", name);

View File

@ -1,207 +0,0 @@
#pragma once
#include <IO/WriteHelpers.h>
#include <IO/ReadHelpers.h>
#include <DataTypes/DataTypeArray.h>
#include <DataTypes/DataTypesNumber.h>
#include <DataTypes/DataTypesDecimal.h>
#include <Columns/ColumnVector.h>
#include <Columns/ColumnArray.h>
#include <Common/ArenaAllocator.h>
#include <Common/assert_cast.h>
#include <AggregateFunctions/IAggregateFunction.h>
#include <type_traits>
#define AGGREGATE_FUNCTION_MOVING_MAX_ARRAY_SIZE 0xFFFFFF
namespace DB
{
struct Settings;
namespace ErrorCodes
{
extern const int TOO_LARGE_ARRAY_SIZE;
}
template <typename T>
struct MovingData
{
/// For easy serialization.
static_assert(std::has_unique_object_representations_v<T> || std::is_floating_point_v<T>);
using Accumulator = T;
/// Switch to ordinary Allocator after 4096 bytes to avoid fragmentation and trash in Arena
using Allocator = MixedAlignedArenaAllocator<alignof(T), 4096>;
using Array = PODArray<T, 32, Allocator>;
Array value; /// Prefix sums.
T sum{};
void NO_SANITIZE_UNDEFINED add(T val, Arena * arena)
{
sum += val;
value.push_back(sum, arena);
}
};
template <typename T>
struct MovingSumData : public MovingData<T>
{
static constexpr auto name = "groupArrayMovingSum";
T NO_SANITIZE_UNDEFINED get(size_t idx, UInt64 window_size) const
{
if (idx < window_size)
return this->value[idx];
else
return this->value[idx] - this->value[idx - window_size];
}
};
template <typename T>
struct MovingAvgData : public MovingData<T>
{
static constexpr auto name = "groupArrayMovingAvg";
T NO_SANITIZE_UNDEFINED get(size_t idx, UInt64 window_size) const
{
if (idx < window_size)
return this->value[idx] / T(window_size);
else
return (this->value[idx] - this->value[idx - window_size]) / T(window_size);
}
};
template <typename T, typename LimitNumElements, typename Data>
class MovingImpl final
: public IAggregateFunctionDataHelper<Data, MovingImpl<T, LimitNumElements, Data>>
{
static constexpr bool limit_num_elems = LimitNumElements::value;
UInt64 window_size;
public:
using ResultT = typename Data::Accumulator;
using ColumnSource = ColumnVectorOrDecimal<T>;
/// Probably for overflow function in the future.
using ColumnResult = ColumnVectorOrDecimal<ResultT>;
explicit MovingImpl(const DataTypePtr & data_type_, UInt64 window_size_ = std::numeric_limits<UInt64>::max())
: IAggregateFunctionDataHelper<Data, MovingImpl<T, LimitNumElements, Data>>({data_type_}, {}, createResultType(data_type_))
, window_size(window_size_) {}
String getName() const override { return Data::name; }
static DataTypePtr createResultType(const DataTypePtr & argument)
{
return std::make_shared<DataTypeArray>(getReturnTypeElement(argument));
}
void NO_SANITIZE_UNDEFINED add(AggregateDataPtr __restrict place, const IColumn ** columns, size_t row_num, Arena * arena) const override
{
auto value = static_cast<const ColumnSource &>(*columns[0]).getData()[row_num];
this->data(place).add(static_cast<ResultT>(value), arena);
}
void NO_SANITIZE_UNDEFINED merge(AggregateDataPtr __restrict place, ConstAggregateDataPtr rhs, Arena * arena) const override
{
auto & cur_elems = this->data(place);
auto & rhs_elems = this->data(rhs);
size_t cur_size = cur_elems.value.size();
if (rhs_elems.value.size())
cur_elems.value.insert(rhs_elems.value.begin(), rhs_elems.value.end(), arena);
for (size_t i = cur_size; i < cur_elems.value.size(); ++i)
{
cur_elems.value[i] += cur_elems.sum;
}
cur_elems.sum += rhs_elems.sum;
}
void serialize(ConstAggregateDataPtr __restrict place, WriteBuffer & buf, std::optional<size_t> /* version */) const override
{
const auto & value = this->data(place).value;
size_t size = value.size();
writeVarUInt(size, buf);
buf.write(reinterpret_cast<const char *>(value.data()), size * sizeof(value[0]));
}
void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional<size_t> /* version */, Arena * arena) const override
{
size_t size = 0;
readVarUInt(size, buf);
if (unlikely(size > AGGREGATE_FUNCTION_MOVING_MAX_ARRAY_SIZE))
throw Exception(ErrorCodes::TOO_LARGE_ARRAY_SIZE,
"Too large array size (maximum: {})", AGGREGATE_FUNCTION_MOVING_MAX_ARRAY_SIZE);
if (size > 0)
{
auto & value = this->data(place).value;
value.resize(size, arena);
buf.readStrict(reinterpret_cast<char *>(value.data()), size * sizeof(value[0]));
this->data(place).sum = value.back();
}
}
void insertResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena *) const override
{
const auto & data = this->data(place);
size_t size = data.value.size();
ColumnArray & arr_to = assert_cast<ColumnArray &>(to);
ColumnArray::Offsets & offsets_to = arr_to.getOffsets();
offsets_to.push_back(offsets_to.back() + size);
if (size)
{
typename ColumnResult::Container & data_to = assert_cast<ColumnResult &>(arr_to.getData()).getData();
for (size_t i = 0; i < size; ++i)
{
if (!limit_num_elems)
{
data_to.push_back(data.get(i, size));
}
else
{
data_to.push_back(data.get(i, window_size));
}
}
}
}
bool allocatesMemoryInArena() const override
{
return true;
}
private:
static auto getReturnTypeElement(const DataTypePtr & argument)
{
if constexpr (!is_decimal<ResultT>)
return std::make_shared<DataTypeNumber<ResultT>>();
else
{
using Res = DataTypeDecimal<ResultT>;
return std::make_shared<Res>(Res::maxPrecision(), getDecimalScale(*argument));
}
}
};
#undef AGGREGATE_FUNCTION_MOVING_MAX_ARRAY_SIZE
}

View File

@ -46,6 +46,7 @@
#include <Parsers/ASTColumnDeclaration.h> #include <Parsers/ASTColumnDeclaration.h>
#include <Parsers/ASTFunction.h> #include <Parsers/ASTFunction.h>
#include <Parsers/Kusto/ParserKQLStatement.h> #include <Parsers/Kusto/ParserKQLStatement.h>
#include <Parsers/PRQL/ParserPRQLQuery.h>
#include <Processors/Formats/Impl/NullFormat.h> #include <Processors/Formats/Impl/NullFormat.h>
#include <Processors/Formats/IInputFormat.h> #include <Processors/Formats/IInputFormat.h>
@ -72,6 +73,7 @@
#include <iostream> #include <iostream>
#include <filesystem> #include <filesystem>
#include <map> #include <map>
#include <memory>
#include <unordered_map> #include <unordered_map>
#include "config_version.h" #include "config_version.h"
@ -338,6 +340,8 @@ ASTPtr ClientBase::parseQuery(const char *& pos, const char * end, bool allow_mu
if (dialect == Dialect::kusto) if (dialect == Dialect::kusto)
parser = std::make_unique<ParserKQLStatement>(end, global_context->getSettings().allow_settings_after_format_in_insert); parser = std::make_unique<ParserKQLStatement>(end, global_context->getSettings().allow_settings_after_format_in_insert);
else if (dialect == Dialect::prql)
parser = std::make_unique<ParserPRQLQuery>(max_length, settings.max_parser_depth);
else else
parser = std::make_unique<ParserQuery>(end, global_context->getSettings().allow_settings_after_format_in_insert); parser = std::make_unique<ParserQuery>(end, global_context->getSettings().allow_settings_after_format_in_insert);

View File

@ -105,6 +105,8 @@ void Connection::connect(const ConnectionTimeouts & timeouts)
for (auto it = addresses.begin(); it != addresses.end();) for (auto it = addresses.begin(); it != addresses.end();)
{ {
have_more_addresses_to_connect = it != std::prev(addresses.end());
if (connected) if (connected)
disconnect(); disconnect();

View File

@ -159,6 +159,8 @@ public:
out->setAsyncCallback(async_callback); out->setAsyncCallback(async_callback);
} }
bool haveMoreAddressesToConnect() const { return have_more_addresses_to_connect; }
private: private:
String host; String host;
UInt16 port; UInt16 port;
@ -227,6 +229,8 @@ private:
std::shared_ptr<WriteBuffer> maybe_compressed_out; std::shared_ptr<WriteBuffer> maybe_compressed_out;
std::unique_ptr<NativeWriter> block_out; std::unique_ptr<NativeWriter> block_out;
bool have_more_addresses_to_connect = false;
/// Logger is created lazily, for avoid to run DNS request in constructor. /// Logger is created lazily, for avoid to run DNS request in constructor.
class LoggerWrapper class LoggerWrapper
{ {

View File

@ -179,7 +179,7 @@ bool ConnectionEstablisherAsync::checkTimeout()
is_timeout_alarmed = true; is_timeout_alarmed = true;
} }
if (is_timeout_alarmed && !is_socket_ready) if (is_timeout_alarmed && !is_socket_ready && !haveMoreAddressesToConnect())
{ {
/// In not async case timeout exception would be thrown and caught in ConnectionEstablisher::run, /// In not async case timeout exception would be thrown and caught in ConnectionEstablisher::run,
/// but in async case we process timeout outside and cannot throw exception. So, we just save fail message. /// but in async case we process timeout outside and cannot throw exception. So, we just save fail message.
@ -225,6 +225,11 @@ void ConnectionEstablisherAsync::resetResult()
} }
} }
bool ConnectionEstablisherAsync::haveMoreAddressesToConnect()
{
return !result.entry.isNull() && result.entry->haveMoreAddressesToConnect();
}
#endif #endif
} }

View File

@ -104,6 +104,8 @@ private:
void resetResult(); void resetResult();
bool haveMoreAddressesToConnect();
ConnectionEstablisher connection_establisher; ConnectionEstablisher connection_establisher;
TryResult result; TryResult result;
std::string fail_message; std::string fail_message;

View File

@ -353,6 +353,8 @@ bool HedgedConnections::resumePacketReceiver(const HedgedConnections::ReplicaLoc
if (replica_state.packet_receiver->isPacketReady()) if (replica_state.packet_receiver->isPacketReady())
{ {
/// Reset the socket timeout after some packet received
replica_state.packet_receiver->setTimeout(hedged_connections_factory.getConnectionTimeouts().receive_timeout);
last_received_packet = replica_state.packet_receiver->getPacket(); last_received_packet = replica_state.packet_receiver->getPacket();
return true; return true;
} }

View File

@ -319,24 +319,21 @@ Packet MultiplexedConnections::receivePacketUnlocked(AsyncCallback async_callbac
throw Exception(ErrorCodes::NO_AVAILABLE_REPLICA, "Logical error: no available replica"); throw Exception(ErrorCodes::NO_AVAILABLE_REPLICA, "Logical error: no available replica");
Packet packet; Packet packet;
try
{ {
AsyncCallbackSetter async_setter(current_connection, std::move(async_callback)); AsyncCallbackSetter async_setter(current_connection, std::move(async_callback));
packet = current_connection->receivePacket();
try }
catch (Exception & e)
{
if (e.code() == ErrorCodes::UNKNOWN_PACKET_FROM_SERVER)
{ {
packet = current_connection->receivePacket(); /// Exception may happen when packet is received, e.g. when got unknown packet.
} /// In this case, invalidate replica, so that we would not read from it anymore.
catch (Exception & e) current_connection->disconnect();
{ invalidateReplica(state);
if (e.code() == ErrorCodes::UNKNOWN_PACKET_FROM_SERVER)
{
/// Exception may happen when packet is received, e.g. when got unknown packet.
/// In this case, invalidate replica, so that we would not read from it anymore.
current_connection->disconnect();
invalidateReplica(state);
}
throw;
} }
throw;
} }
switch (packet.type) switch (packet.type)

View File

@ -1,26 +1,4 @@
#include "Allocator.h" #include "Allocator.h"
/** Keep definition of this constant in cpp file; otherwise its value template class Allocator<false>;
* is inlined into allocator code making it impossible to override it template class Allocator<true>;
* in third-party code.
*
* Note: extern may seem redundant, but is actually needed due to bug in GCC.
* See also: https://gcc.gnu.org/legacy-ml/gcc-help/2017-12/msg00021.html
*/
#ifdef NDEBUG
__attribute__((__weak__)) extern const size_t MMAP_THRESHOLD = 128 * (1ULL << 20);
#else
/**
* In debug build, use small mmap threshold to reproduce more memory
* stomping bugs. Along with ASLR it will hopefully detect more issues than
* ASan. The program may fail due to the limit on number of memory mappings.
*
* Not too small to avoid too quick exhaust of memory mappings.
*/
__attribute__((__weak__)) extern const size_t MMAP_THRESHOLD = 16384;
#endif
template class Allocator<false, false>;
template class Allocator<true, false>;
template class Allocator<false, true>;
template class Allocator<true, true>;

View File

@ -36,51 +36,26 @@
#include <Common/Allocator_fwd.h> #include <Common/Allocator_fwd.h>
/// Required for older Darwin builds, that lack definition of MAP_ANONYMOUS
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
/**
* Many modern allocators (for example, tcmalloc) do not do a mremap for
* realloc, even in case of large enough chunks of memory. Although this allows
* you to increase performance and reduce memory consumption during realloc.
* To fix this, we do mremap manually if the chunk of memory is large enough.
* The threshold (64 MB) is chosen quite large, since changing the address
* space is very slow, especially in the case of a large number of threads. We
* expect that the set of operations mmap/something to do/mremap can only be
* performed about 1000 times per second.
*
* P.S. This is also required, because tcmalloc can not allocate a chunk of
* memory greater than 16 GB.
*
* P.P.S. Note that MMAP_THRESHOLD symbol is intentionally made weak. It allows
* to override it during linkage when using ClickHouse as a library in
* third-party applications which may already use own allocator doing mmaps
* in the implementation of alloc/realloc.
*/
extern const size_t MMAP_THRESHOLD;
static constexpr size_t MALLOC_MIN_ALIGNMENT = 8; static constexpr size_t MALLOC_MIN_ALIGNMENT = 8;
namespace CurrentMetrics
{
extern const Metric MMappedAllocs;
extern const Metric MMappedAllocBytes;
}
namespace DB namespace DB
{ {
namespace ErrorCodes namespace ErrorCodes
{ {
extern const int BAD_ARGUMENTS;
extern const int CANNOT_ALLOCATE_MEMORY; extern const int CANNOT_ALLOCATE_MEMORY;
extern const int CANNOT_MUNMAP;
extern const int CANNOT_MREMAP;
extern const int LOGICAL_ERROR; extern const int LOGICAL_ERROR;
} }
} }
/** Previously there was a code which tried to use manual mmap and mremap (clickhouse_mremap.h) for large allocations/reallocations (64MB+).
* Most modern allocators (including jemalloc) don't use mremap, so the idea was to take advantage from mremap system call for large reallocs.
* Actually jemalloc had support for mremap, but it was intentionally removed from codebase https://github.com/jemalloc/jemalloc/commit/e2deab7a751c8080c2b2cdcfd7b11887332be1bb.
* Our performance tests also shows that without manual mmap/mremap/munmap clickhouse is overall faster for about 1-2% and up to 5-7x for some types of queries.
* That is why we don't do manuall mmap/mremap/munmap here and completely rely on jemalloc for allocations of any size.
*/
/** Responsible for allocating / freeing memory. Used, for example, in PODArray, Arena. /** Responsible for allocating / freeing memory. Used, for example, in PODArray, Arena.
* Also used in hash tables. * Also used in hash tables.
* The interface is different from std::allocator * The interface is different from std::allocator
@ -88,10 +63,8 @@ namespace ErrorCodes
* - passing the size into the `free` method; * - passing the size into the `free` method;
* - by the presence of the `alignment` argument; * - by the presence of the `alignment` argument;
* - the possibility of zeroing memory (used in hash tables); * - the possibility of zeroing memory (used in hash tables);
* - random hint address for mmap
* - mmap_threshold for using mmap less or more
*/ */
template <bool clear_memory_, bool mmap_populate> template <bool clear_memory_>
class Allocator class Allocator
{ {
public: public:
@ -109,7 +82,7 @@ public:
try try
{ {
checkSize(size); checkSize(size);
freeNoTrack(buf, size); freeNoTrack(buf);
CurrentMemoryTracker::free(size); CurrentMemoryTracker::free(size);
} }
catch (...) catch (...)
@ -132,49 +105,26 @@ public:
/// nothing to do. /// nothing to do.
/// BTW, it's not possible to change alignment while doing realloc. /// BTW, it's not possible to change alignment while doing realloc.
} }
else if (old_size < MMAP_THRESHOLD && new_size < MMAP_THRESHOLD else if (alignment <= MALLOC_MIN_ALIGNMENT)
&& alignment <= MALLOC_MIN_ALIGNMENT)
{ {
/// Resize malloc'd memory region with no special alignment requirement. /// Resize malloc'd memory region with no special alignment requirement.
CurrentMemoryTracker::realloc(old_size, new_size); CurrentMemoryTracker::realloc(old_size, new_size);
void * new_buf = ::realloc(buf, new_size); void * new_buf = ::realloc(buf, new_size);
if (nullptr == new_buf) if (nullptr == new_buf)
DB::throwFromErrno(fmt::format("Allocator: Cannot realloc from {} to {}.", ReadableSize(old_size), ReadableSize(new_size)), DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY); {
DB::throwFromErrno(
fmt::format("Allocator: Cannot realloc from {} to {}.", ReadableSize(old_size), ReadableSize(new_size)), DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
}
buf = new_buf; buf = new_buf;
if constexpr (clear_memory) if constexpr (clear_memory)
if (new_size > old_size) if (new_size > old_size)
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size); memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
} }
else if (old_size >= MMAP_THRESHOLD && new_size >= MMAP_THRESHOLD)
{
/// Resize mmap'd memory region.
CurrentMemoryTracker::realloc(old_size, new_size);
// On apple and freebsd self-implemented mremap used (common/mremap.h)
buf = clickhouse_mremap(buf, old_size, new_size, MREMAP_MAYMOVE,
PROT_READ | PROT_WRITE, mmap_flags, -1, 0);
if (MAP_FAILED == buf)
DB::throwFromErrno(fmt::format("Allocator: Cannot mremap memory chunk from {} to {}.",
ReadableSize(old_size), ReadableSize(new_size)), DB::ErrorCodes::CANNOT_MREMAP);
/// No need for zero-fill, because mmap guarantees it.
}
else if (new_size < MMAP_THRESHOLD)
{
/// Small allocs that requires a copy. Assume there's enough memory in system. Call CurrentMemoryTracker once.
CurrentMemoryTracker::realloc(old_size, new_size);
void * new_buf = allocNoTrack(new_size, alignment);
memcpy(new_buf, buf, std::min(old_size, new_size));
freeNoTrack(buf, old_size);
buf = new_buf;
}
else else
{ {
/// Big allocs that requires a copy. MemoryTracker is called inside 'alloc', 'free' methods. /// Big allocs that requires a copy. MemoryTracker is called inside 'alloc', 'free' methods.
void * new_buf = alloc(new_size, alignment); void * new_buf = alloc(new_size, alignment);
memcpy(new_buf, buf, std::min(old_size, new_size)); memcpy(new_buf, buf, std::min(old_size, new_size));
free(buf, old_size); free(buf, old_size);
@ -192,83 +142,38 @@ protected:
static constexpr bool clear_memory = clear_memory_; static constexpr bool clear_memory = clear_memory_;
// Freshly mmapped pages are copy-on-write references to a global zero page.
// On the first write, a page fault occurs, and an actual writable page is
// allocated. If we are going to use this memory soon, such as when resizing
// hash tables, it makes sense to pre-fault the pages by passing
// MAP_POPULATE to mmap(). This takes some time, but should be faster
// overall than having a hot loop interrupted by page faults.
// It is only supported on Linux.
static constexpr int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS
#if defined(OS_LINUX)
| (mmap_populate ? MAP_POPULATE : 0)
#endif
;
private: private:
void * allocNoTrack(size_t size, size_t alignment) void * allocNoTrack(size_t size, size_t alignment)
{ {
void * buf; void * buf;
size_t mmap_min_alignment = ::getPageSize(); if (alignment <= MALLOC_MIN_ALIGNMENT)
if (size >= MMAP_THRESHOLD)
{ {
if (alignment > mmap_min_alignment) if constexpr (clear_memory)
throw DB::Exception(DB::ErrorCodes::BAD_ARGUMENTS, buf = ::calloc(size, 1);
"Too large alignment {}: more than page size when allocating {}.", else
ReadableSize(alignment), ReadableSize(size)); buf = ::malloc(size);
buf = mmap(getMmapHint(), size, PROT_READ | PROT_WRITE, if (nullptr == buf)
mmap_flags, -1, 0); DB::throwFromErrno(fmt::format("Allocator: Cannot malloc {}.", ReadableSize(size)), DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
if (MAP_FAILED == buf)
DB::throwFromErrno(fmt::format("Allocator: Cannot mmap {}.", ReadableSize(size)), DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
/// No need for zero-fill, because mmap guarantees it.
CurrentMetrics::add(CurrentMetrics::MMappedAllocs);
CurrentMetrics::add(CurrentMetrics::MMappedAllocBytes, size);
} }
else else
{ {
if (alignment <= MALLOC_MIN_ALIGNMENT) buf = nullptr;
{ int res = posix_memalign(&buf, alignment, size);
if constexpr (clear_memory)
buf = ::calloc(size, 1);
else
buf = ::malloc(size);
if (nullptr == buf) if (0 != res)
DB::throwFromErrno(fmt::format("Allocator: Cannot malloc {}.", ReadableSize(size)), DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY); DB::throwFromErrno(fmt::format("Cannot allocate memory (posix_memalign) {}.", ReadableSize(size)),
} DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY, res);
else
{
buf = nullptr;
int res = posix_memalign(&buf, alignment, size);
if (0 != res) if constexpr (clear_memory)
DB::throwFromErrno(fmt::format("Cannot allocate memory (posix_memalign) {}.", ReadableSize(size)), memset(buf, 0, size);
DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY, res);
if constexpr (clear_memory)
memset(buf, 0, size);
}
} }
return buf; return buf;
} }
void freeNoTrack(void * buf, size_t size) void freeNoTrack(void * buf)
{ {
if (size >= MMAP_THRESHOLD) ::free(buf);
{
if (0 != munmap(buf, size))
DB::throwFromErrno(fmt::format("Allocator: Cannot munmap {}.", ReadableSize(size)), DB::ErrorCodes::CANNOT_MUNMAP);
CurrentMetrics::sub(CurrentMetrics::MMappedAllocs);
CurrentMetrics::sub(CurrentMetrics::MMappedAllocBytes, size);
}
else
{
::free(buf);
}
} }
void checkSize(size_t size) void checkSize(size_t size)
@ -277,21 +182,6 @@ private:
if (size >= 0x8000000000000000ULL) if (size >= 0x8000000000000000ULL)
throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "Too large size ({}) passed to allocator. It indicates an error.", size); throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "Too large size ({}) passed to allocator. It indicates an error.", size);
} }
#ifndef NDEBUG
/// In debug builds, request mmap() at random addresses (a kind of ASLR), to
/// reproduce more memory stomping bugs. Note that Linux doesn't do it by
/// default. This may lead to worse TLB performance.
void * getMmapHint()
{
return reinterpret_cast<void *>(std::uniform_int_distribution<intptr_t>(0x100000000000UL, 0x700000000000UL)(thread_local_rng));
}
#else
void * getMmapHint()
{
return nullptr;
}
#endif
}; };
@ -367,7 +257,5 @@ constexpr size_t allocatorInitialBytes<AllocatorWithStackMemory<
/// Prevent implicit template instantiation of Allocator /// Prevent implicit template instantiation of Allocator
extern template class Allocator<false, false>; extern template class Allocator<false>;
extern template class Allocator<true, false>; extern template class Allocator<true>;
extern template class Allocator<false, true>;
extern template class Allocator<true, true>;

View File

@ -3,7 +3,7 @@
* This file provides forward declarations for Allocator. * This file provides forward declarations for Allocator.
*/ */
template <bool clear_memory_, bool mmap_populate = false> template <bool clear_memory_>
class Allocator; class Allocator;
template <typename Base, size_t N = 64, size_t Alignment = 1> template <typename Base, size_t N = 64, size_t Alignment = 1>

View File

@ -5,7 +5,6 @@ namespace DB
AsyncTaskExecutor::AsyncTaskExecutor(std::unique_ptr<AsyncTask> task_) : task(std::move(task_)) AsyncTaskExecutor::AsyncTaskExecutor(std::unique_ptr<AsyncTask> task_) : task(std::move(task_))
{ {
createFiber();
} }
void AsyncTaskExecutor::resume() void AsyncTaskExecutor::resume()
@ -13,6 +12,10 @@ void AsyncTaskExecutor::resume()
if (routine_is_finished) if (routine_is_finished)
return; return;
/// Create fiber lazily on first resume() call.
if (!fiber)
createFiber();
if (!checkBeforeTaskResume()) if (!checkBeforeTaskResume())
return; return;
@ -22,6 +25,11 @@ void AsyncTaskExecutor::resume()
return; return;
resumeUnlocked(); resumeUnlocked();
/// Destroy fiber when it's finished.
if (routine_is_finished)
destroyFiber();
if (exception) if (exception)
processException(exception); processException(exception);
} }
@ -46,9 +54,8 @@ void AsyncTaskExecutor::cancel()
void AsyncTaskExecutor::restart() void AsyncTaskExecutor::restart()
{ {
std::lock_guard guard(fiber_lock); std::lock_guard guard(fiber_lock);
if (fiber) if (!routine_is_finished)
destroyFiber(); destroyFiber();
createFiber();
routine_is_finished = false; routine_is_finished = false;
} }

View File

@ -173,8 +173,6 @@
M(PartsInMemory, "In-memory parts.") \ M(PartsInMemory, "In-memory parts.") \
M(MMappedFiles, "Total number of mmapped files.") \ M(MMappedFiles, "Total number of mmapped files.") \
M(MMappedFileBytes, "Sum size of mmapped file regions.") \ M(MMappedFileBytes, "Sum size of mmapped file regions.") \
M(MMappedAllocs, "Total number of mmapped allocations") \
M(MMappedAllocBytes, "Sum bytes of mmapped allocations") \
M(AsynchronousReadWait, "Number of threads waiting for asynchronous read.") \ M(AsynchronousReadWait, "Number of threads waiting for asynchronous read.") \
M(PendingAsyncInsert, "Number of asynchronous inserts that are waiting for flush.") \ M(PendingAsyncInsert, "Number of asynchronous inserts that are waiting for flush.") \
M(KafkaConsumers, "Number of active Kafka consumers") \ M(KafkaConsumers, "Number of active Kafka consumers") \

View File

@ -0,0 +1,56 @@
#include <Common/HTTPHeaderFilter.h>
#include <Common/StringUtils/StringUtils.h>
#include <Common/Exception.h>
#include <re2/re2.h>
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
void HTTPHeaderFilter::checkHeaders(const HTTPHeaderEntries & entries) const
{
std::lock_guard guard(mutex);
for (const auto & entry : entries)
{
if (forbidden_headers.contains(entry.name))
throw Exception(ErrorCodes::BAD_ARGUMENTS, "HTTP header \"{}\" is forbidden in configuration file, "
"see <http_forbid_headers>", entry.name);
for (const auto & header_regex : forbidden_headers_regexp)
if (re2::RE2::FullMatch(entry.name, header_regex))
throw Exception(ErrorCodes::BAD_ARGUMENTS, "HTTP header \"{}\" is forbidden in configuration file, "
"see <http_forbid_headers>", entry.name);
}
}
void HTTPHeaderFilter::setValuesFromConfig(const Poco::Util::AbstractConfiguration & config)
{
std::lock_guard guard(mutex);
if (config.has("http_forbid_headers"))
{
std::vector<std::string> keys;
config.keys("http_forbid_headers", keys);
for (const auto & key : keys)
{
if (startsWith(key, "header_regexp"))
forbidden_headers_regexp.push_back(config.getString("http_forbid_headers." + key));
else if (startsWith(key, "header"))
forbidden_headers.insert(config.getString("http_forbid_headers." + key));
}
}
else
{
forbidden_headers.clear();
forbidden_headers_regexp.clear();
}
}
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <IO/HTTPHeaderEntries.h>
#include <Poco/Util/AbstractConfiguration.h>
#include <vector>
#include <unordered_set>
#include <mutex>
namespace DB
{
class HTTPHeaderFilter
{
public:
void setValuesFromConfig(const Poco::Util::AbstractConfiguration & config);
void checkHeaders(const HTTPHeaderEntries & entries) const;
private:
std::unordered_set<std::string> forbidden_headers;
std::vector<std::string> forbidden_headers_regexp;
mutable std::mutex mutex;
};
}

View File

@ -8,7 +8,7 @@
* table, so it makes sense to pre-fault the pages so that page faults don't * table, so it makes sense to pre-fault the pages so that page faults don't
* interrupt the resize loop. Set the allocator parameter accordingly. * interrupt the resize loop. Set the allocator parameter accordingly.
*/ */
using HashTableAllocator = Allocator<true /* clear_memory */, true /* mmap_populate */>; using HashTableAllocator = Allocator<true /* clear_memory */>;
template <size_t initial_bytes = 64> template <size_t initial_bytes = 64>
using HashTableAllocatorWithStackMemory = AllocatorWithStackMemory<HashTableAllocator, initial_bytes>; using HashTableAllocatorWithStackMemory = AllocatorWithStackMemory<HashTableAllocator, initial_bytes>;

View File

@ -125,6 +125,7 @@
M(ZooKeeperMulti, "Number of 'multi' requests to ZooKeeper (compound transactions).") \ M(ZooKeeperMulti, "Number of 'multi' requests to ZooKeeper (compound transactions).") \
M(ZooKeeperCheck, "Number of 'check' requests to ZooKeeper. Usually they don't make sense in isolation, only as part of a complex transaction.") \ M(ZooKeeperCheck, "Number of 'check' requests to ZooKeeper. Usually they don't make sense in isolation, only as part of a complex transaction.") \
M(ZooKeeperSync, "Number of 'sync' requests to ZooKeeper. These requests are rarely needed or usable.") \ M(ZooKeeperSync, "Number of 'sync' requests to ZooKeeper. These requests are rarely needed or usable.") \
M(ZooKeeperReconfig, "Number of 'reconfig' requests to ZooKeeper.") \
M(ZooKeeperClose, "Number of times connection with ZooKeeper has been closed voluntary.") \ M(ZooKeeperClose, "Number of times connection with ZooKeeper has been closed voluntary.") \
M(ZooKeeperWatchResponse, "Number of times watch notification has been received from ZooKeeper.") \ M(ZooKeeperWatchResponse, "Number of times watch notification has been received from ZooKeeper.") \
M(ZooKeeperUserExceptions, "Number of exceptions while working with ZooKeeper related to the data (no node, bad version or similar).") \ M(ZooKeeperUserExceptions, "Number of exceptions while working with ZooKeeper related to the data (no node, bad version or similar).") \
@ -503,6 +504,7 @@ The server successfully detected this situation and will download merged part fr
M(KeeperCreateRequest, "Number of create requests")\ M(KeeperCreateRequest, "Number of create requests")\
M(KeeperRemoveRequest, "Number of remove requests")\ M(KeeperRemoveRequest, "Number of remove requests")\
M(KeeperSetRequest, "Number of set requests")\ M(KeeperSetRequest, "Number of set requests")\
M(KeeperReconfigRequest, "Number of reconfig requests")\
M(KeeperCheckRequest, "Number of check requests")\ M(KeeperCheckRequest, "Number of check requests")\
M(KeeperMultiRequest, "Number of multi requests")\ M(KeeperMultiRequest, "Number of multi requests")\
M(KeeperMultiReadRequest, "Number of multi read requests")\ M(KeeperMultiReadRequest, "Number of multi read requests")\

View File

@ -38,43 +38,30 @@ namespace
ISystemLog::~ISystemLog() = default; ISystemLog::~ISystemLog() = default;
void ISystemLog::stopFlushThread()
{
{
std::lock_guard lock(mutex);
if (!saving_thread || !saving_thread->joinable())
return;
if (is_shutdown)
return;
is_shutdown = true;
/// Tell thread to shutdown.
flush_event.notify_all();
}
saving_thread->join();
}
void ISystemLog::startup()
{
std::lock_guard lock(mutex);
saving_thread = std::make_unique<ThreadFromGlobalPool>([this] { savingThreadFunction(); });
}
static thread_local bool recursive_add_call = false;
template <typename LogElement> template <typename LogElement>
void SystemLogBase<LogElement>::add(const LogElement & element) SystemLogQueue<LogElement>::SystemLogQueue(
const String & table_name_,
size_t flush_interval_milliseconds_,
bool turn_off_logger_)
: log(&Poco::Logger::get("SystemLogQueue (" + table_name_ + ")"))
, flush_interval_milliseconds(flush_interval_milliseconds_)
{
if (turn_off_logger_)
log->setLevel(0);
}
static thread_local bool recursive_push_call = false;
template <typename LogElement>
void SystemLogQueue<LogElement>::push(const LogElement & element)
{ {
/// It is possible that the method will be called recursively. /// It is possible that the method will be called recursively.
/// Better to drop these events to avoid complications. /// Better to drop these events to avoid complications.
if (recursive_add_call) if (recursive_push_call)
return; return;
recursive_add_call = true; recursive_push_call = true;
SCOPE_EXIT({ recursive_add_call = false; }); SCOPE_EXIT({ recursive_push_call = false; });
/// Memory can be allocated while resizing on queue.push_back. /// Memory can be allocated while resizing on queue.push_back.
/// The size of allocation can be in order of a few megabytes. /// The size of allocation can be in order of a few megabytes.
@ -137,10 +124,16 @@ void SystemLogBase<LogElement>::add(const LogElement & element)
template <typename LogElement> template <typename LogElement>
void SystemLogBase<LogElement>::flush(bool force) void SystemLogBase<LogElement>::flush(bool force)
{ {
uint64_t this_thread_requested_offset = notifyFlushImpl(force); uint64_t this_thread_requested_offset = queue->notifyFlush(force);
if (this_thread_requested_offset == uint64_t(-1)) if (this_thread_requested_offset == uint64_t(-1))
return; return;
queue->waitFlush(this_thread_requested_offset);
}
template <typename LogElement>
void SystemLogQueue<LogElement>::waitFlush(uint64_t expected_flushed_up_to)
{
// Use an arbitrary timeout to avoid endless waiting. 60s proved to be // Use an arbitrary timeout to avoid endless waiting. 60s proved to be
// too fast for our parallel functional tests, probably because they // too fast for our parallel functional tests, probably because they
// heavily load the disk. // heavily load the disk.
@ -148,7 +141,7 @@ void SystemLogBase<LogElement>::flush(bool force)
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
bool result = flush_event.wait_for(lock, std::chrono::seconds(timeout_seconds), [&] bool result = flush_event.wait_for(lock, std::chrono::seconds(timeout_seconds), [&]
{ {
return flushed_up_to >= this_thread_requested_offset && !is_force_prepare_tables; return flushed_up_to >= expected_flushed_up_to && !is_force_prepare_tables;
}); });
if (!result) if (!result)
@ -159,10 +152,7 @@ void SystemLogBase<LogElement>::flush(bool force)
} }
template <typename LogElement> template <typename LogElement>
void SystemLogBase<LogElement>::notifyFlush(bool force) { notifyFlushImpl(force); } uint64_t SystemLogQueue<LogElement>::notifyFlush(bool should_prepare_tables_anyway)
template <typename LogElement>
uint64_t SystemLogBase<LogElement>::notifyFlushImpl(bool force)
{ {
uint64_t this_thread_requested_offset; uint64_t this_thread_requested_offset;
@ -175,7 +165,7 @@ uint64_t SystemLogBase<LogElement>::notifyFlushImpl(bool force)
// Publish our flush request, taking care not to overwrite the requests // Publish our flush request, taking care not to overwrite the requests
// made by other threads. // made by other threads.
is_force_prepare_tables |= force; is_force_prepare_tables |= should_prepare_tables_anyway;
requested_flush_up_to = std::max(requested_flush_up_to, this_thread_requested_offset); requested_flush_up_to = std::max(requested_flush_up_to, this_thread_requested_offset);
flush_event.notify_all(); flush_event.notify_all();
@ -185,7 +175,77 @@ uint64_t SystemLogBase<LogElement>::notifyFlushImpl(bool force)
return this_thread_requested_offset; return this_thread_requested_offset;
} }
template <typename LogElement>
void SystemLogQueue<LogElement>::confirm(uint64_t to_flush_end)
{
std::lock_guard lock(mutex);
flushed_up_to = to_flush_end;
is_force_prepare_tables = false;
flush_event.notify_all();
}
template <typename LogElement>
SystemLogQueue<LogElement>::Index SystemLogQueue<LogElement>::pop(std::vector<LogElement>& output, bool& should_prepare_tables_anyway, bool& exit_this_thread)
{
std::unique_lock lock(mutex);
flush_event.wait_for(lock,
std::chrono::milliseconds(flush_interval_milliseconds),
[&] ()
{
return requested_flush_up_to > flushed_up_to || is_shutdown || is_force_prepare_tables;
}
);
queue_front_index += queue.size();
// Swap with existing array from previous flush, to save memory
// allocations.
output.resize(0);
queue.swap(output);
should_prepare_tables_anyway = is_force_prepare_tables;
exit_this_thread = is_shutdown;
return queue_front_index;
}
template <typename LogElement>
void SystemLogQueue<LogElement>::shutdown()
{
std::unique_lock lock(mutex);
is_shutdown = true;
/// Tell thread to shutdown.
flush_event.notify_all();
}
template <typename LogElement>
SystemLogBase<LogElement>::SystemLogBase(
const String& table_name_,
size_t flush_interval_milliseconds_,
std::shared_ptr<SystemLogQueue<LogElement>> queue_)
: queue(queue_ ? queue_ : std::make_shared<SystemLogQueue<LogElement>>(table_name_, flush_interval_milliseconds_))
{
}
template <typename LogElement>
void SystemLogBase<LogElement>::startup()
{
std::lock_guard lock(thread_mutex);
saving_thread = std::make_unique<ThreadFromGlobalPool>([this] { savingThreadFunction(); });
}
template <typename LogElement>
void SystemLogBase<LogElement>::add(const LogElement & element)
{
queue->push(element);
}
template <typename LogElement>
void SystemLogBase<LogElement>::notifyFlush(bool force) { queue->notifyFlush(force); }
#define INSTANTIATE_SYSTEM_LOG_BASE(ELEMENT) template class SystemLogBase<ELEMENT>; #define INSTANTIATE_SYSTEM_LOG_BASE(ELEMENT) template class SystemLogBase<ELEMENT>;
SYSTEM_LOG_ELEMENTS(INSTANTIATE_SYSTEM_LOG_BASE) SYSTEM_LOG_ELEMENTS(INSTANTIATE_SYSTEM_LOG_BASE)
#define INSTANTIATE_SYSTEM_LOG_QUEUE(ELEMENT) template class SystemLogQueue<ELEMENT>;
SYSTEM_LOG_ELEMENTS(INSTANTIATE_SYSTEM_LOG_QUEUE)
} }

View File

@ -55,33 +55,88 @@ public:
virtual void prepareTable() = 0; virtual void prepareTable() = 0;
/// Start the background thread. /// Start the background thread.
virtual void startup(); virtual void startup() = 0;
/// Stop the background flush thread before destructor. No more data will be written. /// Stop the background flush thread before destructor. No more data will be written.
virtual void shutdown() = 0; virtual void shutdown() = 0;
virtual void stopFlushThread() = 0;
virtual ~ISystemLog(); virtual ~ISystemLog();
virtual void savingThreadFunction() = 0; virtual void savingThreadFunction() = 0;
protected: protected:
std::mutex thread_mutex;
std::unique_ptr<ThreadFromGlobalPool> saving_thread; std::unique_ptr<ThreadFromGlobalPool> saving_thread;
bool is_shutdown = false;
};
template <typename LogElement>
class SystemLogQueue
{
using Index = uint64_t;
public:
SystemLogQueue(
const String & table_name_,
size_t flush_interval_milliseconds_,
bool turn_off_logger_ = false);
void shutdown();
// producer methods
void push(const LogElement & element);
Index notifyFlush(bool should_prepare_tables_anyway);
void waitFlush(Index expected_flushed_up_to);
// consumer methods
Index pop(std::vector<LogElement>& output, bool& should_prepare_tables_anyway, bool& exit_this_thread);
void confirm(Index to_flush_end);
private:
/// Data shared between callers of add()/flush()/shutdown(), and the saving thread /// Data shared between callers of add()/flush()/shutdown(), and the saving thread
std::mutex mutex; std::mutex mutex;
bool is_shutdown = false; Poco::Logger * log;
std::condition_variable flush_event;
void stopFlushThread(); // Queue is bounded. But its size is quite large to not block in all normal cases.
std::vector<LogElement> queue;
// An always-incrementing index of the first message currently in the queue.
// We use it to give a global sequential index to every message, so that we
// can wait until a particular message is flushed. This is used to implement
// synchronous log flushing for SYSTEM FLUSH LOGS.
Index queue_front_index = 0;
// A flag that says we must create the tables even if the queue is empty.
bool is_force_prepare_tables = false;
// Requested to flush logs up to this index, exclusive
Index requested_flush_up_to = 0;
// Flushed log up to this index, exclusive
Index flushed_up_to = 0;
// Logged overflow message at this queue front index
Index logged_queue_full_at_index = -1;
bool is_shutdown = false;
std::condition_variable flush_event;
const size_t flush_interval_milliseconds;
}; };
template <typename LogElement> template <typename LogElement>
class SystemLogBase : public ISystemLog class SystemLogBase : public ISystemLog
{ {
public: public:
using Self = SystemLogBase; using Self = SystemLogBase;
SystemLogBase(
const String& table_name_,
size_t flush_interval_milliseconds_,
std::shared_ptr<SystemLogQueue<LogElement>> queue_ = nullptr);
void startup() override;
/** Append a record into log. /** Append a record into log.
* Writing to table will be done asynchronously and in case of failure, record could be lost. * Writing to table will be done asynchronously and in case of failure, record could be lost.
*/ */
@ -98,27 +153,6 @@ public:
static const char * getDefaultOrderBy() { return "event_date, event_time"; } static const char * getDefaultOrderBy() { return "event_date, event_time"; }
protected: protected:
Poco::Logger * log; std::shared_ptr<SystemLogQueue<LogElement>> queue;
// Queue is bounded. But its size is quite large to not block in all normal cases.
std::vector<LogElement> queue;
// An always-incrementing index of the first message currently in the queue.
// We use it to give a global sequential index to every message, so that we
// can wait until a particular message is flushed. This is used to implement
// synchronous log flushing for SYSTEM FLUSH LOGS.
uint64_t queue_front_index = 0;
// A flag that says we must create the tables even if the queue is empty.
bool is_force_prepare_tables = false;
// Requested to flush logs up to this index, exclusive
uint64_t requested_flush_up_to = 0;
// Flushed log up to this index, exclusive
uint64_t flushed_up_to = 0;
// Logged overflow message at this queue front index
uint64_t logged_queue_full_at_index = -1;
private:
uint64_t notifyFlushImpl(bool force);
}; };
} }

View File

@ -350,6 +350,29 @@ struct SyncResponse : virtual Response
size_t bytesSize() const override { return path.size(); } size_t bytesSize() const override { return path.size(); }
}; };
struct ReconfigRequest : virtual Request
{
String joining;
String leaving;
String new_members;
int32_t version;
String getPath() const final { return keeper_config_path; }
size_t bytesSize() const final
{
return joining.size() + leaving.size() + new_members.size() + sizeof(version);
}
};
struct ReconfigResponse : virtual Response
{
String value;
Stat stat;
size_t bytesSize() const override { return value.size() + sizeof(stat); }
};
struct MultiRequest : virtual Request struct MultiRequest : virtual Request
{ {
Requests requests; Requests requests;
@ -395,9 +418,9 @@ using SetCallback = std::function<void(const SetResponse &)>;
using ListCallback = std::function<void(const ListResponse &)>; using ListCallback = std::function<void(const ListResponse &)>;
using CheckCallback = std::function<void(const CheckResponse &)>; using CheckCallback = std::function<void(const CheckResponse &)>;
using SyncCallback = std::function<void(const SyncResponse &)>; using SyncCallback = std::function<void(const SyncResponse &)>;
using ReconfigCallback = std::function<void(const ReconfigResponse &)>;
using MultiCallback = std::function<void(const MultiResponse &)>; using MultiCallback = std::function<void(const MultiResponse &)>;
/// For watches. /// For watches.
enum State enum State
{ {
@ -526,6 +549,13 @@ public:
const String & path, const String & path,
SyncCallback callback) = 0; SyncCallback callback) = 0;
virtual void reconfig(
std::string_view joining,
std::string_view leaving,
std::string_view new_members,
int32_t version,
ReconfigCallback callback) = 0;
virtual void multi( virtual void multi(
const Requests & requests, const Requests & requests,
MultiCallback callback) = 0; MultiCallback callback) = 0;
@ -539,3 +569,11 @@ public:
}; };
} }
template <> struct fmt::formatter<Coordination::Error> : fmt::formatter<std::string_view>
{
constexpr auto format(Coordination::Error code, auto & ctx)
{
return formatter<string_view>::format(Coordination::errorMessage(code), ctx);
}
};

View File

@ -3,12 +3,8 @@
#include <Common/setThreadName.h> #include <Common/setThreadName.h>
#include <Common/StringUtils/StringUtils.h> #include <Common/StringUtils/StringUtils.h>
#include <base/types.h> #include <base/types.h>
#include <sstream>
#include <iomanip>
#include <functional> #include <functional>
namespace Coordination namespace Coordination
{ {
@ -147,6 +143,14 @@ struct TestKeeperSyncRequest final : SyncRequest, TestKeeperRequest
std::pair<ResponsePtr, Undo> process(TestKeeper::Container & container, int64_t zxid) const override; std::pair<ResponsePtr, Undo> process(TestKeeper::Container & container, int64_t zxid) const override;
}; };
struct TestKeeperReconfigRequest final : ReconfigRequest, TestKeeperRequest
{
TestKeeperReconfigRequest() = default;
explicit TestKeeperReconfigRequest(const ReconfigRequest & base) : ReconfigRequest(base) {}
ResponsePtr createResponse() const override;
std::pair<ResponsePtr, Undo> process(TestKeeper::Container & container, int64_t zxid) const override;
};
struct TestKeeperMultiRequest final : MultiRequest, TestKeeperRequest struct TestKeeperMultiRequest final : MultiRequest, TestKeeperRequest
{ {
explicit TestKeeperMultiRequest(const Requests & generic_requests) explicit TestKeeperMultiRequest(const Requests & generic_requests)
@ -226,15 +230,7 @@ std::pair<ResponsePtr, Undo> TestKeeperCreateRequest::process(TestKeeper::Contai
std::string path_created = path; std::string path_created = path;
if (is_sequential) if (is_sequential)
{ path_created += fmt::format("{:0>10}", it->second.seq_num);
auto seq_num = it->second.seq_num;
std::stringstream seq_num_str; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
seq_num_str.exceptions(std::ios::failbit);
seq_num_str << std::setw(10) << std::setfill('0') << seq_num;
path_created += seq_num_str.str();
}
/// Increment sequential number even if node is not sequential /// Increment sequential number even if node is not sequential
++it->second.seq_num; ++it->second.seq_num;
@ -446,6 +442,17 @@ std::pair<ResponsePtr, Undo> TestKeeperSyncRequest::process(TestKeeper::Containe
return { std::make_shared<SyncResponse>(std::move(response)), {} }; return { std::make_shared<SyncResponse>(std::move(response)), {} };
} }
std::pair<ResponsePtr, Undo> TestKeeperReconfigRequest::process(TestKeeper::Container &, int64_t) const
{
// In TestKeeper we assume data is stored on one server, so this is a dummy implementation to
// satisfy IKeeper interface.
// We can't even check the validity of input data, neither can we create the /keeper/config znode
// as we don't know the id of current "server".
ReconfigResponse response;
response.error = Error::ZOK;
return { std::make_shared<ReconfigResponse>(std::move(response)), {} };
}
std::pair<ResponsePtr, Undo> TestKeeperMultiRequest::process(TestKeeper::Container & container, int64_t zxid) const std::pair<ResponsePtr, Undo> TestKeeperMultiRequest::process(TestKeeper::Container & container, int64_t zxid) const
{ {
MultiResponse response; MultiResponse response;
@ -505,6 +512,7 @@ ResponsePtr TestKeeperSetRequest::createResponse() const { return std::make_shar
ResponsePtr TestKeeperListRequest::createResponse() const { return std::make_shared<ListResponse>(); } ResponsePtr TestKeeperListRequest::createResponse() const { return std::make_shared<ListResponse>(); }
ResponsePtr TestKeeperCheckRequest::createResponse() const { return std::make_shared<CheckResponse>(); } ResponsePtr TestKeeperCheckRequest::createResponse() const { return std::make_shared<CheckResponse>(); }
ResponsePtr TestKeeperSyncRequest::createResponse() const { return std::make_shared<SyncResponse>(); } ResponsePtr TestKeeperSyncRequest::createResponse() const { return std::make_shared<SyncResponse>(); }
ResponsePtr TestKeeperReconfigRequest::createResponse() const { return std::make_shared<ReconfigResponse>(); }
ResponsePtr TestKeeperMultiRequest::createResponse() const { return std::make_shared<MultiResponse>(); } ResponsePtr TestKeeperMultiRequest::createResponse() const { return std::make_shared<MultiResponse>(); }
@ -828,6 +836,28 @@ void TestKeeper::sync(
pushRequest(std::move(request_info)); pushRequest(std::move(request_info));
} }
void TestKeeper::reconfig(
std::string_view joining,
std::string_view leaving,
std::string_view new_members,
int32_t version,
ReconfigCallback callback)
{
TestKeeperReconfigRequest req;
req.joining = joining;
req.leaving = leaving;
req.new_members = new_members;
req.version = version;
pushRequest({
.request = std::make_shared<TestKeeperReconfigRequest>(std::move(req)),
.callback = [callback](const Response & response)
{
callback(dynamic_cast<const ReconfigResponse &>(response));
}
});
}
void TestKeeper::multi( void TestKeeper::multi(
const Requests & requests, const Requests & requests,
MultiCallback callback) MultiCallback callback)

View File

@ -87,6 +87,13 @@ public:
const String & path, const String & path,
SyncCallback callback) override; SyncCallback callback) override;
void reconfig(
std::string_view joining,
std::string_view leaving,
std::string_view new_members,
int32_t version,
ReconfigCallback callback) final;
void multi( void multi(
const Requests & requests, const Requests & requests,
MultiCallback callback) override; MultiCallback callback) override;

View File

@ -3,8 +3,10 @@
#include "KeeperException.h" #include "KeeperException.h"
#include "TestKeeper.h" #include "TestKeeper.h"
#include <functional>
#include <filesystem> #include <filesystem>
#include <functional>
#include <ranges>
#include <vector>
#include <Common/ZooKeeper/Types.h> #include <Common/ZooKeeper/Types.h>
#include <Common/ZooKeeper/ZooKeeperCommon.h> #include <Common/ZooKeeper/ZooKeeperCommon.h>
@ -75,13 +77,14 @@ void ZooKeeper::init(ZooKeeperArgs args_)
auto & host_string = host.host; auto & host_string = host.host;
try try
{ {
bool secure = startsWith(host_string, "secure://"); const bool secure = startsWith(host_string, "secure://");
if (secure) if (secure)
host_string.erase(0, strlen("secure://")); host_string.erase(0, strlen("secure://"));
LOG_TEST(log, "Adding ZooKeeper host {} ({})", host_string, Poco::Net::SocketAddress{host_string}.toString()); const Poco::Net::SocketAddress host_socket_addr{host_string};
nodes.emplace_back(Coordination::ZooKeeper::Node{Poco::Net::SocketAddress{host_string}, secure}); LOG_TEST(log, "Adding ZooKeeper host {} ({})", host_string, host_socket_addr.toString());
nodes.emplace_back(Coordination::ZooKeeper::Node{host_socket_addr, secure});
} }
catch (const Poco::Net::HostNotFoundException & e) catch (const Poco::Net::HostNotFoundException & e)
{ {
@ -191,12 +194,7 @@ std::vector<ShuffleHost> ZooKeeper::shuffleHosts() const
shuffle_hosts.emplace_back(shuffle_host); shuffle_hosts.emplace_back(shuffle_host);
} }
::sort( ::sort(shuffle_hosts.begin(), shuffle_hosts.end(), ShuffleHost::compare);
shuffle_hosts.begin(), shuffle_hosts.end(),
[](const ShuffleHost & lhs, const ShuffleHost & rhs)
{
return ShuffleHost::compare(lhs, rhs);
});
return shuffle_hosts; return shuffle_hosts;
} }
@ -231,7 +229,7 @@ Coordination::Error ZooKeeper::getChildrenImpl(const std::string & path, Strings
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::List), path)); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::List, path));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -298,7 +296,7 @@ Coordination::Error ZooKeeper::createImpl(const std::string & path, const std::s
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::Create), path)); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::Create, path));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -350,15 +348,35 @@ void ZooKeeper::createIfNotExists(const std::string & path, const std::string &
void ZooKeeper::createAncestors(const std::string & path) void ZooKeeper::createAncestors(const std::string & path)
{ {
size_t pos = 1; std::string data;
std::string path_created; // Ignored
std::vector<std::string> pending_nodes;
size_t last_pos = path.rfind('/');
if (last_pos == std::string::npos || last_pos == 0)
return;
std::string current_node = path.substr(0, last_pos);
while (true) while (true)
{ {
pos = path.find('/', pos); Coordination::Error code = createImpl(current_node, data, CreateMode::Persistent, path_created);
if (pos == std::string::npos) if (code == Coordination::Error::ZNONODE)
{
/// The parent node doesn't exist. Save the current node and try with the parent
last_pos = current_node.rfind('/');
if (last_pos == std::string::npos || last_pos == 0)
throw KeeperException(code, path);
pending_nodes.emplace_back(std::move(current_node));
current_node = path.substr(0, last_pos);
}
else if (code == Coordination::Error::ZOK || code == Coordination::Error::ZNODEEXISTS)
break; break;
createIfNotExists(path.substr(0, pos), ""); else
++pos; throw KeeperException(code, path);
} }
for (const std::string & pending : pending_nodes | std::views::reverse)
createIfNotExists(pending, data);
} }
void ZooKeeper::checkExistsAndGetCreateAncestorsOps(const std::string & path, Coordination::Requests & requests) void ZooKeeper::checkExistsAndGetCreateAncestorsOps(const std::string & path, Coordination::Requests & requests)
@ -393,7 +411,7 @@ Coordination::Error ZooKeeper::removeImpl(const std::string & path, int32_t vers
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::Remove), path)); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::Remove, path));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -425,7 +443,7 @@ Coordination::Error ZooKeeper::existsImpl(const std::string & path, Coordination
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::Exists), path)); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::Exists, path));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -459,7 +477,7 @@ Coordination::Error ZooKeeper::getImpl(const std::string & path, std::string & r
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::Get), path)); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::Get, path));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -531,7 +549,7 @@ Coordination::Error ZooKeeper::setImpl(const std::string & path, const std::stri
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::Set), path)); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::Set, path));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -583,7 +601,7 @@ Coordination::Error ZooKeeper::multiImpl(const Coordination::Requests & requests
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::Multi), requests[0]->getPath())); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::Multi, requests[0]->getPath()));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -617,7 +635,7 @@ Coordination::Error ZooKeeper::syncImpl(const std::string & path, std::string &
if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready) if (future_result.wait_for(std::chrono::milliseconds(args.operation_timeout_ms)) != std::future_status::ready)
{ {
impl->finalize(fmt::format("Operation timeout on {} {}", toString(Coordination::OpNum::Sync), path)); impl->finalize(fmt::format("Operation timeout on {} {}", Coordination::OpNum::Sync, path));
return Coordination::Error::ZOPERATIONTIMEOUT; return Coordination::Error::ZOPERATIONTIMEOUT;
} }
else else
@ -1229,7 +1247,7 @@ size_t getFailedOpIndex(Coordination::Error exception_code, const Coordination::
if (!Coordination::isUserError(exception_code)) if (!Coordination::isUserError(exception_code))
throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR,
"There are no failed OPs because '{}' is not valid response code for that", "There are no failed OPs because '{}' is not valid response code for that",
std::string(Coordination::errorMessage(exception_code))); exception_code);
throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "There is no failed OpResult"); throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "There is no failed OpResult");
} }

View File

@ -36,7 +36,7 @@ std::string ZooKeeperRequest::toString() const
"OpNum = {}\n" "OpNum = {}\n"
"Additional info:\n{}", "Additional info:\n{}",
xid, xid,
Coordination::toString(getOpNum()), getOpNum(),
toStringImpl()); toStringImpl());
} }
@ -76,6 +76,41 @@ void ZooKeeperSyncResponse::writeImpl(WriteBuffer & out) const
Coordination::write(path, out); Coordination::write(path, out);
} }
void ZooKeeperReconfigRequest::writeImpl(WriteBuffer & out) const
{
Coordination::write(joining, out);
Coordination::write(leaving, out);
Coordination::write(new_members, out);
Coordination::write(version, out);
}
void ZooKeeperReconfigRequest::readImpl(ReadBuffer & in)
{
Coordination::read(joining, in);
Coordination::read(leaving, in);
Coordination::read(new_members, in);
Coordination::read(version, in);
}
std::string ZooKeeperReconfigRequest::toStringImpl() const
{
return fmt::format(
"joining = {}\nleaving = {}\nnew_members = {}\nversion = {}",
joining, leaving, new_members, version);
}
void ZooKeeperReconfigResponse::readImpl(ReadBuffer & in)
{
Coordination::read(value, in);
Coordination::read(stat, in);
}
void ZooKeeperReconfigResponse::writeImpl(WriteBuffer & out) const
{
Coordination::write(value, out);
Coordination::write(stat, out);
}
void ZooKeeperWatchResponse::readImpl(ReadBuffer & in) void ZooKeeperWatchResponse::readImpl(ReadBuffer & in)
{ {
Coordination::read(type, in); Coordination::read(type, in);
@ -664,6 +699,7 @@ ZooKeeperResponsePtr ZooKeeperRemoveRequest::makeResponse() const { return setTi
ZooKeeperResponsePtr ZooKeeperExistsRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperExistsResponse>()); } ZooKeeperResponsePtr ZooKeeperExistsRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperExistsResponse>()); }
ZooKeeperResponsePtr ZooKeeperGetRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperGetResponse>()); } ZooKeeperResponsePtr ZooKeeperGetRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperGetResponse>()); }
ZooKeeperResponsePtr ZooKeeperSetRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperSetResponse>()); } ZooKeeperResponsePtr ZooKeeperSetRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperSetResponse>()); }
ZooKeeperResponsePtr ZooKeeperReconfigRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperReconfigResponse>()); }
ZooKeeperResponsePtr ZooKeeperListRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperListResponse>()); } ZooKeeperResponsePtr ZooKeeperListRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperListResponse>()); }
ZooKeeperResponsePtr ZooKeeperSimpleListRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperSimpleListResponse>()); } ZooKeeperResponsePtr ZooKeeperSimpleListRequest::makeResponse() const { return setTime(std::make_shared<ZooKeeperSimpleListResponse>()); }
@ -861,7 +897,8 @@ void ZooKeeperMultiResponse::fillLogElements(LogElements & elems, size_t idx) co
void ZooKeeperRequestFactory::registerRequest(OpNum op_num, Creator creator) void ZooKeeperRequestFactory::registerRequest(OpNum op_num, Creator creator)
{ {
if (!op_num_to_request.try_emplace(op_num, creator).second) if (!op_num_to_request.try_emplace(op_num, creator).second)
throw Coordination::Exception("Request type " + toString(op_num) + " already registered", Coordination::Error::ZRUNTIMEINCONSISTENCY); throw Coordination::Exception(Coordination::Error::ZRUNTIMEINCONSISTENCY,
"Request type {} already registered", op_num);
} }
std::shared_ptr<ZooKeeperRequest> ZooKeeperRequest::read(ReadBuffer & in) std::shared_ptr<ZooKeeperRequest> ZooKeeperRequest::read(ReadBuffer & in)
@ -916,7 +953,7 @@ ZooKeeperRequestPtr ZooKeeperRequestFactory::get(OpNum op_num) const
{ {
auto it = op_num_to_request.find(op_num); auto it = op_num_to_request.find(op_num);
if (it == op_num_to_request.end()) if (it == op_num_to_request.end())
throw Exception("Unknown operation type " + toString(op_num), Error::ZBADARGUMENTS); throw Exception(Error::ZBADARGUMENTS, "Unknown operation type {}", op_num);
return it->second(); return it->second();
} }
@ -960,6 +997,7 @@ ZooKeeperRequestFactory::ZooKeeperRequestFactory()
registerZooKeeperRequest<OpNum::SimpleList, ZooKeeperSimpleListRequest>(*this); registerZooKeeperRequest<OpNum::SimpleList, ZooKeeperSimpleListRequest>(*this);
registerZooKeeperRequest<OpNum::List, ZooKeeperListRequest>(*this); registerZooKeeperRequest<OpNum::List, ZooKeeperListRequest>(*this);
registerZooKeeperRequest<OpNum::Check, ZooKeeperCheckRequest>(*this); registerZooKeeperRequest<OpNum::Check, ZooKeeperCheckRequest>(*this);
registerZooKeeperRequest<OpNum::Reconfig, ZooKeeperReconfigRequest>(*this);
registerZooKeeperRequest<OpNum::Multi, ZooKeeperMultiRequest>(*this); registerZooKeeperRequest<OpNum::Multi, ZooKeeperMultiRequest>(*this);
registerZooKeeperRequest<OpNum::MultiRead, ZooKeeperMultiRequest>(*this); registerZooKeeperRequest<OpNum::MultiRead, ZooKeeperMultiRequest>(*this);
registerZooKeeperRequest<OpNum::SessionID, ZooKeeperSessionIDRequest>(*this); registerZooKeeperRequest<OpNum::SessionID, ZooKeeperSessionIDRequest>(*this);

View File

@ -117,6 +117,35 @@ struct ZooKeeperSyncResponse final : SyncResponse, ZooKeeperResponse
OpNum getOpNum() const override { return OpNum::Sync; } OpNum getOpNum() const override { return OpNum::Sync; }
}; };
struct ZooKeeperReconfigRequest final : ZooKeeperRequest
{
String joining;
String leaving;
String new_members;
int64_t version; // kazoo sends a 64bit integer in this request
String getPath() const override { return keeper_config_path; }
OpNum getOpNum() const override { return OpNum::Reconfig; }
void writeImpl(WriteBuffer & out) const override;
void readImpl(ReadBuffer & in) override;
std::string toStringImpl() const override;
ZooKeeperResponsePtr makeResponse() const override;
bool isReadRequest() const override { return false; }
size_t bytesSize() const override
{
return ZooKeeperRequest::bytesSize() + joining.size() + leaving.size() + new_members.size()
+ sizeof(version);
}
};
struct ZooKeeperReconfigResponse final : ReconfigResponse, ZooKeeperResponse
{
void readImpl(ReadBuffer & in) override;
void writeImpl(WriteBuffer & out) const override;
OpNum getOpNum() const override { return OpNum::Reconfig; }
};
struct ZooKeeperHeartbeatResponse final : ZooKeeperResponse struct ZooKeeperHeartbeatResponse final : ZooKeeperResponse
{ {
void readImpl(ReadBuffer &) override {} void readImpl(ReadBuffer &) override {}

View File

@ -19,6 +19,7 @@ static const std::unordered_set<int32_t> VALID_OPERATIONS =
static_cast<int32_t>(OpNum::Heartbeat), static_cast<int32_t>(OpNum::Heartbeat),
static_cast<int32_t>(OpNum::List), static_cast<int32_t>(OpNum::List),
static_cast<int32_t>(OpNum::Check), static_cast<int32_t>(OpNum::Check),
static_cast<int32_t>(OpNum::Reconfig),
static_cast<int32_t>(OpNum::Multi), static_cast<int32_t>(OpNum::Multi),
static_cast<int32_t>(OpNum::MultiRead), static_cast<int32_t>(OpNum::MultiRead),
static_cast<int32_t>(OpNum::Auth), static_cast<int32_t>(OpNum::Auth),
@ -29,55 +30,6 @@ static const std::unordered_set<int32_t> VALID_OPERATIONS =
static_cast<int32_t>(OpNum::CheckNotExists), static_cast<int32_t>(OpNum::CheckNotExists),
}; };
std::string toString(OpNum op_num)
{
switch (op_num)
{
case OpNum::Close:
return "Close";
case OpNum::Error:
return "Error";
case OpNum::Create:
return "Create";
case OpNum::Remove:
return "Remove";
case OpNum::Exists:
return "Exists";
case OpNum::Get:
return "Get";
case OpNum::Set:
return "Set";
case OpNum::SimpleList:
return "SimpleList";
case OpNum::List:
return "List";
case OpNum::Check:
return "Check";
case OpNum::Multi:
return "Multi";
case OpNum::MultiRead:
return "MultiRead";
case OpNum::Sync:
return "Sync";
case OpNum::Heartbeat:
return "Heartbeat";
case OpNum::Auth:
return "Auth";
case OpNum::SessionID:
return "SessionID";
case OpNum::SetACL:
return "SetACL";
case OpNum::GetACL:
return "GetACL";
case OpNum::FilteredList:
return "FilteredList";
case OpNum::CheckNotExists:
return "CheckNotExists";
}
int32_t raw_op = static_cast<int32_t>(op_num);
throw Exception("Operation " + std::to_string(raw_op) + " is unknown", Error::ZUNIMPLEMENTED);
}
OpNum getOpNum(int32_t raw_op_num) OpNum getOpNum(int32_t raw_op_num)
{ {
if (!VALID_OPERATIONS.contains(raw_op_num)) if (!VALID_OPERATIONS.contains(raw_op_num))

View File

@ -31,6 +31,7 @@ enum class OpNum : int32_t
List = 12, List = 12,
Check = 13, Check = 13,
Multi = 14, Multi = 14,
Reconfig = 16,
MultiRead = 22, MultiRead = 22,
Auth = 100, Auth = 100,
@ -41,7 +42,6 @@ enum class OpNum : int32_t
SessionID = 997, /// Special internal request SessionID = 997, /// Special internal request
}; };
std::string toString(OpNum op_num);
OpNum getOpNum(int32_t raw_op_num); OpNum getOpNum(int32_t raw_op_num);
static constexpr int32_t ZOOKEEPER_PROTOCOL_VERSION = 0; static constexpr int32_t ZOOKEEPER_PROTOCOL_VERSION = 0;

View File

@ -35,6 +35,7 @@ namespace ProfileEvents
extern const Event ZooKeeperRemove; extern const Event ZooKeeperRemove;
extern const Event ZooKeeperExists; extern const Event ZooKeeperExists;
extern const Event ZooKeeperMulti; extern const Event ZooKeeperMulti;
extern const Event ZooKeeperReconfig;
extern const Event ZooKeeperGet; extern const Event ZooKeeperGet;
extern const Event ZooKeeperSet; extern const Event ZooKeeperSet;
extern const Event ZooKeeperList; extern const Event ZooKeeperList;
@ -571,7 +572,7 @@ void ZooKeeper::sendAuth(const String & scheme, const String & data)
if (err != Error::ZOK) if (err != Error::ZOK)
throw Exception(Error::ZMARSHALLINGERROR, "Error received in reply to auth request. Code: {}. Message: {}", throw Exception(Error::ZMARSHALLINGERROR, "Error received in reply to auth request. Code: {}. Message: {}",
static_cast<int32_t>(err), errorMessage(err)); static_cast<int32_t>(err), err);
} }
void ZooKeeper::sendThread() void ZooKeeper::sendThread()
@ -697,7 +698,7 @@ void ZooKeeper::receiveThread()
if (earliest_operation) if (earliest_operation)
{ {
throw Exception(Error::ZOPERATIONTIMEOUT, "Operation timeout (no response in {} ms) for request {} for path: {}", throw Exception(Error::ZOPERATIONTIMEOUT, "Operation timeout (no response in {} ms) for request {} for path: {}",
args.operation_timeout_ms, toString(earliest_operation->request->getOpNum()), earliest_operation->request->getPath()); args.operation_timeout_ms, earliest_operation->request->getOpNum(), earliest_operation->request->getPath());
} }
waited_us += max_wait_us; waited_us += max_wait_us;
if (waited_us >= args.session_timeout_ms * 1000) if (waited_us >= args.session_timeout_ms * 1000)
@ -738,7 +739,7 @@ void ZooKeeper::receiveEvent()
if (xid == PING_XID) if (xid == PING_XID)
{ {
if (err != Error::ZOK) if (err != Error::ZOK)
throw Exception(Error::ZRUNTIMEINCONSISTENCY, "Received error in heartbeat response: {}", errorMessage(err)); throw Exception(Error::ZRUNTIMEINCONSISTENCY, "Received error in heartbeat response: {}", err);
response = std::make_shared<ZooKeeperHeartbeatResponse>(); response = std::make_shared<ZooKeeperHeartbeatResponse>();
} }
@ -1195,7 +1196,6 @@ void ZooKeeper::create(
ProfileEvents::increment(ProfileEvents::ZooKeeperCreate); ProfileEvents::increment(ProfileEvents::ZooKeeperCreate);
} }
void ZooKeeper::remove( void ZooKeeper::remove(
const String & path, const String & path,
int32_t version, int32_t version,
@ -1335,6 +1335,26 @@ void ZooKeeper::sync(
ProfileEvents::increment(ProfileEvents::ZooKeeperSync); ProfileEvents::increment(ProfileEvents::ZooKeeperSync);
} }
void ZooKeeper::reconfig(
std::string_view joining,
std::string_view leaving,
std::string_view new_members,
int32_t version,
ReconfigCallback callback)
{
ZooKeeperReconfigRequest request;
request.joining = joining;
request.leaving = leaving;
request.new_members = new_members;
request.version = version;
RequestInfo request_info;
request_info.request = std::make_shared<ZooKeeperReconfigRequest>(std::move(request));
request_info.callback = [callback](const Response & response) { callback(dynamic_cast<const ReconfigResponse &>(response)); };
pushRequest(std::move(request_info));
ProfileEvents::increment(ProfileEvents::ZooKeeperReconfig);
}
void ZooKeeper::multi( void ZooKeeper::multi(
const Requests & requests, const Requests & requests,

View File

@ -178,6 +178,13 @@ public:
const String & path, const String & path,
SyncCallback callback) override; SyncCallback callback) override;
void reconfig(
std::string_view joining,
std::string_view leaving,
std::string_view new_members,
int32_t version,
ReconfigCallback callback) final;
void multi( void multi(
const Requests & requests, const Requests & requests,
MultiCallback callback) override; MultiCallback callback) override;

View File

@ -54,6 +54,7 @@
#cmakedefine01 USE_BORINGSSL #cmakedefine01 USE_BORINGSSL
#cmakedefine01 USE_BLAKE3 #cmakedefine01 USE_BLAKE3
#cmakedefine01 USE_SKIM #cmakedefine01 USE_SKIM
#cmakedefine01 USE_PRQL
#cmakedefine01 USE_OPENSSL_INTREE #cmakedefine01 USE_OPENSSL_INTREE
#cmakedefine01 USE_ULID #cmakedefine01 USE_ULID
#cmakedefine01 FIU_ENABLE #cmakedefine01 FIU_ENABLE

View File

@ -1,5 +1,4 @@
#pragma once #pragma once
#include <IO/WriteHelpers.h> #include <IO/WriteHelpers.h>
namespace DB namespace DB
@ -14,8 +13,8 @@ enum class KeeperApiVersion : uint8_t
WITH_CHECK_NOT_EXISTS, WITH_CHECK_NOT_EXISTS,
}; };
const std::string keeper_system_path = "/keeper"; const String keeper_system_path = "/keeper";
const std::string keeper_api_version_path = keeper_system_path + "/api_version"; const String keeper_api_version_path = keeper_system_path + "/api_version";
const std::string keeper_api_feature_flags_path = keeper_system_path + "/feature_flags"; const String keeper_api_feature_flags_path = keeper_system_path + "/feature_flags";
const String keeper_config_path = keeper_system_path + "/config";
} }

View File

@ -32,8 +32,9 @@ KeeperContext::KeeperContext(bool standalone_keeper_)
system_nodes_with_data[keeper_api_version_path] = toString(static_cast<uint8_t>(KeeperApiVersion::WITH_MULTI_READ)); system_nodes_with_data[keeper_api_version_path] = toString(static_cast<uint8_t>(KeeperApiVersion::WITH_MULTI_READ));
} }
void KeeperContext::initialize(const Poco::Util::AbstractConfiguration & config) void KeeperContext::initialize(const Poco::Util::AbstractConfiguration & config, KeeperDispatcher * dispatcher_)
{ {
dispatcher = dispatcher_;
digest_enabled = config.getBool("keeper_server.digest_enabled", false); digest_enabled = config.getBool("keeper_server.digest_enabled", false);
ignore_system_path_on_startup = config.getBool("keeper_server.ignore_system_path_on_startup", false); ignore_system_path_on_startup = config.getBool("keeper_server.ignore_system_path_on_startup", false);
@ -41,9 +42,38 @@ void KeeperContext::initialize(const Poco::Util::AbstractConfiguration & config)
initializeDisks(config); initializeDisks(config);
} }
namespace
{
bool diskValidator(const Poco::Util::AbstractConfiguration & config, const std::string & disk_config_prefix)
{
const auto disk_type = config.getString(disk_config_prefix + ".type", "local");
using namespace std::literals;
static constexpr std::array supported_disk_types
{
"s3"sv,
"s3_plain"sv,
"local"sv
};
if (std::all_of(
supported_disk_types.begin(),
supported_disk_types.end(),
[&](const auto supported_type) { return disk_type != supported_type; }))
{
LOG_INFO(&Poco::Logger::get("KeeperContext"), "Disk type '{}' is not supported for Keeper", disk_type);
return false;
}
return true;
}
}
void KeeperContext::initializeDisks(const Poco::Util::AbstractConfiguration & config) void KeeperContext::initializeDisks(const Poco::Util::AbstractConfiguration & config)
{ {
disk_selector->initialize(config, "storage_configuration.disks", Context::getGlobalContextInstance()); disk_selector->initialize(config, "storage_configuration.disks", Context::getGlobalContextInstance(), diskValidator);
log_storage = getLogsPathFromConfig(config); log_storage = getLogsPathFromConfig(config);

View File

@ -1,10 +1,8 @@
#pragma once #pragma once
#include <Poco/Util/AbstractConfiguration.h>
#include <Coordination/KeeperFeatureFlags.h> #include <Coordination/KeeperFeatureFlags.h>
#include <IO/WriteBufferFromString.h>
#include <Disks/DiskSelector.h> #include <Disks/DiskSelector.h>
#include <IO/WriteBufferFromString.h>
#include <Poco/Util/AbstractConfiguration.h>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@ -12,6 +10,8 @@
namespace DB namespace DB
{ {
class KeeperDispatcher;
class KeeperContext class KeeperContext
{ {
public: public:
@ -24,7 +24,7 @@ public:
SHUTDOWN SHUTDOWN
}; };
void initialize(const Poco::Util::AbstractConfiguration & config); void initialize(const Poco::Util::AbstractConfiguration & config, KeeperDispatcher * dispatcher_);
Phase getServerState() const; Phase getServerState() const;
void setServerState(Phase server_state_); void setServerState(Phase server_state_);
@ -51,6 +51,9 @@ public:
const KeeperFeatureFlags & getFeatureFlags() const; const KeeperFeatureFlags & getFeatureFlags() const;
void dumpConfiguration(WriteBufferFromOwnString & buf) const; void dumpConfiguration(WriteBufferFromOwnString & buf) const;
constexpr KeeperDispatcher * getDispatcher() const { return dispatcher; }
private: private:
/// local disk defined using path or disk name /// local disk defined using path or disk name
using Storage = std::variant<DiskPtr, std::string>; using Storage = std::variant<DiskPtr, std::string>;
@ -85,8 +88,8 @@ private:
std::unordered_map<std::string, std::string> system_nodes_with_data; std::unordered_map<std::string, std::string> system_nodes_with_data;
KeeperFeatureFlags feature_flags; KeeperFeatureFlags feature_flags;
KeeperDispatcher * dispatcher{nullptr};
}; };
using KeeperContextPtr = std::shared_ptr<KeeperContext>; using KeeperContextPtr = std::shared_ptr<KeeperContext>;
} }

View File

@ -38,6 +38,8 @@ namespace ProfileEvents
extern const Event MemoryAllocatorPurgeTimeMicroseconds; extern const Event MemoryAllocatorPurgeTimeMicroseconds;
} }
using namespace std::chrono_literals;
namespace DB namespace DB
{ {
@ -80,6 +82,7 @@ void KeeperDispatcher::requestThread()
/// requests into a batch we must check that the new request is not read request. Otherwise we have to /// requests into a batch we must check that the new request is not read request. Otherwise we have to
/// process all already accumulated write requests, wait them synchronously and only after that process /// process all already accumulated write requests, wait them synchronously and only after that process
/// read request. So reads are some kind of "separator" for writes. /// read request. So reads are some kind of "separator" for writes.
/// Also there is a special reconfig request also being a separator.
try try
{ {
if (requests_queue->tryPop(request, max_wait)) if (requests_queue->tryPop(request, max_wait))
@ -92,10 +95,13 @@ void KeeperDispatcher::requestThread()
size_t current_batch_bytes_size = 0; size_t current_batch_bytes_size = 0;
bool has_read_request = false; bool has_read_request = false;
bool has_reconfig_request = false;
/// If new request is not read request or we must to process it through quorum. /// If new request is not read request or reconfig request we must process it through quorum.
/// Otherwise we will process it locally. /// Otherwise we will process it locally.
if (coordination_settings->quorum_reads || !request.request->isReadRequest()) if (request.request->getOpNum() == Coordination::OpNum::Reconfig)
has_reconfig_request = true;
else if (coordination_settings->quorum_reads || !request.request->isReadRequest())
{ {
current_batch_bytes_size += request.request->bytesSize(); current_batch_bytes_size += request.request->bytesSize();
current_batch.emplace_back(request); current_batch.emplace_back(request);
@ -113,6 +119,11 @@ void KeeperDispatcher::requestThread()
std::lock_guard lock(read_request_queue_mutex); std::lock_guard lock(read_request_queue_mutex);
read_request_queue[last_request.session_id][last_request.request->xid].push_back(request); read_request_queue[last_request.session_id][last_request.request->xid].push_back(request);
} }
else if (request.request->getOpNum() == Coordination::OpNum::Reconfig)
{
has_reconfig_request = true;
return false;
}
else else
{ {
current_batch_bytes_size += request.request->bytesSize(); current_batch_bytes_size += request.request->bytesSize();
@ -128,6 +139,7 @@ void KeeperDispatcher::requestThread()
/// TODO: Deprecate max_requests_quick_batch_size and use only max_requests_batch_size and max_requests_batch_bytes_size /// TODO: Deprecate max_requests_quick_batch_size and use only max_requests_batch_size and max_requests_batch_bytes_size
size_t max_quick_batch_size = coordination_settings->max_requests_quick_batch_size; size_t max_quick_batch_size = coordination_settings->max_requests_quick_batch_size;
while (!shutdown_called && !has_read_request && while (!shutdown_called && !has_read_request &&
!has_reconfig_request &&
current_batch.size() < max_quick_batch_size && current_batch_bytes_size < max_batch_bytes_size && current_batch.size() < max_quick_batch_size && current_batch_bytes_size < max_batch_bytes_size &&
try_get_request()) try_get_request())
; ;
@ -140,8 +152,10 @@ void KeeperDispatcher::requestThread()
}; };
/// Waiting until previous append will be successful, or batch is big enough /// Waiting until previous append will be successful, or batch is big enough
while (!shutdown_called && !has_read_request && !prev_result_done() && while (!shutdown_called && !has_read_request &&
current_batch.size() <= max_batch_size && current_batch_bytes_size < max_batch_bytes_size) !has_reconfig_request && !prev_result_done() &&
current_batch.size() <= max_batch_size
&& current_batch_bytes_size < max_batch_bytes_size)
{ {
try_get_request(); try_get_request();
} }
@ -165,7 +179,8 @@ void KeeperDispatcher::requestThread()
if (result) if (result)
{ {
if (has_read_request) /// If we will execute read request next, than we have to process result now /// If we will execute read or reconfig next, we have to process result now
if (has_read_request || has_reconfig_request)
forceWaitAndProcessResult(result, current_batch); forceWaitAndProcessResult(result, current_batch);
} }
else else
@ -179,6 +194,9 @@ void KeeperDispatcher::requestThread()
prev_result = result; prev_result = result;
} }
if (has_reconfig_request)
server->getKeeperStateMachine()->reconfigure(request);
/// Read request always goes after write batch (last request) /// Read request always goes after write batch (last request)
if (has_read_request) if (has_read_request)
{ {
@ -335,7 +353,7 @@ void KeeperDispatcher::initialize(const Poco::Util::AbstractConfiguration & conf
snapshot_s3.startup(config, macros); snapshot_s3.startup(config, macros);
keeper_context = std::make_shared<KeeperContext>(standalone_keeper); keeper_context = std::make_shared<KeeperContext>(standalone_keeper);
keeper_context->initialize(config); keeper_context->initialize(config, this);
server = std::make_unique<KeeperServer>( server = std::make_unique<KeeperServer>(
configuration_and_settings, configuration_and_settings,
@ -392,7 +410,10 @@ void KeeperDispatcher::initialize(const Poco::Util::AbstractConfiguration & conf
/// Start it after keeper server start /// Start it after keeper server start
session_cleaner_thread = ThreadFromGlobalPool([this] { sessionCleanerTask(); }); session_cleaner_thread = ThreadFromGlobalPool([this] { sessionCleanerTask(); });
update_configuration_thread = ThreadFromGlobalPool([this] { updateConfigurationThread(); });
update_configuration_thread = reconfigEnabled()
? ThreadFromGlobalPool([this] { clusterUpdateThread(); })
: ThreadFromGlobalPool([this] { clusterUpdateWithReconfigDisabledThread(); });
LOG_DEBUG(log, "Dispatcher initialized"); LOG_DEBUG(log, "Dispatcher initialized");
} }
@ -429,7 +450,7 @@ void KeeperDispatcher::shutdown()
if (snapshot_thread.joinable()) if (snapshot_thread.joinable())
snapshot_thread.join(); snapshot_thread.join();
update_configuration_queue.finish(); cluster_update_queue.finish();
if (update_configuration_thread.joinable()) if (update_configuration_thread.joinable())
update_configuration_thread.join(); update_configuration_thread.join();
} }
@ -473,23 +494,30 @@ void KeeperDispatcher::shutdown()
session_to_response_callback.clear(); session_to_response_callback.clear();
} }
// if there is no leader, there is no reason to do CLOSE because it's a write request if (server && !close_requests.empty())
if (server && hasLeader() && !close_requests.empty())
{ {
LOG_INFO(log, "Trying to close {} session(s)", close_requests.size()); // if there is no leader, there is no reason to do CLOSE because it's a write request
const auto raft_result = server->putRequestBatch(close_requests); if (hasLeader())
auto sessions_closing_done_promise = std::make_shared<std::promise<void>>(); {
auto sessions_closing_done = sessions_closing_done_promise->get_future(); LOG_INFO(log, "Trying to close {} session(s)", close_requests.size());
raft_result->when_ready([my_sessions_closing_done_promise = std::move(sessions_closing_done_promise)]( const auto raft_result = server->putRequestBatch(close_requests);
nuraft::cmd_result<nuraft::ptr<nuraft::buffer>> & /*result*/, auto sessions_closing_done_promise = std::make_shared<std::promise<void>>();
nuraft::ptr<std::exception> & /*exception*/) { my_sessions_closing_done_promise->set_value(); }); auto sessions_closing_done = sessions_closing_done_promise->get_future();
raft_result->when_ready([my_sessions_closing_done_promise = std::move(sessions_closing_done_promise)](
nuraft::cmd_result<nuraft::ptr<nuraft::buffer>> & /*result*/,
nuraft::ptr<std::exception> & /*exception*/) { my_sessions_closing_done_promise->set_value(); });
auto session_shutdown_timeout = configuration_and_settings->coordination_settings->session_shutdown_timeout.totalMilliseconds(); auto session_shutdown_timeout = configuration_and_settings->coordination_settings->session_shutdown_timeout.totalMilliseconds();
if (sessions_closing_done.wait_for(std::chrono::milliseconds(session_shutdown_timeout)) != std::future_status::ready) if (sessions_closing_done.wait_for(std::chrono::milliseconds(session_shutdown_timeout)) != std::future_status::ready)
LOG_WARNING( LOG_WARNING(
log, log,
"Failed to close sessions in {}ms. If they are not closed, they will be closed after session timeout.", "Failed to close sessions in {}ms. If they are not closed, they will be closed after session timeout.",
session_shutdown_timeout); session_shutdown_timeout);
}
else
{
LOG_INFO(log, "Sessions cannot be closed during shutdown because there is no active leader");
}
} }
if (server) if (server)
@ -608,7 +636,7 @@ void KeeperDispatcher::addErrorResponses(const KeeperStorage::RequestsForSession
"Could not push error response xid {} zxid {} error message {} to responses queue", "Could not push error response xid {} zxid {} error message {} to responses queue",
response->xid, response->xid,
response->zxid, response->zxid,
errorMessage(error)); error);
} }
} }
@ -653,7 +681,7 @@ int64_t KeeperDispatcher::getSessionID(int64_t session_timeout_ms)
{ {
if (response->getOpNum() != Coordination::OpNum::SessionID) if (response->getOpNum() != Coordination::OpNum::SessionID)
promise->set_exception(std::make_exception_ptr(Exception(ErrorCodes::LOGICAL_ERROR, promise->set_exception(std::make_exception_ptr(Exception(ErrorCodes::LOGICAL_ERROR,
"Incorrect response of type {} instead of SessionID response", Coordination::toString(response->getOpNum())))); "Incorrect response of type {} instead of SessionID response", response->getOpNum())));
auto session_id_response = dynamic_cast<const Coordination::ZooKeeperSessionIDResponse &>(*response); auto session_id_response = dynamic_cast<const Coordination::ZooKeeperSessionIDResponse &>(*response);
if (session_id_response.internal_id != internal_id) if (session_id_response.internal_id != internal_id)
@ -685,17 +713,12 @@ int64_t KeeperDispatcher::getSessionID(int64_t session_timeout_ms)
return future.get(); return future.get();
} }
void KeeperDispatcher::clusterUpdateWithReconfigDisabledThread()
void KeeperDispatcher::updateConfigurationThread()
{ {
while (true) while (!shutdown_called)
{ {
if (shutdown_called)
return;
try try
{ {
using namespace std::chrono_literals;
if (!server->checkInit()) if (!server->checkInit())
{ {
LOG_INFO(log, "Server still not initialized, will not apply configuration until initialization finished"); LOG_INFO(log, "Server still not initialized, will not apply configuration until initialization finished");
@ -710,11 +733,10 @@ void KeeperDispatcher::updateConfigurationThread()
continue; continue;
} }
ConfigUpdateAction action; ClusterUpdateAction action;
if (!update_configuration_queue.pop(action)) if (!cluster_update_queue.pop(action))
break; break;
/// We must wait this update from leader or apply it ourself (if we are leader) /// We must wait this update from leader or apply it ourself (if we are leader)
bool done = false; bool done = false;
while (!done) while (!done)
@ -727,15 +749,13 @@ void KeeperDispatcher::updateConfigurationThread()
if (isLeader()) if (isLeader())
{ {
server->applyConfigurationUpdate(action); server->applyConfigUpdateWithReconfigDisabled(action);
done = true; done = true;
} }
else else if (done = server->waitForConfigUpdateWithReconfigDisabled(action); !done)
{ LOG_INFO(log,
done = server->waitConfigurationUpdate(action); "Cannot wait for configuration update, maybe we became leader "
if (!done) "or maybe update is invalid, will try to wait one more time");
LOG_INFO(log, "Cannot wait for configuration update, maybe we become leader, or maybe update is invalid, will try to wait one more time");
}
} }
} }
catch (...) catch (...)
@ -745,6 +765,41 @@ void KeeperDispatcher::updateConfigurationThread()
} }
} }
void KeeperDispatcher::clusterUpdateThread()
{
while (!shutdown_called)
{
ClusterUpdateAction action;
if (!cluster_update_queue.pop(action))
return;
if (server->applyConfigUpdate(action))
LOG_DEBUG(log, "Processing config update {}: accepted", action);
else // TODO (myrrc) sleep a random amount? sleep less?
{
(void)cluster_update_queue.pushFront(action);
LOG_DEBUG(log, "Processing config update {}: declined, backoff", action);
std::this_thread::sleep_for(50ms);
}
}
}
void KeeperDispatcher::pushClusterUpdates(ClusterUpdateActions && actions)
{
if (shutdown_called) return;
for (auto && action : actions)
{
if (!cluster_update_queue.push(std::move(action)))
throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot push configuration update");
LOG_DEBUG(log, "Processing config update {}: pushed", action);
}
}
bool KeeperDispatcher::reconfigEnabled() const
{
return server->reconfigEnabled();
}
bool KeeperDispatcher::isServerActive() const bool KeeperDispatcher::isServerActive() const
{ {
return checkInit() && hasLeader() && !server->isRecovering(); return checkInit() && hasLeader() && !server->isRecovering();
@ -752,20 +807,25 @@ bool KeeperDispatcher::isServerActive() const
void KeeperDispatcher::updateConfiguration(const Poco::Util::AbstractConfiguration & config, const MultiVersion<Macros>::Version & macros) void KeeperDispatcher::updateConfiguration(const Poco::Util::AbstractConfiguration & config, const MultiVersion<Macros>::Version & macros)
{ {
auto diff = server->getConfigurationDiff(config); auto diff = server->getRaftConfigurationDiff(config);
if (diff.empty()) if (diff.empty())
LOG_TRACE(log, "Configuration update triggered, but nothing changed for RAFT"); LOG_TRACE(log, "Configuration update triggered, but nothing changed for Raft");
else if (reconfigEnabled())
LOG_WARNING(log,
"Raft configuration changed, but keeper_server.enable_reconfiguration is on. "
"This update will be ignored. Use \"reconfig\" instead");
else if (diff.size() > 1) else if (diff.size() > 1)
LOG_WARNING(log, "Configuration changed for more than one server ({}) from cluster, it's strictly not recommended", diff.size()); LOG_WARNING(log,
"Configuration changed for more than one server ({}) from cluster, "
"it's strictly not recommended", diff.size());
else else
LOG_DEBUG(log, "Configuration change size ({})", diff.size()); LOG_DEBUG(log, "Configuration change size ({})", diff.size());
for (auto & change : diff) if (!reconfigEnabled())
{ for (auto & change : diff)
bool push_result = update_configuration_queue.push(change); if (!cluster_update_queue.push(change))
if (!push_result) throw Exception(ErrorCodes::SYSTEM_ERROR, "Cannot push configuration update to queue");
throw Exception(ErrorCodes::SYSTEM_ERROR, "Cannot push configuration update to queue");
}
snapshot_s3.updateS3Configuration(config, macros); snapshot_s3.updateS3Configuration(config, macros);
} }

View File

@ -31,7 +31,7 @@ private:
using RequestsQueue = ConcurrentBoundedQueue<KeeperStorage::RequestForSession>; using RequestsQueue = ConcurrentBoundedQueue<KeeperStorage::RequestForSession>;
using SessionToResponseCallback = std::unordered_map<int64_t, ZooKeeperResponseCallback>; using SessionToResponseCallback = std::unordered_map<int64_t, ZooKeeperResponseCallback>;
using UpdateConfigurationQueue = ConcurrentBoundedQueue<ConfigUpdateAction>; using ClusterUpdateQueue = ConcurrentBoundedQueue<ClusterUpdateAction>;
/// Size depends on coordination settings /// Size depends on coordination settings
std::unique_ptr<RequestsQueue> requests_queue; std::unique_ptr<RequestsQueue> requests_queue;
@ -39,7 +39,7 @@ private:
SnapshotsQueue snapshots_queue{1}; SnapshotsQueue snapshots_queue{1};
/// More than 1k updates is definitely misconfiguration. /// More than 1k updates is definitely misconfiguration.
UpdateConfigurationQueue update_configuration_queue{1000}; ClusterUpdateQueue cluster_update_queue{1000};
std::atomic<bool> shutdown_called{false}; std::atomic<bool> shutdown_called{false};
@ -91,8 +91,10 @@ private:
void sessionCleanerTask(); void sessionCleanerTask();
/// Thread create snapshots in the background /// Thread create snapshots in the background
void snapshotThread(); void snapshotThread();
/// Thread apply or wait configuration changes from leader
void updateConfigurationThread(); // TODO (myrrc) this should be removed once "reconfig" is stabilized
void clusterUpdateWithReconfigDisabledThread();
void clusterUpdateThread();
void setResponse(int64_t session_id, const Coordination::ZooKeeperResponsePtr & response); void setResponse(int64_t session_id, const Coordination::ZooKeeperResponsePtr & response);
@ -132,10 +134,9 @@ public:
/// and achieved quorum /// and achieved quorum
bool isServerActive() const; bool isServerActive() const;
/// Registered in ConfigReloader callback. Add new configuration changes to
/// update_configuration_queue. Keeper Dispatcher apply them asynchronously.
/// 'macros' are used to substitute macros in endpoint of disks
void updateConfiguration(const Poco::Util::AbstractConfiguration & config, const MultiVersion<Macros>::Version & macros); void updateConfiguration(const Poco::Util::AbstractConfiguration & config, const MultiVersion<Macros>::Version & macros);
void pushClusterUpdates(ClusterUpdateActions && actions);
bool reconfigEnabled() const;
/// Shutdown internal keeper parts (server, state machine, log storage, etc) /// Shutdown internal keeper parts (server, state machine, log storage, etc)
void shutdown(); void shutdown();

View File

@ -0,0 +1,91 @@
#include "KeeperReconfiguration.h"
#include <unordered_set>
#include <base/find_symbols.h>
#include <fmt/format.h>
namespace DB
{
ClusterUpdateActions joiningToClusterUpdates(const ClusterConfigPtr & cfg, std::string_view joining)
{
ClusterUpdateActions out;
std::unordered_set<String> endpoints;
for (const auto & server : cfg->get_servers())
endpoints.emplace(server->get_endpoint());
// We can either add new servers or change weight of existing ones.
// It makes no sense having a server in _joining_ which is identical to existing one including
// weight, so such requests are declined.
for (const RaftServerConfig & update : parseRaftServers(joining))
if (auto server_ptr = cfg->get_server(update.id))
{
if (update.endpoint != server_ptr->get_endpoint() || update.learner != server_ptr->is_learner()
|| update.priority == server_ptr->get_priority())
return {}; // can't change server endpoint/type due to NuRaft API limitations
out.emplace_back(UpdateRaftServerPriority{.id = update.id, .priority = update.priority});
}
else if (endpoints.contains(update.endpoint))
return {};
else
out.emplace_back(AddRaftServer{update});
return out;
}
ClusterUpdateActions leavingToClusterUpdates(const ClusterConfigPtr & cfg, std::string_view leaving)
{
std::vector<std::string_view> leaving_arr;
splitInto<','>(leaving_arr, leaving);
if (leaving_arr.size() >= cfg->get_servers().size())
return {};
std::unordered_set<int32_t> remove_ids;
ClusterUpdateActions out;
for (std::string_view leaving_server : leaving_arr)
{
int32_t id;
if (!tryParse(id, leaving_server))
return {};
if (remove_ids.contains(id))
continue;
if (auto ptr = cfg->get_server(id))
out.emplace_back(RemoveRaftServer{.id = id});
else
return {};
remove_ids.emplace(id);
}
return out;
}
String serializeClusterConfig(const ClusterConfigPtr & cfg, const ClusterUpdateActions & updates)
{
RaftServers new_config;
std::unordered_set<int32_t> remove_update_ids;
for (const auto & update : updates)
{
if (const auto * add = std::get_if<AddRaftServer>(&update))
new_config.emplace_back(*add);
else if (const auto * remove = std::get_if<RemoveRaftServer>(&update))
remove_update_ids.insert(remove->id);
else if (const auto * priority = std::get_if<UpdateRaftServerPriority>(&update))
{
remove_update_ids.insert(priority->id);
new_config.emplace_back(RaftServerConfig{*cfg->get_server(priority->id)});
}
else
UNREACHABLE();
}
for (const auto & item : cfg->get_servers())
if (!remove_update_ids.contains(item->get_id()))
new_config.emplace_back(RaftServerConfig{*item});
return fmt::format("{}", fmt::join(new_config.begin(), new_config.end(), "\n"));
}
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <Coordination/KeeperSnapshotManager.h>
#include <Coordination/RaftServerConfig.h>
namespace DB
{
ClusterUpdateActions joiningToClusterUpdates(const ClusterConfigPtr & cfg, std::string_view joining);
ClusterUpdateActions leavingToClusterUpdates(const ClusterConfigPtr & cfg, std::string_view leaving);
String serializeClusterConfig(const ClusterConfigPtr & cfg, const ClusterUpdateActions & updates = {});
}

View File

@ -27,6 +27,7 @@
#include <Common/Stopwatch.h> #include <Common/Stopwatch.h>
#include <Common/getMultipleKeysFromConfig.h> #include <Common/getMultipleKeysFromConfig.h>
#include <Disks/DiskLocal.h> #include <Disks/DiskLocal.h>
#include <fmt/chrono.h>
namespace DB namespace DB
{ {
@ -40,6 +41,8 @@ namespace ErrorCodes
extern const int INVALID_CONFIG_PARAMETER; extern const int INVALID_CONFIG_PARAMETER;
} }
using namespace std::chrono_literals;
namespace namespace
{ {
@ -118,6 +121,7 @@ KeeperServer::KeeperServer(
, is_recovering(config.getBool("keeper_server.force_recovery", false)) , is_recovering(config.getBool("keeper_server.force_recovery", false))
, keeper_context{std::move(keeper_context_)} , keeper_context{std::move(keeper_context_)}
, create_snapshot_on_exit(config.getBool("keeper_server.create_snapshot_on_exit", true)) , create_snapshot_on_exit(config.getBool("keeper_server.create_snapshot_on_exit", true))
, enable_reconfiguration(config.getBool("keeper_server.enable_reconfiguration", false))
{ {
if (coordination_settings->quorum_reads) if (coordination_settings->quorum_reads)
LOG_WARNING(log, "Quorum reads enabled, Keeper will work slower."); LOG_WARNING(log, "Quorum reads enabled, Keeper will work slower.");
@ -450,7 +454,7 @@ void KeeperServer::shutdownRaftServer()
size_t count = 0; size_t count = 0;
while (asio_service->get_active_workers() != 0 && count < timeout * 100) while (asio_service->get_active_workers() != 0 && count < timeout * 100)
{ {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(10ms);
count++; count++;
} }
} }
@ -715,10 +719,12 @@ nuraft::cb_func::ReturnCode KeeperServer::callbackFunc(nuraft::cb_func::Type typ
if (next_index < last_commited || next_index - last_commited <= 1) if (next_index < last_commited || next_index - last_commited <= 1)
commited_store = true; commited_store = true;
auto set_initialized = [this]() auto set_initialized = [this]
{ {
std::lock_guard lock(initialized_mutex); {
initialized_flag = true; std::lock_guard lock(initialized_mutex);
initialized_flag = true;
}
initialized_cv.notify_all(); initialized_cv.notify_all();
}; };
@ -783,9 +789,45 @@ std::vector<int64_t> KeeperServer::getDeadSessions()
return state_machine->getDeadSessions(); return state_machine->getDeadSessions();
} }
ConfigUpdateActions KeeperServer::getConfigurationDiff(const Poco::Util::AbstractConfiguration & config) bool KeeperServer::applyConfigUpdate(const ClusterUpdateAction & action)
{ {
auto diff = state_manager->getConfigurationDiff(config); std::lock_guard _{server_write_mutex};
if (const auto * add = std::get_if<AddRaftServer>(&action))
return raft_instance->get_srv_config(add->id) != nullptr
|| raft_instance->add_srv(static_cast<nuraft::srv_config>(*add))->get_accepted();
else if (const auto * remove = std::get_if<RemoveRaftServer>(&action))
{
if (remove->id == raft_instance->get_leader())
{
if (isLeader())
raft_instance->yield_leadership();
else
raft_instance->request_leadership();
return false;
}
return raft_instance->get_srv_config(remove->id) == nullptr
|| raft_instance->remove_srv(remove->id)->get_accepted();
}
else if (const auto * update = std::get_if<UpdateRaftServerPriority>(&action))
{
if (auto ptr = raft_instance->get_srv_config(update->id); ptr == nullptr)
throw Exception(ErrorCodes::RAFT_ERROR,
"Attempt to apply {} but server is not present in Raft",
action);
else if (ptr->get_priority() == update->priority)
return true;
raft_instance->set_priority(update->id, update->priority, /*broadcast on live leader*/true);
return true;
}
UNREACHABLE();
}
ClusterUpdateActions KeeperServer::getRaftConfigurationDiff(const Poco::Util::AbstractConfiguration & config)
{
auto diff = state_manager->getRaftConfigurationDiff(config);
if (!diff.empty()) if (!diff.empty())
{ {
@ -796,160 +838,103 @@ ConfigUpdateActions KeeperServer::getConfigurationDiff(const Poco::Util::Abstrac
return diff; return diff;
} }
void KeeperServer::applyConfigurationUpdate(const ConfigUpdateAction & task) void KeeperServer::applyConfigUpdateWithReconfigDisabled(const ClusterUpdateAction& action)
{ {
std::lock_guard lock{server_write_mutex}; std::lock_guard _{server_write_mutex};
if (is_recovering) if (is_recovering) return;
return; constexpr auto sleep_time = 500ms;
size_t sleep_ms = 500; LOG_INFO(log, "Will try to apply {}", action);
if (task.action_type == ConfigUpdateActionType::AddServer)
auto applied = [&] { LOG_INFO(log, "Applied {}", action); };
auto not_leader = [&] { LOG_INFO(log, "Not leader anymore, aborting"); };
auto backoff_on_refusal = [&](size_t i)
{
LOG_INFO(log, "Update was not accepted (try {}), backing off for {}", i + 1, sleep_time * (i + 1));
std::this_thread::sleep_for(sleep_time * (i + 1));
};
if (const auto * add = std::get_if<AddRaftServer>(&action))
{ {
LOG_INFO(log, "Will try to add server with id {}", task.server->get_id());
bool added = false;
for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i) for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i)
{ {
if (raft_instance->get_srv_config(task.server->get_id()) != nullptr) if (raft_instance->get_srv_config(add->id) != nullptr)
{ return applied();
LOG_INFO(log, "Server with id {} was successfully added", task.server->get_id());
added = true;
break;
}
if (!isLeader()) if (!isLeader())
{ return not_leader();
LOG_INFO(log, "We are not leader anymore, will not try to add server {}", task.server->get_id()); if (!raft_instance->add_srv(static_cast<nuraft::srv_config>(*add))->get_accepted())
break; backoff_on_refusal(i);
}
auto result = raft_instance->add_srv(*task.server);
if (!result->get_accepted())
LOG_INFO(
log,
"Command to add server {} was not accepted for the {} time, will sleep for {} ms and retry",
task.server->get_id(),
i + 1,
sleep_ms * (i + 1));
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms * (i + 1)));
} }
if (!added)
throw Exception(
ErrorCodes::RAFT_ERROR,
"Configuration change to add server (id {}) was not accepted by RAFT after all {} retries",
task.server->get_id(),
coordination_settings->configuration_change_tries_count);
} }
else if (task.action_type == ConfigUpdateActionType::RemoveServer) else if (const auto * remove = std::get_if<RemoveRaftServer>(&action))
{ {
LOG_INFO(log, "Will try to remove server with id {}", task.server->get_id()); if (remove->id == state_manager->server_id())
bool removed = false;
if (task.server->get_id() == state_manager->server_id())
{ {
LOG_INFO( LOG_INFO(log,
log, "Trying to remove leader node (ourself), so will yield leadership and some other node "
"Trying to remove leader node (ourself), so will yield leadership and some other node (new leader) will try remove us. " "(new leader) will try to remove us. "
"Probably you will have to run SYSTEM RELOAD CONFIG on the new leader node"); "Probably you will have to run SYSTEM RELOAD CONFIG on the new leader node");
return raft_instance->yield_leadership();
raft_instance->yield_leadership();
return;
} }
for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i) for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i)
{ {
if (raft_instance->get_srv_config(task.server->get_id()) == nullptr) if (raft_instance->get_srv_config(remove->id) == nullptr)
{ return applied();
LOG_INFO(log, "Server with id {} was successfully removed", task.server->get_id());
removed = true;
break;
}
if (!isLeader()) if (!isLeader())
{ return not_leader();
LOG_INFO(log, "We are not leader anymore, will not try to remove server {}", task.server->get_id()); if (!raft_instance->remove_srv(remove->id)->get_accepted())
break; backoff_on_refusal(i);
}
auto result = raft_instance->remove_srv(task.server->get_id());
if (!result->get_accepted())
LOG_INFO(
log,
"Command to remove server {} was not accepted for the {} time, will sleep for {} ms and retry",
task.server->get_id(),
i + 1,
sleep_ms * (i + 1));
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms * (i + 1)));
} }
if (!removed)
throw Exception(
ErrorCodes::RAFT_ERROR,
"Configuration change to remove server (id {}) was not accepted by RAFT after all {} retries",
task.server->get_id(),
coordination_settings->configuration_change_tries_count);
} }
else if (task.action_type == ConfigUpdateActionType::UpdatePriority) else if (const auto * update = std::get_if<UpdateRaftServerPriority>(&action))
raft_instance->set_priority(task.server->get_id(), task.server->get_priority()); {
else raft_instance->set_priority(update->id, update->priority, /*broadcast on live leader*/true);
LOG_WARNING(log, "Unknown configuration update type {}", static_cast<uint64_t>(task.action_type)); return;
}
throw Exception(ErrorCodes::RAFT_ERROR,
"Configuration change {} was not accepted by Raft after {} retries",
action, coordination_settings->configuration_change_tries_count);
} }
bool KeeperServer::waitForConfigUpdateWithReconfigDisabled(const ClusterUpdateAction& action)
bool KeeperServer::waitConfigurationUpdate(const ConfigUpdateAction & task)
{ {
if (is_recovering) if (is_recovering) return false;
return false; constexpr auto sleep_time = 500ms;
size_t sleep_ms = 500; LOG_INFO(log, "Will try to wait for {}", action);
if (task.action_type == ConfigUpdateActionType::AddServer)
auto applied = [&] { LOG_INFO(log, "Applied {}", action); return true; };
auto became_leader = [&] { LOG_INFO(log, "Became leader, aborting"); return false; };
auto backoff = [&](size_t i) { std::this_thread::sleep_for(sleep_time * (i + 1)); };
if (const auto* add = std::get_if<AddRaftServer>(&action))
{ {
LOG_INFO(log, "Will try to wait server with id {} to be added", task.server->get_id());
for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i) for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i)
{ {
if (raft_instance->get_srv_config(task.server->get_id()) != nullptr) if (raft_instance->get_srv_config(add->id) != nullptr)
{ return applied();
LOG_INFO(log, "Server with id {} was successfully added by leader", task.server->get_id());
return true;
}
if (isLeader()) if (isLeader())
{ return became_leader();
LOG_INFO(log, "We are leader now, probably we will have to add server {}", task.server->get_id()); backoff(i);
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms * (i + 1)));
} }
return false;
} }
else if (task.action_type == ConfigUpdateActionType::RemoveServer) else if (const auto* remove = std::get_if<RemoveRaftServer>(&action))
{ {
LOG_INFO(log, "Will try to wait remove of server with id {}", task.server->get_id());
for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i) for (size_t i = 0; i < coordination_settings->configuration_change_tries_count && !is_recovering; ++i)
{ {
if (raft_instance->get_srv_config(task.server->get_id()) == nullptr) if (raft_instance->get_srv_config(remove->id) == nullptr)
{ return applied();
LOG_INFO(log, "Server with id {} was successfully removed by leader", task.server->get_id());
return true;
}
if (isLeader()) if (isLeader())
{ return became_leader();
LOG_INFO(log, "We are leader now, probably we will have to remove server {}", task.server->get_id()); backoff(i);
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms * (i + 1)));
} }
return false;
} }
else if (task.action_type == ConfigUpdateActionType::UpdatePriority) else if (std::holds_alternative<UpdateRaftServerPriority>(action))
return true; return true;
else
LOG_WARNING(log, "Unknown configuration update type {}", static_cast<uint64_t>(task.action_type)); return false;
return true;
} }
Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const

View File

@ -10,6 +10,7 @@
#include <Poco/Util/AbstractConfiguration.h> #include <Poco/Util/AbstractConfiguration.h>
#include <Coordination/Keeper4LWInfo.h> #include <Coordination/Keeper4LWInfo.h>
#include <Coordination/KeeperContext.h> #include <Coordination/KeeperContext.h>
#include <Coordination/RaftServerConfig.h>
namespace DB namespace DB
{ {
@ -28,9 +29,10 @@ private:
nuraft::ptr<KeeperStateManager> state_manager; nuraft::ptr<KeeperStateManager> state_manager;
struct KeeperRaftServer; struct KeeperRaftServer;
nuraft::ptr<KeeperRaftServer> raft_instance; nuraft::ptr<KeeperRaftServer> raft_instance; // TSA_GUARDED_BY(server_write_mutex);
nuraft::ptr<nuraft::asio_service> asio_service; nuraft::ptr<nuraft::asio_service> asio_service;
std::vector<nuraft::ptr<nuraft::rpc_listener>> asio_listeners; std::vector<nuraft::ptr<nuraft::rpc_listener>> asio_listeners;
// because some actions can be applied // because some actions can be applied
// when we are sure that there are no requests currently being // when we are sure that there are no requests currently being
// processed (e.g. recovery) we do all write actions // processed (e.g. recovery) we do all write actions
@ -65,6 +67,7 @@ private:
std::shared_ptr<KeeperContext> keeper_context; std::shared_ptr<KeeperContext> keeper_context;
const bool create_snapshot_on_exit; const bool create_snapshot_on_exit;
const bool enable_reconfiguration;
public: public:
KeeperServer( KeeperServer(
@ -84,6 +87,7 @@ public:
void putLocalReadRequest(const KeeperStorage::RequestForSession & request); void putLocalReadRequest(const KeeperStorage::RequestForSession & request);
bool isRecovering() const { return is_recovering; } bool isRecovering() const { return is_recovering; }
bool reconfigEnabled() const { return enable_reconfiguration; }
/// Put batch of requests into Raft and get result of put. Responses will be set separately into /// Put batch of requests into Raft and get result of put. Responses will be set separately into
/// responses_queue. /// responses_queue.
@ -122,17 +126,12 @@ public:
int getServerID() const { return server_id; } int getServerID() const { return server_id; }
/// Get configuration diff between current configuration in RAFT and in XML file bool applyConfigUpdate(const ClusterUpdateAction& action);
ConfigUpdateActions getConfigurationDiff(const Poco::Util::AbstractConfiguration & config);
/// Apply action for configuration update. Actually call raft_instance->remove_srv or raft_instance->add_srv. // TODO (myrrc) these functions should be removed once "reconfig" is stabilized
/// Synchronously check for update results with retries. void applyConfigUpdateWithReconfigDisabled(const ClusterUpdateAction& action);
void applyConfigurationUpdate(const ConfigUpdateAction & task); bool waitForConfigUpdateWithReconfigDisabled(const ClusterUpdateAction& action);
ClusterUpdateActions getRaftConfigurationDiff(const Poco::Util::AbstractConfiguration & config);
/// Wait configuration update for action. Used by followers.
/// Return true if update was successfully received.
bool waitConfigurationUpdate(const ConfigUpdateAction & task);
uint64_t createSnapshot(); uint64_t createSnapshot();

View File

@ -2,24 +2,27 @@
#include <future> #include <future>
#include <Coordination/KeeperSnapshotManager.h> #include <Coordination/KeeperSnapshotManager.h>
#include <Coordination/KeeperStateMachine.h> #include <Coordination/KeeperStateMachine.h>
#include <Coordination/KeeperDispatcher.h>
#include <Coordination/KeeperStorage.h>
#include <Coordination/KeeperReconfiguration.h>
#include <Coordination/ReadBufferFromNuraftBuffer.h> #include <Coordination/ReadBufferFromNuraftBuffer.h>
#include <Coordination/WriteBufferFromNuraftBuffer.h> #include <Coordination/WriteBufferFromNuraftBuffer.h>
#include <IO/ReadHelpers.h> #include <IO/ReadHelpers.h>
#include <base/defines.h> #include <base/defines.h>
#include <base/errnoToString.h> #include <base/errnoToString.h>
#include <base/move_extend.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <Common/ProfileEvents.h> #include <Common/ProfileEvents.h>
#include <Common/ZooKeeper/ZooKeeperCommon.h> #include <Common/ZooKeeper/ZooKeeperCommon.h>
#include <Common/ZooKeeper/ZooKeeperIO.h> #include <Common/ZooKeeper/ZooKeeperIO.h>
#include <Common/logger_useful.h> #include <Common/logger_useful.h>
#include "Coordination/KeeperStorage.h"
#include <Disks/DiskLocal.h> #include <Disks/DiskLocal.h>
namespace ProfileEvents namespace ProfileEvents
{ {
extern const Event KeeperCommits; extern const Event KeeperCommits;
extern const Event KeeperReconfigRequest;
extern const Event KeeperCommitsFailed; extern const Event KeeperCommitsFailed;
extern const Event KeeperSnapshotCreations; extern const Event KeeperSnapshotCreations;
extern const Event KeeperSnapshotCreationsFailed; extern const Event KeeperSnapshotCreationsFailed;
@ -146,7 +149,7 @@ void assertDigest(
"Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest - {} (digest " "Digest for nodes is not matching after {} request of type '{}'.\nExpected digest - {}, actual digest - {} (digest "
"{}). Keeper will terminate to avoid inconsistencies.\nExtra information about the request:\n{}", "{}). Keeper will terminate to avoid inconsistencies.\nExtra information about the request:\n{}",
committing ? "committing" : "preprocessing", committing ? "committing" : "preprocessing",
Coordination::toString(request.getOpNum()), request.getOpNum(),
first.value, first.value,
second.value, second.value,
first.version, first.version,
@ -261,7 +264,8 @@ std::shared_ptr<KeeperStorage::RequestForSession> KeeperStateMachine::parseReque
bool KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & request_for_session) bool KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & request_for_session)
{ {
if (request_for_session.request->getOpNum() == Coordination::OpNum::SessionID) const auto op_num = request_for_session.request->getOpNum();
if (op_num == Coordination::OpNum::SessionID || op_num == Coordination::OpNum::Reconfig)
return true; return true;
std::lock_guard lock(storage_and_responses_lock); std::lock_guard lock(storage_and_responses_lock);
@ -291,14 +295,105 @@ bool KeeperStateMachine::preprocess(const KeeperStorage::RequestForSession & req
return true; return true;
} }
void KeeperStateMachine::reconfigure(const KeeperStorage::RequestForSession& request_for_session)
{
std::lock_guard _(storage_and_responses_lock);
KeeperStorage::ResponseForSession response = processReconfiguration(request_for_session);
if (!responses_queue.push(response))
{
ProfileEvents::increment(ProfileEvents::KeeperCommitsFailed);
LOG_WARNING(log,
"Failed to push response with session id {} to the queue, probably because of shutdown",
response.session_id);
}
}
KeeperStorage::ResponseForSession KeeperStateMachine::processReconfiguration(
const KeeperStorage::RequestForSession & request_for_session)
{
ProfileEvents::increment(ProfileEvents::KeeperReconfigRequest);
const auto & request = static_cast<const Coordination::ZooKeeperReconfigRequest&>(*request_for_session.request);
const int64_t session_id = request_for_session.session_id;
const int64_t zxid = request_for_session.zxid;
using enum Coordination::Error;
auto bad_request = [&](Coordination::Error code = ZBADARGUMENTS) -> KeeperStorage::ResponseForSession
{
auto res = std::make_shared<Coordination::ZooKeeperReconfigResponse>();
res->xid = request.xid;
res->zxid = zxid;
res->error = code;
return { session_id, std::move(res) };
};
if (!storage->checkACL(keeper_config_path, Coordination::ACL::Write, session_id, true))
return bad_request(ZNOAUTH);
KeeperDispatcher& dispatcher = *keeper_context->getDispatcher();
if (!dispatcher.reconfigEnabled())
return bad_request(ZUNIMPLEMENTED);
if (request.version != -1)
return bad_request(ZBADVERSION);
const bool has_new_members = !request.new_members.empty();
const bool has_joining = !request.joining.empty();
const bool has_leaving = !request.leaving.empty();
const bool incremental_reconfig = (has_joining || has_leaving) && !has_new_members;
if (!incremental_reconfig)
return bad_request();
const ClusterConfigPtr config = getClusterConfig();
if (!config) // Server can be uninitialized yet
return bad_request();
ClusterUpdateActions updates;
if (has_joining)
{
if (auto join_updates = joiningToClusterUpdates(config, request.joining); !join_updates.empty())
moveExtend(updates, std::move(join_updates));
else
return bad_request();
}
if (has_leaving)
{
if (auto leave_updates = leavingToClusterUpdates(config, request.leaving); !leave_updates.empty())
moveExtend(updates, std::move(leave_updates));
else
return bad_request();
}
auto response = std::make_shared<Coordination::ZooKeeperReconfigResponse>();
response->xid = request.xid;
response->zxid = zxid;
response->error = Coordination::Error::ZOK;
response->value = serializeClusterConfig(config, updates);
dispatcher.pushClusterUpdates(std::move(updates));
return { session_id, std::move(response) };
}
nuraft::ptr<nuraft::buffer> KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data) nuraft::ptr<nuraft::buffer> KeeperStateMachine::commit(const uint64_t log_idx, nuraft::buffer & data)
{ {
auto request_for_session = parseRequest(data, true); auto request_for_session = parseRequest(data, true);
if (!request_for_session->zxid) if (!request_for_session->zxid)
request_for_session->zxid = log_idx; request_for_session->zxid = log_idx;
/// Special processing of session_id request auto try_push = [this](const KeeperStorage::ResponseForSession& response)
if (request_for_session->request->getOpNum() == Coordination::OpNum::SessionID) {
if (!responses_queue.push(response))
{
ProfileEvents::increment(ProfileEvents::KeeperCommitsFailed);
LOG_WARNING(log,
"Failed to push response with session id {} to the queue, probably because of shutdown",
response.session_id);
}
};
const auto op_num = request_for_session->request->getOpNum();
if (op_num == Coordination::OpNum::SessionID)
{ {
const Coordination::ZooKeeperSessionIDRequest & session_id_request const Coordination::ZooKeeperSessionIDRequest & session_id_request
= dynamic_cast<const Coordination::ZooKeeperSessionIDRequest &>(*request_for_session->request); = dynamic_cast<const Coordination::ZooKeeperSessionIDRequest &>(*request_for_session->request);
@ -309,21 +404,16 @@ nuraft::ptr<nuraft::buffer> KeeperStateMachine::commit(const uint64_t log_idx, n
KeeperStorage::ResponseForSession response_for_session; KeeperStorage::ResponseForSession response_for_session;
response_for_session.session_id = -1; response_for_session.session_id = -1;
response_for_session.response = response; response_for_session.response = response;
{
std::lock_guard lock(storage_and_responses_lock); std::lock_guard lock(storage_and_responses_lock);
session_id = storage->getSessionID(session_id_request.session_timeout_ms); session_id = storage->getSessionID(session_id_request.session_timeout_ms);
LOG_DEBUG(log, "Session ID response {} with timeout {}", session_id, session_id_request.session_timeout_ms); LOG_DEBUG(log, "Session ID response {} with timeout {}", session_id, session_id_request.session_timeout_ms);
response->session_id = session_id; response->session_id = session_id;
if (!responses_queue.push(response_for_session)) try_push(response_for_session);
{
ProfileEvents::increment(ProfileEvents::KeeperCommitsFailed);
LOG_WARNING(log, "Failed to push response with session id {} to the queue, probably because of shutdown", session_id);
}
}
} }
else else
{ {
if (request_for_session->request->getOpNum() == Coordination::OpNum::Close) if (op_num == Coordination::OpNum::Close)
{ {
std::lock_guard lock(request_cache_mutex); std::lock_guard lock(request_cache_mutex);
parsed_request_cache.erase(request_for_session->session_id); parsed_request_cache.erase(request_for_session->session_id);
@ -333,14 +423,7 @@ nuraft::ptr<nuraft::buffer> KeeperStateMachine::commit(const uint64_t log_idx, n
KeeperStorage::ResponsesForSessions responses_for_sessions KeeperStorage::ResponsesForSessions responses_for_sessions
= storage->processRequest(request_for_session->request, request_for_session->session_id, request_for_session->zxid); = storage->processRequest(request_for_session->request, request_for_session->session_id, request_for_session->zxid);
for (auto & response_for_session : responses_for_sessions) for (auto & response_for_session : responses_for_sessions)
if (!responses_queue.push(response_for_session)) try_push(response_for_session);
{
ProfileEvents::increment(ProfileEvents::KeeperCommitsFailed);
LOG_WARNING(
log,
"Failed to push response with session id {} to the queue, probably because of shutdown",
response_for_session.session_id);
}
if (keeper_context->digestEnabled() && request_for_session->digest) if (keeper_context->digestEnabled() && request_for_session->digest)
assertDigest(*request_for_session->digest, storage->getNodesDigest(true), *request_for_session->request, true); assertDigest(*request_for_session->digest, storage->getNodesDigest(true), *request_for_session->request, true);
@ -390,7 +473,7 @@ bool KeeperStateMachine::apply_snapshot(nuraft::snapshot & s)
/// maybe some logs were preprocessed with log idx larger than the snapshot idx /// maybe some logs were preprocessed with log idx larger than the snapshot idx
/// we have to apply them to the new storage /// we have to apply them to the new storage
storage->applyUncommittedState(*snapshot_deserialization_result.storage, s.get_last_log_idx()); storage->applyUncommittedState(*snapshot_deserialization_result.storage, snapshot_deserialization_result.storage->getZXID());
storage = std::move(snapshot_deserialization_result.storage); storage = std::move(snapshot_deserialization_result.storage);
latest_snapshot_meta = snapshot_deserialization_result.snapshot_meta; latest_snapshot_meta = snapshot_deserialization_result.snapshot_meta;
cluster_config = snapshot_deserialization_result.cluster_config; cluster_config = snapshot_deserialization_result.cluster_config;
@ -782,5 +865,4 @@ void KeeperStateMachine::recalculateStorageStats()
storage->recalculateStats(); storage->recalculateStats();
LOG_INFO(log, "Done recalculating storage stats"); LOG_INFO(log, "Done recalculating storage stats");
} }
} }

View File

@ -12,7 +12,6 @@
namespace DB namespace DB
{ {
using ResponsesQueue = ConcurrentBoundedQueue<KeeperStorage::ResponseForSession>; using ResponsesQueue = ConcurrentBoundedQueue<KeeperStorage::ResponseForSession>;
using SnapshotsQueue = ConcurrentBoundedQueue<CreateSnapshotTask>; using SnapshotsQueue = ConcurrentBoundedQueue<CreateSnapshotTask>;
@ -67,7 +66,9 @@ public:
// (can happen in case of exception during preprocessing) // (can happen in case of exception during preprocessing)
void rollbackRequest(const KeeperStorage::RequestForSession & request_for_session, bool allow_missing); void rollbackRequest(const KeeperStorage::RequestForSession & request_for_session, bool allow_missing);
void rollbackRequestNoLock(const KeeperStorage::RequestForSession & request_for_session, bool allow_missing); void rollbackRequestNoLock(
const KeeperStorage::RequestForSession & request_for_session,
bool allow_missing) TSA_NO_THREAD_SAFETY_ANALYSIS;
uint64_t last_commit_index() override { return last_committed_idx; } uint64_t last_commit_index() override { return last_committed_idx; }
@ -87,8 +88,13 @@ public:
int read_logical_snp_obj( int read_logical_snp_obj(
nuraft::snapshot & s, void *& user_snp_ctx, uint64_t obj_id, nuraft::ptr<nuraft::buffer> & data_out, bool & is_last_obj) override; nuraft::snapshot & s, void *& user_snp_ctx, uint64_t obj_id, nuraft::ptr<nuraft::buffer> & data_out, bool & is_last_obj) override;
/// just for test // This should be used only for tests or keeper-data-dumper because it violates
KeeperStorage & getStorage() { return *storage; } // TSA -- we can't acquire the lock outside of this class or return a storage under lock
// in a reasonable way.
KeeperStorage & getStorageUnsafe() TSA_NO_THREAD_SAFETY_ANALYSIS
{
return *storage;
}
void shutdownStorage(); void shutdownStorage();
@ -122,6 +128,9 @@ public:
uint64_t getLatestSnapshotBufSize() const; uint64_t getLatestSnapshotBufSize() const;
void recalculateStorageStats(); void recalculateStorageStats();
void reconfigure(const KeeperStorage::RequestForSession& request_for_session);
private: private:
CommitCallback commit_callback; CommitCallback commit_callback;
/// In our state machine we always have a single snapshot which is stored /// In our state machine we always have a single snapshot which is stored
@ -133,7 +142,7 @@ private:
CoordinationSettingsPtr coordination_settings; CoordinationSettingsPtr coordination_settings;
/// Main state machine logic /// Main state machine logic
KeeperStoragePtr storage; KeeperStoragePtr storage TSA_PT_GUARDED_BY(storage_and_responses_lock);
/// Save/Load and Serialize/Deserialize logic for snapshots. /// Save/Load and Serialize/Deserialize logic for snapshots.
KeeperSnapshotManager snapshot_manager; KeeperSnapshotManager snapshot_manager;
@ -178,6 +187,9 @@ private:
KeeperContextPtr keeper_context; KeeperContextPtr keeper_context;
KeeperSnapshotManagerS3 * snapshot_manager_s3; KeeperSnapshotManagerS3 * snapshot_manager_s3;
};
KeeperStorage::ResponseForSession processReconfiguration(
const KeeperStorage::RequestForSession& request_for_session)
TSA_REQUIRES(storage_and_responses_lock);
};
} }

View File

@ -451,7 +451,7 @@ nuraft::ptr<nuraft::srv_state> KeeperStateManager::read_state()
return nullptr; return nullptr;
} }
ConfigUpdateActions KeeperStateManager::getConfigurationDiff(const Poco::Util::AbstractConfiguration & config) const ClusterUpdateActions KeeperStateManager::getRaftConfigurationDiff(const Poco::Util::AbstractConfiguration & config) const
{ {
auto new_configuration_wrapper = parseServersConfiguration(config, true); auto new_configuration_wrapper = parseServersConfiguration(config, true);
@ -465,14 +465,14 @@ ConfigUpdateActions KeeperStateManager::getConfigurationDiff(const Poco::Util::A
old_ids[old_server->get_id()] = old_server; old_ids[old_server->get_id()] = old_server;
} }
ConfigUpdateActions result; ClusterUpdateActions result;
/// First of all add new servers /// First of all add new servers
for (const auto & [new_id, server_config] : new_ids) for (const auto & [new_id, server_config] : new_ids)
{ {
auto old_server_it = old_ids.find(new_id); auto old_server_it = old_ids.find(new_id);
if (old_server_it == old_ids.end()) if (old_server_it == old_ids.end())
result.emplace_back(ConfigUpdateAction{ConfigUpdateActionType::AddServer, server_config}); result.emplace_back(AddRaftServer{RaftServerConfig{*server_config}});
else else
{ {
const auto & old_endpoint = old_server_it->second->get_endpoint(); const auto & old_endpoint = old_server_it->second->get_endpoint();
@ -491,10 +491,8 @@ ConfigUpdateActions KeeperStateManager::getConfigurationDiff(const Poco::Util::A
/// After that remove old ones /// After that remove old ones
for (auto [old_id, server_config] : old_ids) for (auto [old_id, server_config] : old_ids)
{
if (!new_ids.contains(old_id)) if (!new_ids.contains(old_id))
result.emplace_back(ConfigUpdateAction{ConfigUpdateActionType::RemoveServer, server_config}); result.emplace_back(RemoveRaftServer{old_id});
}
{ {
std::lock_guard lock(configuration_wrapper_mutex); std::lock_guard lock(configuration_wrapper_mutex);
@ -507,7 +505,10 @@ ConfigUpdateActions KeeperStateManager::getConfigurationDiff(const Poco::Util::A
{ {
if (old_server->get_priority() != new_server->get_priority()) if (old_server->get_priority() != new_server->get_priority())
{ {
result.emplace_back(ConfigUpdateAction{ConfigUpdateActionType::UpdatePriority, new_server}); result.emplace_back(UpdateRaftServerPriority{
.id = new_server->get_id(),
.priority = new_server->get_priority()
});
} }
break; break;
} }

View File

@ -7,31 +7,13 @@
#include <libnuraft/nuraft.hxx> #include <libnuraft/nuraft.hxx>
#include <Poco/Util/AbstractConfiguration.h> #include <Poco/Util/AbstractConfiguration.h>
#include "Coordination/KeeperStateMachine.h" #include "Coordination/KeeperStateMachine.h"
#include "Coordination/RaftServerConfig.h"
#include <Coordination/KeeperSnapshotManager.h> #include <Coordination/KeeperSnapshotManager.h>
namespace DB namespace DB
{ {
using KeeperServerConfigPtr = nuraft::ptr<nuraft::srv_config>; using KeeperServerConfigPtr = nuraft::ptr<nuraft::srv_config>;
/// When our configuration changes the following action types
/// can happen
enum class ConfigUpdateActionType
{
RemoveServer,
AddServer,
UpdatePriority,
};
/// Action to update configuration
struct ConfigUpdateAction
{
ConfigUpdateActionType action_type;
KeeperServerConfigPtr server;
};
using ConfigUpdateActions = std::vector<ConfigUpdateAction>;
/// Responsible for managing our and cluster configuration /// Responsible for managing our and cluster configuration
class KeeperStateManager : public nuraft::state_mgr class KeeperStateManager : public nuraft::state_mgr
{ {
@ -74,7 +56,11 @@ public:
int32_t server_id() override { return my_server_id; } int32_t server_id() override { return my_server_id; }
nuraft::ptr<nuraft::srv_config> get_srv_config() const { return configuration_wrapper.config; } /// NOLINT nuraft::ptr<nuraft::srv_config> get_srv_config() const
{
std::lock_guard lk(configuration_wrapper_mutex);
return configuration_wrapper.config;
}
void system_exit(const int exit_code) override; /// NOLINT void system_exit(const int exit_code) override; /// NOLINT
@ -106,8 +92,8 @@ public:
/// Read all log entries in log store from the begging and return latest config (with largest log_index) /// Read all log entries in log store from the begging and return latest config (with largest log_index)
ClusterConfigPtr getLatestConfigFromLogStore() const; ClusterConfigPtr getLatestConfigFromLogStore() const;
/// Get configuration diff between proposed XML and current state in RAFT // TODO (myrrc) This should be removed once "reconfig" is stabilized
ConfigUpdateActions getConfigurationDiff(const Poco::Util::AbstractConfiguration & config) const; ClusterUpdateActions getRaftConfigurationDiff(const Poco::Util::AbstractConfiguration & config) const;
private: private:
const String & getOldServerStatePath(); const String & getOldServerStatePath();
@ -133,7 +119,7 @@ private:
std::string config_prefix; std::string config_prefix;
mutable std::mutex configuration_wrapper_mutex; mutable std::mutex configuration_wrapper_mutex;
KeeperConfigurationWrapper configuration_wrapper; KeeperConfigurationWrapper configuration_wrapper TSA_GUARDED_BY(configuration_wrapper_mutex);
nuraft::ptr<KeeperLogStore> log_store; nuraft::ptr<KeeperLogStore> log_store;

View File

@ -20,10 +20,10 @@
#include <Coordination/pathUtils.h> #include <Coordination/pathUtils.h>
#include <Coordination/KeeperConstants.h> #include <Coordination/KeeperConstants.h>
#include <Coordination/KeeperReconfiguration.h>
#include <Coordination/KeeperStorage.h> #include <Coordination/KeeperStorage.h>
#include <Coordination/KeeperDispatcher.h>
#include <sstream>
#include <iomanip>
#include <mutex> #include <mutex>
#include <functional> #include <functional>
#include <base/defines.h> #include <base/defines.h>
@ -53,7 +53,6 @@ namespace ErrorCodes
namespace namespace
{ {
String getSHA1(const String & userdata) String getSHA1(const String & userdata)
{ {
Poco::SHA1Engine engine; Poco::SHA1Engine engine;
@ -1060,7 +1059,8 @@ struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProce
ProfileEvents::increment(ProfileEvents::KeeperGetRequest); ProfileEvents::increment(ProfileEvents::KeeperGetRequest);
Coordination::ZooKeeperGetRequest & request = dynamic_cast<Coordination::ZooKeeperGetRequest &>(*zk_request); Coordination::ZooKeeperGetRequest & request = dynamic_cast<Coordination::ZooKeeperGetRequest &>(*zk_request);
if (request.path == Coordination::keeper_api_feature_flags_path) if (request.path == Coordination::keeper_api_feature_flags_path
|| request.path == Coordination::keeper_config_path)
return {}; return {};
if (!storage.uncommitted_state.getNode(request.path)) if (!storage.uncommitted_state.getNode(request.path))
@ -1085,6 +1085,14 @@ struct KeeperStorageGetRequestProcessor final : public KeeperStorageRequestProce
} }
} }
if (request.path == Coordination::keeper_config_path)
{
response.data = serializeClusterConfig(
storage.keeper_context->getDispatcher()->getStateMachine().getClusterConfig());
response.error = Coordination::Error::ZOK;
return response_ptr;
}
auto & container = storage.container; auto & container = storage.container;
auto node_it = container.find(request.path); auto node_it = container.find(request.path);
if (node_it == container.end()) if (node_it == container.end())
@ -1784,7 +1792,7 @@ struct KeeperStorageMultiRequestProcessor final : public KeeperStorageRequestPro
throw DB::Exception( throw DB::Exception(
ErrorCodes::BAD_ARGUMENTS, ErrorCodes::BAD_ARGUMENTS,
"Illegal command as part of multi ZooKeeper request {}", "Illegal command as part of multi ZooKeeper request {}",
Coordination::toString(sub_zk_request->getOpNum())); sub_zk_request->getOpNum());
} }
} }
@ -1975,7 +1983,7 @@ public:
{ {
auto request_it = op_num_to_request.find(zk_request->getOpNum()); auto request_it = op_num_to_request.find(zk_request->getOpNum());
if (request_it == op_num_to_request.end()) if (request_it == op_num_to_request.end())
throw DB::Exception(ErrorCodes::LOGICAL_ERROR, "Unknown operation type {}", toString(zk_request->getOpNum())); throw DB::Exception(ErrorCodes::LOGICAL_ERROR, "Unknown operation type {}", zk_request->getOpNum());
return request_it->second(zk_request); return request_it->second(zk_request);
} }

View File

@ -0,0 +1,97 @@
#include "RaftServerConfig.h"
#include <unordered_set>
#include <IO/ReadHelpers.h>
#include <base/find_symbols.h>
namespace DB
{
RaftServerConfig::RaftServerConfig(const nuraft::srv_config & cfg) noexcept
: id(cfg.get_id()), endpoint(cfg.get_endpoint()), learner(cfg.is_learner()), priority(cfg.get_priority())
{
}
RaftServerConfig::operator nuraft::srv_config() const noexcept
{
return {id, 0, endpoint, "", learner, priority};
}
std::optional<RaftServerConfig> RaftServerConfig::parse(std::string_view server) noexcept
{
std::vector<std::string_view> parts;
splitInto<';', '='>(parts, server);
const bool with_id_endpoint = parts.size() == 2;
const bool with_server_type = parts.size() == 3;
const bool with_priority = parts.size() == 4;
if (!with_id_endpoint && !with_server_type && !with_priority)
return std::nullopt;
const std::string_view id_str = parts[0];
if (!id_str.starts_with("server."))
return std::nullopt;
Int32 id;
if (!tryParse(id, std::next(id_str.begin(), 7)))
return std::nullopt;
if (id <= 0)
return std::nullopt;
const std::string_view endpoint = parts[1];
const size_t port_delimiter = endpoint.find_last_of(':');
if (port_delimiter == std::string::npos)
return {};
const std::string_view port = endpoint.substr(port_delimiter + 1);
uint16_t port_tmp;
if (!tryParse(port_tmp, port))
return std::nullopt;
RaftServerConfig out{id, endpoint};
if (with_id_endpoint)
return out;
if (parts[2] != "learner" && parts[2] != "participant")
return std::nullopt;
out.learner = parts[2] == "learner";
if (with_server_type)
return out;
const std::string_view priority = parts[3];
if (!tryParse(out.priority, priority))
return std::nullopt;
if (out.priority < 0)
return std::nullopt;
return out;
}
RaftServers parseRaftServers(std::string_view servers)
{
std::vector<std::string_view> server_arr;
std::unordered_set<int32_t> ids;
std::unordered_set<String> endpoints;
RaftServers out;
for (auto & server : splitInto<','>(server_arr, servers))
{
if (auto maybe_server = RaftServerConfig::parse(server))
{
String endpoint = maybe_server->endpoint;
if (endpoints.contains(endpoint))
return {};
const int id = maybe_server->id;
if (ids.contains(id))
return {};
out.emplace_back(std::move(*maybe_server));
endpoints.emplace(std::move(endpoint));
ids.emplace(id);
}
else
return {};
}
return out;
}
}

View File

@ -0,0 +1,78 @@
#pragma once
#include <base/defines.h>
#include <base/types.h>
#include <fmt/core.h>
#include <libnuraft/srv_config.hxx>
namespace DB
{
// default- and copy-constructible version of nuraft::srv_config
struct RaftServerConfig
{
int id;
String endpoint;
bool learner;
int priority;
constexpr RaftServerConfig() = default;
constexpr RaftServerConfig(int id_, std::string_view endpoint_, bool learner_ = false, int priority_ = 1)
: id(id_), endpoint(endpoint_), learner(learner_), priority(priority_)
{
}
constexpr bool operator==(const RaftServerConfig &) const = default;
explicit RaftServerConfig(const nuraft::srv_config & cfg) noexcept;
explicit operator nuraft::srv_config() const noexcept;
/// Parse server in format "server.id=host:port[;learner][;priority]"
static std::optional<RaftServerConfig> parse(std::string_view server) noexcept;
};
using RaftServers = std::vector<RaftServerConfig>;
/// Parse comma-delimited servers. Check for duplicate endpoints and ids.
/// @returns {} on parsing or validation error.
RaftServers parseRaftServers(std::string_view servers);
struct AddRaftServer : RaftServerConfig
{
};
struct RemoveRaftServer
{
int id;
};
struct UpdateRaftServerPriority
{
int id;
int priority;
};
using ClusterUpdateAction = std::variant<AddRaftServer, RemoveRaftServer, UpdateRaftServerPriority>;
using ClusterUpdateActions = std::vector<ClusterUpdateAction>;
}
template <>
struct fmt::formatter<DB::RaftServerConfig> : fmt::formatter<string_view>
{
constexpr auto format(const DB::RaftServerConfig & server, format_context & ctx)
{
return fmt::format_to(
ctx.out(), "server.{}={};{};{}", server.id, server.endpoint, server.learner ? "learner" : "participant", server.priority);
}
};
template <>
struct fmt::formatter<DB::ClusterUpdateAction> : fmt::formatter<string_view>
{
constexpr auto format(const DB::ClusterUpdateAction & action, format_context & ctx)
{
if (const auto * add = std::get_if<DB::AddRaftServer>(&action))
return fmt::format_to(ctx.out(), "(Add server {})", add->id);
if (const auto * remove = std::get_if<DB::RemoveRaftServer>(&action))
return fmt::format_to(ctx.out(), "(Remove server {})", remove->id);
if (const auto * update = std::get_if<DB::UpdateRaftServerPriority>(&action))
return fmt::format_to(ctx.out(), "(Change server {} priority to {})", update->id, update->priority);
UNREACHABLE();
}
};

View File

@ -84,6 +84,47 @@ protected:
} }
}; };
TEST_P(CoordinationTest, RaftServerConfigParse)
{
auto parse = Coordination::RaftServerConfig::parse;
using Cfg = std::optional<DB::RaftServerConfig>;
EXPECT_EQ(parse(""), std::nullopt);
EXPECT_EQ(parse("="), std::nullopt);
EXPECT_EQ(parse("=;"), std::nullopt);
EXPECT_EQ(parse("=;;"), std::nullopt);
EXPECT_EQ(parse("=:80"), std::nullopt);
EXPECT_EQ(parse("server."), std::nullopt);
EXPECT_EQ(parse("server.=:80"), std::nullopt);
EXPECT_EQ(parse("server.-5=1:2"), std::nullopt);
EXPECT_EQ(parse("server.1=host;-123"), std::nullopt);
EXPECT_EQ(parse("server.1=host:999"), (Cfg{{1, "host:999"}}));
EXPECT_EQ(parse("server.1=host:999;learner"), (Cfg{{1, "host:999", true}}));
EXPECT_EQ(parse("server.1=host:999;participant"), (Cfg{{1, "host:999", false}}));
EXPECT_EQ(parse("server.1=host:999;learner;25"), (Cfg{{1, "host:999", true, 25}}));
EXPECT_EQ(parse("server.1=127.0.0.1:80"), (Cfg{{1, "127.0.0.1:80"}}));
EXPECT_EQ(
parse("server.1=2001:0db8:85a3:0000:0000:8a2e:0370:7334:80"),
(Cfg{{1, "2001:0db8:85a3:0000:0000:8a2e:0370:7334:80"}}));
}
TEST_P(CoordinationTest, RaftServerClusterConfigParse)
{
auto parse = Coordination::parseRaftServers;
using Cfg = DB::RaftServerConfig;
using Servers = DB::RaftServers;
EXPECT_EQ(parse(""), Servers{});
EXPECT_EQ(parse(","), Servers{});
EXPECT_EQ(parse("1,2"), Servers{});
EXPECT_EQ(parse("server.1=host:80,server.1=host2:80"), Servers{});
EXPECT_EQ(parse("server.1=host:80,server.2=host:80"), Servers{});
EXPECT_EQ(
parse("server.1=host:80,server.2=host:81"),
(Servers{Cfg{1, "host:80"}, Cfg{2, "host:81"}}));
}
TEST_P(CoordinationTest, BuildTest) TEST_P(CoordinationTest, BuildTest)
{ {
DB::InMemoryLogStore store; DB::InMemoryLogStore store;
@ -1575,8 +1616,8 @@ void testLogAndStateMachine(
restore_machine->commit(i, changelog.entry_at(i)->get_buf()); restore_machine->commit(i, changelog.entry_at(i)->get_buf());
} }
auto & source_storage = state_machine->getStorage(); auto & source_storage = state_machine->getStorageUnsafe();
auto & restored_storage = restore_machine->getStorage(); auto & restored_storage = restore_machine->getStorageUnsafe();
EXPECT_EQ(source_storage.container.size(), restored_storage.container.size()); EXPECT_EQ(source_storage.container.size(), restored_storage.container.size());
for (size_t i = 1; i < total_logs + 1; ++i) for (size_t i = 1; i < total_logs + 1; ++i)
@ -1678,7 +1719,7 @@ TEST_P(CoordinationTest, TestEphemeralNodeRemove)
auto entry_c = getLogEntryFromZKRequest(0, 1, state_machine->getNextZxid(), request_c); auto entry_c = getLogEntryFromZKRequest(0, 1, state_machine->getNextZxid(), request_c);
state_machine->pre_commit(1, entry_c->get_buf()); state_machine->pre_commit(1, entry_c->get_buf());
state_machine->commit(1, entry_c->get_buf()); state_machine->commit(1, entry_c->get_buf());
const auto & storage = state_machine->getStorage(); const auto & storage = state_machine->getStorageUnsafe();
EXPECT_EQ(storage.ephemerals.size(), 1); EXPECT_EQ(storage.ephemerals.size(), 1);
std::shared_ptr<ZooKeeperRemoveRequest> request_d = std::make_shared<ZooKeeperRemoveRequest>(); std::shared_ptr<ZooKeeperRemoveRequest> request_d = std::make_shared<ZooKeeperRemoveRequest>();
@ -1727,7 +1768,7 @@ TEST_P(CoordinationTest, TestCreateNodeWithAuthSchemeForAclWhenAuthIsPrecommitte
auto create_entry = getLogEntryFromZKRequest(0, 1, state_machine->getNextZxid(), create_req); auto create_entry = getLogEntryFromZKRequest(0, 1, state_machine->getNextZxid(), create_req);
state_machine->pre_commit(2, create_entry->get_buf()); state_machine->pre_commit(2, create_entry->get_buf());
const auto & uncommitted_state = state_machine->getStorage().uncommitted_state; const auto & uncommitted_state = state_machine->getStorageUnsafe().uncommitted_state;
ASSERT_TRUE(uncommitted_state.nodes.contains(node_path)); ASSERT_TRUE(uncommitted_state.nodes.contains(node_path));
// commit log entries // commit log entries
@ -1790,7 +1831,7 @@ TEST_P(CoordinationTest, TestSetACLWithAuthSchemeForAclWhenAuthIsPrecommitted)
state_machine->commit(2, create_entry->get_buf()); state_machine->commit(2, create_entry->get_buf());
state_machine->commit(3, set_acl_entry->get_buf()); state_machine->commit(3, set_acl_entry->get_buf());
const auto & uncommitted_state = state_machine->getStorage().uncommitted_state; const auto & uncommitted_state = state_machine->getStorageUnsafe().uncommitted_state;
auto node = uncommitted_state.getNode(node_path); auto node = uncommitted_state.getNode(node_path);
ASSERT_NE(node, nullptr); ASSERT_NE(node, nullptr);

View File

@ -48,7 +48,11 @@ inline auto scaleMultiplier(UInt32 scale)
/** Components of DecimalX value: /** Components of DecimalX value:
* whole - represents whole part of decimal, can be negative or positive. * whole - represents whole part of decimal, can be negative or positive.
* fractional - for fractional part of decimal, always positive. * fractional - for fractional part of decimal.
*
* 0.123 represents 0 / 0.123
* -0.123 represents 0 / -0.123
* -1.123 represents -1 / 0.123
*/ */
template <typename DecimalType> template <typename DecimalType>
struct DecimalComponents struct DecimalComponents

View File

@ -40,9 +40,9 @@ namespace MySQLReplication
void EventHeader::dump(WriteBuffer & out) const void EventHeader::dump(WriteBuffer & out) const
{ {
out << "\n=== " << to_string(this->type) << " ===" << '\n'; out << "\n=== " << magic_enum::enum_name(this->type) << " ===" << '\n';
out << "Timestamp: " << this->timestamp << '\n'; out << "Timestamp: " << this->timestamp << '\n';
out << "Event Type: " << to_string(this->type) << '\n'; out << "Event Type: " << magic_enum::enum_name(this->type) << '\n';
out << "Server ID: " << this->server_id << '\n'; out << "Server ID: " << this->server_id << '\n';
out << "Event Size: " << this->event_size << '\n'; out << "Event Size: " << this->event_size << '\n';
out << "Log Pos: " << this->log_pos << '\n'; out << "Log Pos: " << this->log_pos << '\n';
@ -121,6 +121,17 @@ namespace MySQLReplication
{ {
typ = QUERY_SAVEPOINT; typ = QUERY_SAVEPOINT;
} }
// https://dev.mysql.com/worklog/task/?id=13355
// When doing query "CREATE TABLE xx AS SELECT", the binlog will be
// "CREATE TABLE ... START TRANSACTION", the DDL will be failed
// so, just ignore the "START TRANSACTION" suffix
if (query.ends_with("START TRANSACTION"))
{
auto pos = query.rfind("START TRANSACTION");
if (pos > 0)
query.resize(pos);
}
} }
void QueryEvent::dump(WriteBuffer & out) const void QueryEvent::dump(WriteBuffer & out) const

View File

@ -120,22 +120,6 @@ namespace MySQLReplication
BINLOG_CHECKSUM_ALG_UNDEF = 255 BINLOG_CHECKSUM_ALG_UNDEF = 255
}; };
inline String to_string(BinlogChecksumAlg type)
{
switch (type)
{
case BINLOG_CHECKSUM_ALG_OFF:
return "BINLOG_CHECKSUM_ALG_OFF";
case BINLOG_CHECKSUM_ALG_CRC32:
return "BINLOG_CHECKSUM_ALG_CRC32";
case BINLOG_CHECKSUM_ALG_ENUM_END:
return "BINLOG_CHECKSUM_ALG_ENUM_END";
case BINLOG_CHECKSUM_ALG_UNDEF:
return "BINLOG_CHECKSUM_ALG_UNDEF";
}
return std::string("Unknown checksum alg: ") + std::to_string(static_cast<int>(type));
}
/// http://dev.mysql.com/doc/internals/en/binlog-event-type.html /// http://dev.mysql.com/doc/internals/en/binlog-event-type.html
enum EventType enum EventType
{ {
@ -187,102 +171,6 @@ namespace MySQLReplication
MARIA_START_ENCRYPTION_EVENT = 164, MARIA_START_ENCRYPTION_EVENT = 164,
}; };
inline String to_string(EventType type)
{
switch (type)
{
case START_EVENT_V3:
return "StartEventV3";
case QUERY_EVENT:
return "QueryEvent";
case STOP_EVENT:
return "StopEvent";
case ROTATE_EVENT:
return "RotateEvent";
case INT_VAR_EVENT:
return "IntVarEvent";
case LOAD_EVENT:
return "LoadEvent";
case SLAVE_EVENT:
return "SlaveEvent";
case CREATE_FILE_EVENT:
return "CreateFileEvent";
case APPEND_BLOCK_EVENT:
return "AppendBlockEvent";
case EXEC_LOAD_EVENT:
return "ExecLoadEvent";
case DELETE_FILE_EVENT:
return "DeleteFileEvent";
case NEW_LOAD_EVENT:
return "NewLoadEvent";
case RAND_EVENT:
return "RandEvent";
case USER_VAR_EVENT:
return "UserVarEvent";
case FORMAT_DESCRIPTION_EVENT:
return "FormatDescriptionEvent";
case XID_EVENT:
return "XIDEvent";
case BEGIN_LOAD_QUERY_EVENT:
return "BeginLoadQueryEvent";
case EXECUTE_LOAD_QUERY_EVENT:
return "ExecuteLoadQueryEvent";
case TABLE_MAP_EVENT:
return "TableMapEvent";
case WRITE_ROWS_EVENT_V0:
return "WriteRowsEventV0";
case UPDATE_ROWS_EVENT_V0:
return "UpdateRowsEventV0";
case DELETE_ROWS_EVENT_V0:
return "DeleteRowsEventV0";
case WRITE_ROWS_EVENT_V1:
return "WriteRowsEventV1";
case UPDATE_ROWS_EVENT_V1:
return "UpdateRowsEventV1";
case DELETE_ROWS_EVENT_V1:
return "DeleteRowsEventV1";
case INCIDENT_EVENT:
return "IncidentEvent";
case HEARTBEAT_EVENT:
return "HeartbeatEvent";
case IGNORABLE_EVENT:
return "IgnorableEvent";
case ROWS_QUERY_EVENT:
return "RowsQueryEvent";
case WRITE_ROWS_EVENT_V2:
return "WriteRowsEventV2";
case UPDATE_ROWS_EVENT_V2:
return "UpdateRowsEventV2";
case DELETE_ROWS_EVENT_V2:
return "DeleteRowsEventV2";
case GTID_EVENT:
return "GTIDEvent";
case ANONYMOUS_GTID_EVENT:
return "AnonymousGTIDEvent";
case PREVIOUS_GTIDS_EVENT:
return "PreviousGTIDsEvent";
case TRANSACTION_CONTEXT_EVENT:
return "TransactionContextEvent";
case VIEW_CHANGE_EVENT:
return "ViewChangeEvent";
case XA_PREPARE_LOG_EVENT:
return "XAPrepareLogEvent";
case MARIA_ANNOTATE_ROWS_EVENT:
return "MariaAnnotateRowsEvent";
case MARIA_BINLOG_CHECKPOINT_EVENT:
return "MariaBinlogCheckpointEvent";
case MARIA_GTID_EVENT:
return "MariaGTIDEvent";
case MARIA_GTID_LIST_EVENT:
return "MariaGTIDListEvent";
case MARIA_START_ENCRYPTION_EVENT:
return "MariaStartEncryptionEvent";
default:
break;
}
return std::string("Unknown event: ") + std::to_string(static_cast<int>(type));
}
enum MySQLEventType enum MySQLEventType
{ {
MYSQL_UNHANDLED_EVENT = 0, MYSQL_UNHANDLED_EVENT = 0,

View File

@ -127,8 +127,9 @@ class IColumn;
\ \
M(Bool, optimize_move_to_prewhere, true, "Allows disabling WHERE to PREWHERE optimization in SELECT queries from MergeTree.", 0) \ M(Bool, optimize_move_to_prewhere, true, "Allows disabling WHERE to PREWHERE optimization in SELECT queries from MergeTree.", 0) \
M(Bool, optimize_move_to_prewhere_if_final, false, "If query has `FINAL`, the optimization `move_to_prewhere` is not always correct and it is enabled only if both settings `optimize_move_to_prewhere` and `optimize_move_to_prewhere_if_final` are turned on", 0) \ M(Bool, optimize_move_to_prewhere_if_final, false, "If query has `FINAL`, the optimization `move_to_prewhere` is not always correct and it is enabled only if both settings `optimize_move_to_prewhere` and `optimize_move_to_prewhere_if_final` are turned on", 0) \
M(Bool, move_all_conditions_to_prewhere, false, "Move all viable conditions from WHERE to PREWHERE", 0) \ M(Bool, move_all_conditions_to_prewhere, true, "Move all viable conditions from WHERE to PREWHERE", 0) \
M(Bool, enable_multiple_prewhere_read_steps, false, "Move more conditions from WHERE to PREWHERE and do reads from disk and filtering in multiple steps if there are multiple conditions combined with AND", 0) \ M(Bool, enable_multiple_prewhere_read_steps, true, "Move more conditions from WHERE to PREWHERE and do reads from disk and filtering in multiple steps if there are multiple conditions combined with AND", 0) \
M(Bool, move_primary_key_columns_to_end_of_prewhere, true, "Move PREWHERE conditions containing primary key columns to the end of AND chain. It is likely that these conditions are taken into account during primary key analysis and thus will not contribute a lot to PREWHERE filtering.", 0) \
\ \
M(UInt64, alter_sync, 1, "Wait for actions to manipulate the partitions. 0 - do not wait, 1 - wait for execution only of itself, 2 - wait for everyone.", 0) ALIAS(replication_alter_partitions_sync) \ M(UInt64, alter_sync, 1, "Wait for actions to manipulate the partitions. 0 - do not wait, 1 - wait for execution only of itself, 2 - wait for everyone.", 0) ALIAS(replication_alter_partitions_sync) \
M(Int64, replication_wait_for_inactive_replica_timeout, 120, "Wait for inactive replica to execute ALTER/OPTIMIZE. Time in seconds, 0 - do not wait, negative - wait for unlimited time.", 0) \ M(Int64, replication_wait_for_inactive_replica_timeout, 120, "Wait for inactive replica to execute ALTER/OPTIMIZE. Time in seconds, 0 - do not wait, negative - wait for unlimited time.", 0) \
@ -577,7 +578,7 @@ class IColumn;
M(Bool, optimize_skip_merged_partitions, false, "Skip partitions with one part with level > 0 in optimize final", 0) \ M(Bool, optimize_skip_merged_partitions, false, "Skip partitions with one part with level > 0 in optimize final", 0) \
M(Bool, optimize_on_insert, true, "Do the same transformation for inserted block of data as if merge was done on this block.", 0) \ M(Bool, optimize_on_insert, true, "Do the same transformation for inserted block of data as if merge was done on this block.", 0) \
M(Bool, optimize_use_projections, true, "Automatically choose projections to perform SELECT query", 0) ALIAS(allow_experimental_projection_optimization) \ M(Bool, optimize_use_projections, true, "Automatically choose projections to perform SELECT query", 0) ALIAS(allow_experimental_projection_optimization) \
M(Bool, optimize_use_implicit_projections, false, "Automatically choose implicit projections to perform SELECT query", 0) \ M(Bool, optimize_use_implicit_projections, true, "Automatically choose implicit projections to perform SELECT query", 0) \
M(Bool, force_optimize_projection, false, "If projection optimization is enabled, SELECT queries need to use projection", 0) \ M(Bool, force_optimize_projection, false, "If projection optimization is enabled, SELECT queries need to use projection", 0) \
M(Bool, async_socket_for_remote, true, "Asynchronously read from socket executing remote query", 0) \ M(Bool, async_socket_for_remote, true, "Asynchronously read from socket executing remote query", 0) \
M(Bool, async_query_sending_for_remote, true, "Asynchronously create connections and send query to shards in remote query", 0) \ M(Bool, async_query_sending_for_remote, true, "Asynchronously create connections and send query to shards in remote query", 0) \
@ -875,6 +876,7 @@ class IColumn;
M(Bool, input_format_csv_detect_header, true, "Automatically detect header with names and types in CSV format", 0) \ M(Bool, input_format_csv_detect_header, true, "Automatically detect header with names and types in CSV format", 0) \
M(Bool, input_format_csv_allow_whitespace_or_tab_as_delimiter, false, "Allow to use spaces and tabs(\\t) as field delimiter in the CSV strings", 0) \ M(Bool, input_format_csv_allow_whitespace_or_tab_as_delimiter, false, "Allow to use spaces and tabs(\\t) as field delimiter in the CSV strings", 0) \
M(Bool, input_format_csv_trim_whitespaces, true, "Trims spaces and tabs (\\t) characters at the beginning and end in CSV strings", 0) \ M(Bool, input_format_csv_trim_whitespaces, true, "Trims spaces and tabs (\\t) characters at the beginning and end in CSV strings", 0) \
M(Bool, input_format_csv_use_default_on_bad_values, false, "Allow to set default value to column when CSV field deserialization failed on bad value", 0) \
M(Bool, input_format_tsv_detect_header, true, "Automatically detect header with names and types in TSV format", 0) \ M(Bool, input_format_tsv_detect_header, true, "Automatically detect header with names and types in TSV format", 0) \
M(Bool, input_format_custom_detect_header, true, "Automatically detect header with names and types in CustomSeparated format", 0) \ M(Bool, input_format_custom_detect_header, true, "Automatically detect header with names and types in CustomSeparated format", 0) \
M(Bool, input_format_parquet_skip_columns_with_unsupported_types_in_schema_inference, false, "Skip columns with unsupported types while schema inference for format Parquet", 0) \ M(Bool, input_format_parquet_skip_columns_with_unsupported_types_in_schema_inference, false, "Skip columns with unsupported types while schema inference for format Parquet", 0) \

View File

@ -80,8 +80,7 @@ namespace SettingsChangesHistory
/// It's used to implement `compatibility` setting (see https://github.com/ClickHouse/ClickHouse/issues/35972) /// It's used to implement `compatibility` setting (see https://github.com/ClickHouse/ClickHouse/issues/35972)
static std::map<ClickHouseVersion, SettingsChangesHistory::SettingsChanges> settings_changes_history = static std::map<ClickHouseVersion, SettingsChangesHistory::SettingsChanges> settings_changes_history =
{ {
{"23.7", {{"function_sleep_max_microseconds_per_block", 0, 3000000, "In previous versions, the maximum sleep time of 3 seconds was applied only for `sleep`, but not for `sleepEachRow` function. In the new version, we introduce this setting. If you set compatibility with the previous versions, we will disable the limit altogether."}, {"23.7", {{"function_sleep_max_microseconds_per_block", 0, 3000000, "In previous versions, the maximum sleep time of 3 seconds was applied only for `sleep`, but not for `sleepEachRow` function. In the new version, we introduce this setting. If you set compatibility with the previous versions, we will disable the limit altogether."}}},
{"optimize_use_implicit_projections", true, false, "Disable implicit projections due to unexpected results."}}},
{"23.6", {{"http_send_timeout", 180, 30, "3 minutes seems crazy long. Note that this is timeout for a single network write call, not for the whole upload operation."}, {"23.6", {{"http_send_timeout", 180, 30, "3 minutes seems crazy long. Note that this is timeout for a single network write call, not for the whole upload operation."},
{"http_receive_timeout", 180, 30, "See http_send_timeout."}}}, {"http_receive_timeout", 180, 30, "See http_send_timeout."}}},
{"23.5", {{"input_format_parquet_preserve_order", true, false, "Allow Parquet reader to reorder rows for better parallelism."}, {"23.5", {{"input_format_parquet_preserve_order", true, false, "Allow Parquet reader to reorder rows for better parallelism."},

View File

@ -138,7 +138,9 @@ IMPLEMENT_SETTING_ENUM(MsgPackUUIDRepresentation, ErrorCodes::BAD_ARGUMENTS,
IMPLEMENT_SETTING_ENUM(Dialect, ErrorCodes::BAD_ARGUMENTS, IMPLEMENT_SETTING_ENUM(Dialect, ErrorCodes::BAD_ARGUMENTS,
{{"clickhouse", Dialect::clickhouse}, {{"clickhouse", Dialect::clickhouse},
{"kusto", Dialect::kusto}}) {"kusto", Dialect::kusto},
{"kusto", Dialect::kusto},
{"prql", Dialect::prql}})
// FIXME: do not add 'kusto_auto' to the list. Maybe remove it from code completely? // FIXME: do not add 'kusto_auto' to the list. Maybe remove it from code completely?
IMPLEMENT_SETTING_ENUM(ParallelReplicasCustomKeyFilterType, ErrorCodes::BAD_ARGUMENTS, IMPLEMENT_SETTING_ENUM(ParallelReplicasCustomKeyFilterType, ErrorCodes::BAD_ARGUMENTS,

View File

@ -207,6 +207,7 @@ enum class Dialect
clickhouse, clickhouse,
kusto, kusto,
kusto_auto, kusto_auto,
prql,
}; };
DECLARE_SETTING_ENUM(Dialect) DECLARE_SETTING_ENUM(Dialect)

View File

@ -814,8 +814,8 @@ void DatabaseReplicated::recoverLostReplica(const ZooKeeperPtr & current_zookeep
{ {
auto query_context = Context::createCopy(getContext()); auto query_context = Context::createCopy(getContext());
query_context->makeQueryContext(); query_context->makeQueryContext();
query_context->getClientInfo().query_kind = ClientInfo::QueryKind::SECONDARY_QUERY; query_context->setQueryKind(ClientInfo::QueryKind::SECONDARY_QUERY);
query_context->getClientInfo().is_replicated_database_internal = true; query_context->setQueryKindReplicatedDatabaseInternal();
query_context->setCurrentDatabase(getDatabaseName()); query_context->setCurrentDatabase(getDatabaseName());
query_context->setCurrentQueryId(""); query_context->setCurrentQueryId("");
auto txn = std::make_shared<ZooKeeperMetadataTransaction>(current_zookeeper, zookeeper_path, false, ""); auto txn = std::make_shared<ZooKeeperMetadataTransaction>(current_zookeeper, zookeeper_path, false, "");

View File

@ -3,6 +3,7 @@
#if USE_MYSQL #if USE_MYSQL
#include <Databases/MySQL/MaterializedMySQLSyncThread.h> #include <Databases/MySQL/MaterializedMySQLSyncThread.h>
#include <Databases/MySQL/tryParseTableIDFromDDL.h>
#include <cstdlib> #include <cstdlib>
#include <random> #include <random>
#include <string_view> #include <string_view>
@ -59,7 +60,7 @@ static ContextMutablePtr createQueryContext(ContextPtr context)
query_context->setSettings(new_query_settings); query_context->setSettings(new_query_settings);
query_context->setInternalQuery(true); query_context->setInternalQuery(true);
query_context->getClientInfo().query_kind = ClientInfo::QueryKind::SECONDARY_QUERY; query_context->setQueryKind(ClientInfo::QueryKind::SECONDARY_QUERY);
query_context->setCurrentQueryId(""); // generate random query_id query_context->setCurrentQueryId(""); // generate random query_id
return query_context; return query_context;
} }
@ -151,61 +152,6 @@ static void checkMySQLVariables(const mysqlxx::Pool::Entry & connection, const S
} }
} }
static std::tuple<String, String> tryExtractTableNameFromDDL(const String & ddl)
{
String table_name;
String database_name;
if (ddl.empty()) return std::make_tuple(database_name, table_name);
bool parse_failed = false;
Tokens tokens(ddl.data(), ddl.data() + ddl.size());
IParser::Pos pos(tokens, 0);
Expected expected;
ASTPtr res;
ASTPtr table;
if (ParserKeyword("CREATE TEMPORARY TABLE").ignore(pos, expected) || ParserKeyword("CREATE TABLE").ignore(pos, expected))
{
ParserKeyword("IF NOT EXISTS").ignore(pos, expected);
if (!ParserCompoundIdentifier(true).parse(pos, table, expected))
parse_failed = true;
}
else if (ParserKeyword("ALTER TABLE").ignore(pos, expected))
{
if (!ParserCompoundIdentifier(true).parse(pos, table, expected))
parse_failed = true;
}
else if (ParserKeyword("DROP TABLE").ignore(pos, expected) || ParserKeyword("DROP TEMPORARY TABLE").ignore(pos, expected))
{
ParserKeyword("IF EXISTS").ignore(pos, expected);
if (!ParserCompoundIdentifier(true).parse(pos, table, expected))
parse_failed = true;
}
else if (ParserKeyword("TRUNCATE").ignore(pos, expected))
{
ParserKeyword("TABLE").ignore(pos, expected);
if (!ParserCompoundIdentifier(true).parse(pos, table, expected))
parse_failed = true;
}
else if (ParserKeyword("RENAME TABLE").ignore(pos, expected))
{
if (!ParserCompoundIdentifier(true).parse(pos, table, expected))
parse_failed = true;
}
else
{
parse_failed = true;
}
if (!parse_failed)
{
if (auto table_id = table->as<ASTTableIdentifier>()->getTableId())
{
database_name = table_id.database_name;
table_name = table_id.table_name;
}
}
return std::make_tuple(database_name, table_name);
}
MaterializedMySQLSyncThread::MaterializedMySQLSyncThread( MaterializedMySQLSyncThread::MaterializedMySQLSyncThread(
ContextPtr context_, ContextPtr context_,
const String & database_name_, const String & database_name_,
@ -868,14 +814,12 @@ void MaterializedMySQLSyncThread::executeDDLAtomic(const QueryEvent & query_even
String query = query_event.query; String query = query_event.query;
if (!materialized_tables_list.empty()) if (!materialized_tables_list.empty())
{ {
auto [ddl_database_name, ddl_table_name] = tryExtractTableNameFromDDL(query_event.query); auto table_id = tryParseTableIDFromDDL(query, query_event.schema);
if (!table_id.table_name.empty())
if (!ddl_table_name.empty())
{ {
ddl_database_name = ddl_database_name.empty() ? query_event.schema: ddl_database_name; if (table_id.database_name != mysql_database_name || !materialized_tables_list.contains(table_id.table_name))
if (ddl_database_name != mysql_database_name || !materialized_tables_list.contains(ddl_table_name))
{ {
LOG_DEBUG(log, "Skip MySQL DDL: \n {}", query_event.query); LOG_DEBUG(log, "Skip MySQL DDL for {}.{}:\n{}", table_id.database_name, table_id.table_name, query);
return; return;
} }
} }

View File

@ -0,0 +1,185 @@
#include "config.h"
#include <gtest/gtest.h>
#include <Databases/MySQL/tryParseTableIDFromDDL.h>
using namespace DB;
struct ParseTableIDFromDDLTestCase
{
String query;
String database_name;
String table_name;
ParseTableIDFromDDLTestCase(
const String & query_,
const String & database_name_,
const String & table_name_)
: query(query_)
, database_name(database_name_)
, table_name(table_name_)
{
}
};
std::ostream & operator<<(std::ostream & ostr, const ParseTableIDFromDDLTestCase & test_case)
{
return ostr << '"' << test_case.query << "\" extracts `" << test_case.database_name << "`.`" << test_case.table_name << "`";
}
class ParseTableIDFromDDLTest : public ::testing::TestWithParam<ParseTableIDFromDDLTestCase>
{
};
TEST_P(ParseTableIDFromDDLTest, parse)
{
const auto & [query, expected_database_name, expected_table_name] = GetParam();
auto table_id = tryParseTableIDFromDDL(query, "default");
EXPECT_EQ(expected_database_name, table_id.database_name);
EXPECT_EQ(expected_table_name, table_id.table_name);
}
INSTANTIATE_TEST_SUITE_P(MaterializedMySQL, ParseTableIDFromDDLTest, ::testing::ValuesIn(std::initializer_list<ParseTableIDFromDDLTestCase>{
{
"SELECT * FROM db.table",
"",
""
},
{
"CREATE TEMPORARY TABLE db.table",
"db",
"table"
},
{
"CREATE TEMPORARY TABLE IF NOT EXISTS db.table",
"db",
"table"
},
{
"CREATE TEMPORARY TABLE table",
"default",
"table"
},
{
"CREATE TEMPORARY TABLE IF NOT EXISTS table",
"default",
"table"
},
{
"CREATE TABLE db.table",
"db",
"table"
},
{
"CREATE TABLE IF NOT EXISTS db.table",
"db",
"table"
},
{
"CREATE TABLE table",
"default",
"table"
},
{
"CREATE TABLE IF NOT EXISTS table",
"default",
"table"
},
{
"ALTER TABLE db.table",
"db",
"table"
},
{
"ALTER TABLE table",
"default",
"table"
},
{
"DROP TABLE db.table",
"db",
"table"
},
{
"DROP TABLE IF EXISTS db.table",
"db",
"table"
},
{
"DROP TABLE table",
"default",
"table"
},
{
"DROP TABLE IF EXISTS table",
"default",
"table"
},
{
"DROP TEMPORARY TABLE db.table",
"db",
"table"
},
{
"DROP TEMPORARY TABLE IF EXISTS db.table",
"db",
"table"
},
{
"DROP TEMPORARY TABLE table",
"default",
"table"
},
{
"DROP TEMPORARY TABLE IF EXISTS table",
"default",
"table"
},
{
"TRUNCATE db.table",
"db",
"table"
},
{
"TRUNCATE TABLE db.table",
"db",
"table"
},
{
"TRUNCATE table1",
"default",
"table1"
},
{
"TRUNCATE TABLE table",
"default",
"table"
},
{
"RENAME TABLE db.table",
"db",
"table"
},
{
"RENAME TABLE table",
"default",
"table"
},
{
"DROP DATABASE db",
"",
""
},
{
"DROP DATA`BASE db",
"",
""
},
{
"NOT A SQL",
"",
""
},
}));

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