mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 23:21:59 +00:00
Merge remote-tracking branch 'origin/master' into HEAD
This commit is contained in:
commit
6293d1dbbe
@ -57,8 +57,8 @@ if (SANITIZE)
|
|||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
elseif (SANITIZE STREQUAL "undefined")
|
elseif (SANITIZE STREQUAL "undefined")
|
||||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero")
|
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero -fsanitize-blacklist=${CMAKE_SOURCE_DIR}/tests/ubsan_suppressions.txt")
|
||||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero")
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SAN_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all -fno-sanitize=float-divide-by-zero -fsanitize-blacklist=${CMAKE_SOURCE_DIR}/tests/ubsan_suppressions.txt")
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
|
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
|
||||||
endif()
|
endif()
|
||||||
|
69
debian/clickhouse-server.init
vendored
69
debian/clickhouse-server.init
vendored
@ -153,82 +153,19 @@ initdb()
|
|||||||
|
|
||||||
start()
|
start()
|
||||||
{
|
{
|
||||||
[ -x $CLICKHOUSE_BINDIR/$PROGRAM ] || exit 0
|
${CLICKHOUSE_GENERIC_PROGRAM} start --user "${CLICKHOUSE_USER}" --pid-path "${CLICKHOUSE_PIDDIR}" --config-path "${CLICKHOUSE_CONFDIR}" --binary-path "${CLICKHOUSE_BINDIR}"
|
||||||
local EXIT_STATUS
|
|
||||||
EXIT_STATUS=0
|
|
||||||
|
|
||||||
echo -n "Start $PROGRAM service: "
|
|
||||||
|
|
||||||
if is_running; then
|
|
||||||
echo -n "already running "
|
|
||||||
EXIT_STATUS=1
|
|
||||||
else
|
|
||||||
ulimit -n 262144
|
|
||||||
mkdir -p $CLICKHOUSE_PIDDIR
|
|
||||||
chown -R $CLICKHOUSE_USER:$CLICKHOUSE_GROUP $CLICKHOUSE_PIDDIR
|
|
||||||
initdb
|
|
||||||
if ! is_running; then
|
|
||||||
# Lock should not be held while running child process, so we release the lock. Note: obviously, there is race condition.
|
|
||||||
# But clickhouse-server has protection from simultaneous runs with same data directory.
|
|
||||||
su -s $SHELL ${CLICKHOUSE_USER} -c "$FLOCK -u 9; $CLICKHOUSE_PROGRAM_ENV exec -a \"$PROGRAM\" \"$CLICKHOUSE_BINDIR/$PROGRAM\" --daemon --pid-file=\"$CLICKHOUSE_PIDFILE\" --config-file=\"$CLICKHOUSE_CONFIG\""
|
|
||||||
EXIT_STATUS=$?
|
|
||||||
if [ $EXIT_STATUS -ne 0 ]; then
|
|
||||||
return $EXIT_STATUS
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $EXIT_STATUS -eq 0 ]; then
|
|
||||||
attempts=0
|
|
||||||
while ! is_running && [ $attempts -le ${CLICKHOUSE_START_TIMEOUT:=10} ]; do
|
|
||||||
attempts=$(($attempts + 1))
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
if is_running; then
|
|
||||||
echo "DONE"
|
|
||||||
else
|
|
||||||
echo "UNKNOWN"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "FAILED"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return $EXIT_STATUS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
stop()
|
stop()
|
||||||
{
|
{
|
||||||
#local EXIT_STATUS
|
${CLICKHOUSE_GENERIC_PROGRAM} stop --pid-path "${CLICKHOUSE_PIDDIR}"
|
||||||
EXIT_STATUS=0
|
|
||||||
|
|
||||||
if [ -f $CLICKHOUSE_PIDFILE ]; then
|
|
||||||
|
|
||||||
echo -n "Stop $PROGRAM service: "
|
|
||||||
|
|
||||||
kill -TERM $(cat "$CLICKHOUSE_PIDFILE")
|
|
||||||
|
|
||||||
if ! wait_for_done ${CLICKHOUSE_STOP_TIMEOUT}; then
|
|
||||||
EXIT_STATUS=2
|
|
||||||
echo "TIMEOUT"
|
|
||||||
else
|
|
||||||
echo "DONE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
return $EXIT_STATUS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
restart()
|
restart()
|
||||||
{
|
{
|
||||||
check_config
|
${CLICKHOUSE_GENERIC_PROGRAM} restart --user "${CLICKHOUSE_USER}" --pid-path "${CLICKHOUSE_PIDDIR}" --config-path "${CLICKHOUSE_CONFDIR}" --binary-path "${CLICKHOUSE_BINDIR}"
|
||||||
if stop; then
|
|
||||||
if start; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
104
debian/clickhouse-server.postinst
vendored
104
debian/clickhouse-server.postinst
vendored
@ -2,6 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
# set -x
|
# set -x
|
||||||
|
|
||||||
|
PROGRAM=clickhouse-server
|
||||||
CLICKHOUSE_USER=${CLICKHOUSE_USER:=clickhouse}
|
CLICKHOUSE_USER=${CLICKHOUSE_USER:=clickhouse}
|
||||||
CLICKHOUSE_GROUP=${CLICKHOUSE_GROUP:=${CLICKHOUSE_USER}}
|
CLICKHOUSE_GROUP=${CLICKHOUSE_GROUP:=${CLICKHOUSE_USER}}
|
||||||
# Please note that we don't support paths with whitespaces. This is rather ignorant.
|
# Please note that we don't support paths with whitespaces. This is rather ignorant.
|
||||||
@ -12,6 +13,7 @@ CLICKHOUSE_BINDIR=${CLICKHOUSE_BINDIR:=/usr/bin}
|
|||||||
CLICKHOUSE_GENERIC_PROGRAM=${CLICKHOUSE_GENERIC_PROGRAM:=clickhouse}
|
CLICKHOUSE_GENERIC_PROGRAM=${CLICKHOUSE_GENERIC_PROGRAM:=clickhouse}
|
||||||
EXTRACT_FROM_CONFIG=${CLICKHOUSE_GENERIC_PROGRAM}-extract-from-config
|
EXTRACT_FROM_CONFIG=${CLICKHOUSE_GENERIC_PROGRAM}-extract-from-config
|
||||||
CLICKHOUSE_CONFIG=$CLICKHOUSE_CONFDIR/config.xml
|
CLICKHOUSE_CONFIG=$CLICKHOUSE_CONFDIR/config.xml
|
||||||
|
CLICKHOUSE_PIDDIR=/var/run/$PROGRAM
|
||||||
|
|
||||||
[ -f /usr/share/debconf/confmodule ] && . /usr/share/debconf/confmodule
|
[ -f /usr/share/debconf/confmodule ] && . /usr/share/debconf/confmodule
|
||||||
[ -f /etc/default/clickhouse ] && . /etc/default/clickhouse
|
[ -f /etc/default/clickhouse ] && . /etc/default/clickhouse
|
||||||
@ -41,105 +43,5 @@ if [ "$1" = configure ] || [ -n "$not_deb_os" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Make sure the administrative user exists
|
${CLICKHOUSE_GENERIC_PROGRAM} install --user "${CLICKHOUSE_USER}" --group "${CLICKHOUSE_GROUP}" --pid-path "${CLICKHOUSE_PIDDIR}" --config-path "${CLICKHOUSE_CONFDIR}" --binary-path "${CLICKHOUSE_BINDIR}" --log-path "${CLICKHOUSE_LOGDIR}" --data-path "${CLICKHOUSE_DATADIR}"
|
||||||
if ! getent passwd ${CLICKHOUSE_USER} > /dev/null; then
|
|
||||||
if [ -n "$not_deb_os" ]; then
|
|
||||||
useradd -r -s /bin/false --home-dir /nonexistent ${CLICKHOUSE_USER} > /dev/null
|
|
||||||
else
|
|
||||||
adduser --system --disabled-login --no-create-home --home /nonexistent \
|
|
||||||
--shell /bin/false --group --gecos "ClickHouse server" ${CLICKHOUSE_USER} > /dev/null
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# if the user was created manually, make sure the group is there as well
|
|
||||||
if ! getent group ${CLICKHOUSE_GROUP} > /dev/null; then
|
|
||||||
groupadd -r ${CLICKHOUSE_GROUP} > /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# make sure user is in the correct group
|
|
||||||
if ! id -Gn ${CLICKHOUSE_USER} | grep -qw ${CLICKHOUSE_USER}; then
|
|
||||||
usermod -a -G ${CLICKHOUSE_GROUP} ${CLICKHOUSE_USER} > /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check validity of user and group
|
|
||||||
if [ "$(id -u ${CLICKHOUSE_USER})" -eq 0 ]; then
|
|
||||||
echo "The ${CLICKHOUSE_USER} system user must not have uid 0 (root).
|
|
||||||
Please fix this and reinstall this package." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$(id -g ${CLICKHOUSE_GROUP})" -eq 0 ]; then
|
|
||||||
echo "The ${CLICKHOUSE_USER} system user must not have root as primary group.
|
|
||||||
Please fix this and reinstall this package." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -x "$CLICKHOUSE_BINDIR/$EXTRACT_FROM_CONFIG" ] && [ -f "$CLICKHOUSE_CONFIG" ]; then
|
|
||||||
if [ -z "$SHELL" ]; then
|
|
||||||
SHELL="/bin/sh"
|
|
||||||
fi
|
|
||||||
CLICKHOUSE_DATADIR_FROM_CONFIG=$(su -s $SHELL ${CLICKHOUSE_USER} -c "$CLICKHOUSE_BINDIR/$EXTRACT_FROM_CONFIG --config-file=\"$CLICKHOUSE_CONFIG\" --key=path") ||:
|
|
||||||
echo "Path to data directory in ${CLICKHOUSE_CONFIG}: ${CLICKHOUSE_DATADIR_FROM_CONFIG}"
|
|
||||||
fi
|
|
||||||
CLICKHOUSE_DATADIR_FROM_CONFIG=${CLICKHOUSE_DATADIR_FROM_CONFIG:=$CLICKHOUSE_DATADIR}
|
|
||||||
|
|
||||||
if [ ! -d ${CLICKHOUSE_DATADIR_FROM_CONFIG} ]; then
|
|
||||||
mkdir -p ${CLICKHOUSE_DATADIR_FROM_CONFIG}
|
|
||||||
chown ${CLICKHOUSE_USER}:${CLICKHOUSE_GROUP} ${CLICKHOUSE_DATADIR_FROM_CONFIG}
|
|
||||||
chmod 700 ${CLICKHOUSE_DATADIR_FROM_CONFIG}
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d ${CLICKHOUSE_CONFDIR} ]; then
|
|
||||||
mkdir -p ${CLICKHOUSE_CONFDIR}/users.d
|
|
||||||
mkdir -p ${CLICKHOUSE_CONFDIR}/config.d
|
|
||||||
rm -fv ${CLICKHOUSE_CONFDIR}/*-preprocessed.xml ||:
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -e ${CLICKHOUSE_CONFDIR}/preprocessed ] || ln -s ${CLICKHOUSE_DATADIR_FROM_CONFIG}/preprocessed_configs ${CLICKHOUSE_CONFDIR}/preprocessed ||:
|
|
||||||
|
|
||||||
if [ ! -d ${CLICKHOUSE_LOGDIR} ]; then
|
|
||||||
mkdir -p ${CLICKHOUSE_LOGDIR}
|
|
||||||
chown root:${CLICKHOUSE_GROUP} ${CLICKHOUSE_LOGDIR}
|
|
||||||
# Allow everyone to read logs, root and clickhouse to read-write
|
|
||||||
chmod 775 ${CLICKHOUSE_LOGDIR}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set net_admin capabilities to support introspection of "taskstats" performance metrics from the kernel
|
|
||||||
# and ipc_lock capabilities to allow mlock of clickhouse binary.
|
|
||||||
|
|
||||||
# 1. Check that "setcap" tool exists.
|
|
||||||
# 2. Check that an arbitrary program with installed capabilities can run.
|
|
||||||
# 3. Set the capabilities.
|
|
||||||
|
|
||||||
# The second is important for Docker and systemd-nspawn.
|
|
||||||
# When the container has no capabilities,
|
|
||||||
# but the executable file inside the container has capabilities,
|
|
||||||
# then attempt to run this file will end up with a cryptic "Operation not permitted" message.
|
|
||||||
|
|
||||||
TMPFILE=/tmp/test_setcap.sh
|
|
||||||
|
|
||||||
command -v setcap >/dev/null \
|
|
||||||
&& echo > $TMPFILE && chmod a+x $TMPFILE && $TMPFILE && setcap "cap_net_admin,cap_ipc_lock,cap_sys_nice+ep" $TMPFILE && $TMPFILE && rm $TMPFILE \
|
|
||||||
&& setcap "cap_net_admin,cap_ipc_lock,cap_sys_nice+ep" "${CLICKHOUSE_BINDIR}/${CLICKHOUSE_GENERIC_PROGRAM}" \
|
|
||||||
|| echo "Cannot set 'net_admin' or 'ipc_lock' or 'sys_nice' capability for clickhouse binary. This is optional. Taskstats accounting will be disabled. To enable taskstats accounting you may add the required capability later manually."
|
|
||||||
|
|
||||||
# Clean old dynamic compilation results
|
|
||||||
if [ -d "${CLICKHOUSE_DATADIR_FROM_CONFIG}/build" ]; then
|
|
||||||
rm -f ${CLICKHOUSE_DATADIR_FROM_CONFIG}/build/*.cpp ${CLICKHOUSE_DATADIR_FROM_CONFIG}/build/*.so ||:
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f /usr/share/debconf/confmodule ]; then
|
|
||||||
db_get clickhouse-server/default-password
|
|
||||||
defaultpassword="$RET"
|
|
||||||
if [ -n "$defaultpassword" ]; then
|
|
||||||
echo "<yandex><users><default><password>$defaultpassword</password></default></users></yandex>" > ${CLICKHOUSE_CONFDIR}/users.d/default-password.xml
|
|
||||||
chown ${CLICKHOUSE_USER}:${CLICKHOUSE_GROUP} ${CLICKHOUSE_CONFDIR}/users.d/default-password.xml
|
|
||||||
chmod 600 ${CLICKHOUSE_CONFDIR}/users.d/default-password.xml
|
|
||||||
fi
|
|
||||||
|
|
||||||
# everything went well, so now let's reset the password
|
|
||||||
db_set clickhouse-server/default-password ""
|
|
||||||
# ... done with debconf here
|
|
||||||
db_stop
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
@ -53,7 +53,6 @@ RUN apt-get update \
|
|||||||
ninja-build \
|
ninja-build \
|
||||||
psmisc \
|
psmisc \
|
||||||
python3 \
|
python3 \
|
||||||
python3-pip \
|
|
||||||
python3-lxml \
|
python3-lxml \
|
||||||
python3-requests \
|
python3-requests \
|
||||||
python3-termcolor \
|
python3-termcolor \
|
||||||
@ -63,8 +62,6 @@ RUN apt-get update \
|
|||||||
unixodbc \
|
unixodbc \
|
||||||
--yes --no-install-recommends
|
--yes --no-install-recommends
|
||||||
|
|
||||||
RUN pip3 install numpy scipy pandas
|
|
||||||
|
|
||||||
# This symlink required by gcc to find lld compiler
|
# This symlink required by gcc to find lld compiler
|
||||||
RUN ln -s /usr/bin/lld-${LLVM_VERSION} /usr/bin/ld.lld
|
RUN ln -s /usr/bin/lld-${LLVM_VERSION} /usr/bin/ld.lld
|
||||||
|
|
||||||
|
@ -219,6 +219,8 @@ TESTS_TO_SKIP=(
|
|||||||
01268_dictionary_direct_layout
|
01268_dictionary_direct_layout
|
||||||
01280_ssd_complex_key_dictionary
|
01280_ssd_complex_key_dictionary
|
||||||
01281_group_by_limit_memory_tracking # max_memory_usage_for_user can interfere another queries running concurrently
|
01281_group_by_limit_memory_tracking # max_memory_usage_for_user can interfere another queries running concurrently
|
||||||
|
01318_encrypt # Depends on OpenSSL
|
||||||
|
01318_decrypt # Depends on OpenSSL
|
||||||
01281_unsucceeded_insert_select_queries_counter
|
01281_unsucceeded_insert_select_queries_counter
|
||||||
01292_create_user
|
01292_create_user
|
||||||
01294_lazy_database_concurrent
|
01294_lazy_database_concurrent
|
||||||
|
@ -48,12 +48,13 @@ This table shows queries that take significantly longer to process on the client
|
|||||||
#### Unexpected Query Duration
|
#### Unexpected Query Duration
|
||||||
Action required for every item -- these are errors that must be fixed.
|
Action required for every item -- these are errors that must be fixed.
|
||||||
|
|
||||||
Queries that have "short" duration (on the order of 0.1 s) can't be reliably tested in a normal way, where we perform a small (about ten) measurements for each server, because the signal-to-noise ratio is much smaller. There is a special mode for such queries that instead runs them for a fixed amount of time, normally with much higher number of measurements (up to thousands). This mode must be explicitly enabled by the test author to avoid accidental errors. It must be used only for queries that are meant to complete "immediately", such as `select count(*)`. If your query is not supposed to be "immediate", try to make it run longer, by e.g. processing more data.
|
A query is supposed to run longer than 0.1 second. If your query runs faster, increase the amount of processed data to bring the run time above this threshold. You can use a bigger table (e.g. `hits_100m` instead of `hits_10m`), increase a `LIMIT`, make a query single-threaded, and so on. Queries that are too fast suffer from poor stability and precision.
|
||||||
|
|
||||||
This table shows queries for which the "short" marking is not consistent with the actual query run time -- i.e., a query runs for a long time but is marked as short, or it runs very fast but is not marked as short.
|
Sometimes you want to test a query that is supposed to complete "instantaneously", i.e. in sublinear time. This might be `count(*)`, or parsing a complicated tuple. It might not be practical or even possible to increase the run time of such queries by adding more data. For such queries there is a specal comparison mode which runs them for a fixed amount of time, instead of a fixed number of iterations like we do normally. This mode is inferior to the normal mode, because the influence of noise and overhead is higher, which leads to less precise and stable results.
|
||||||
|
|
||||||
If your query is really supposed to complete "immediately" and can't be made to run longer, you have to mark it as "short". To do so, write `<query short="1">...` in the test file. The value of "short" attribute is evaluated as a python expression, and substitutions are performed, so you can write something like `<query short="{column1} = {column2}">select count(*) from table where {column1} > {column2}</query>`, to mark only a particular combination of variables as short.
|
If it is impossible to increase the run time of a query and it is supposed to complete "immediately", you have to explicitly mark this in the test. To do so, add a `short` attribute to the query tag in the test file: `<query short="1">...`. The value of the `short` attribute is evaluated as a python expression, and substitutions are performed, so you can write something like `<query short="{column1} = {column2}">select count(*) from table where {column1} > {column2}</query>`, to mark only a particular combination of variables as short.
|
||||||
|
|
||||||
|
This table shows queries for which the `short` marking is not consistent with the actual query run time -- i.e., a query runs for a normal time but is marked as `short`, or it runs faster than normal but is not marked as `short`.
|
||||||
|
|
||||||
#### Partial Queries
|
#### Partial Queries
|
||||||
Action required for the cells marked in red.
|
Action required for the cells marked in red.
|
||||||
|
@ -469,12 +469,12 @@ if args.report == 'main':
|
|||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
'Test', #0
|
'Test', #0
|
||||||
'Wall clock time, s', #1
|
'Wall clock time, entire test, s', #1
|
||||||
'Total client time, s', #2
|
'Total client time for measured query runs, s', #2
|
||||||
'Total queries', #3
|
'Queries', #3
|
||||||
'Longest query<br>(sum for all runs), s', #4
|
'Longest query, total for measured runs, s', #4
|
||||||
'Avg wall clock time<br>(sum for all runs), s', #5
|
'Wall clock time per query, s', #5
|
||||||
'Shortest query<br>(sum for all runs), s', #6
|
'Shortest query, total for measured runs, s', #6
|
||||||
'', # Runs #7
|
'', # Runs #7
|
||||||
]
|
]
|
||||||
attrs = ['' for c in columns]
|
attrs = ['' for c in columns]
|
||||||
|
@ -88,6 +88,7 @@ toc_title: Adopters
|
|||||||
| <a href="https://smi2.ru/" class="favicon">SMI2</a> | News | Analytics | — | — | [Blog Post in Russian, November 2017](https://habr.com/ru/company/smi2/blog/314558/) |
|
| <a href="https://smi2.ru/" class="favicon">SMI2</a> | News | Analytics | — | — | [Blog Post in Russian, November 2017](https://habr.com/ru/company/smi2/blog/314558/) |
|
||||||
| <a href="https://www.splunk.com/" class="favicon">Splunk</a> | Business Analytics | Main product | — | — | [Slides in English, January 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup12/splunk.pdf) |
|
| <a href="https://www.splunk.com/" class="favicon">Splunk</a> | Business Analytics | Main product | — | — | [Slides in English, January 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup12/splunk.pdf) |
|
||||||
| <a href="https://www.spotify.com" class="favicon">Spotify</a> | Music | Experimentation | — | — | [Slides, July 2018](https://www.slideshare.net/glebus/using-clickhouse-for-experimentation-104247173) |
|
| <a href="https://www.spotify.com" class="favicon">Spotify</a> | Music | Experimentation | — | — | [Slides, July 2018](https://www.slideshare.net/glebus/using-clickhouse-for-experimentation-104247173) |
|
||||||
|
| <a href="https://www.staffcop.ru/" class="favicon">Staffcop</a> | Information Security | Main Product | — | — | [Official website, Documentation](https://www.staffcop.ru/sce43) |
|
||||||
| <a href="https://www.tencent.com" class="favicon">Tencent</a> | Big Data | Data processing | — | — | [Slides in Chinese, October 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup19/5.%20ClickHouse大数据集群应用_李俊飞腾讯网媒事业部.pdf) |
|
| <a href="https://www.tencent.com" class="favicon">Tencent</a> | Big Data | Data processing | — | — | [Slides in Chinese, October 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup19/5.%20ClickHouse大数据集群应用_李俊飞腾讯网媒事业部.pdf) |
|
||||||
| <a href="https://www.tencent.com" class="favicon">Tencent</a> | Messaging | Logging | — | — | [Talk in Chinese, November 2019](https://youtu.be/T-iVQRuw-QY?t=5050) |
|
| <a href="https://www.tencent.com" class="favicon">Tencent</a> | Messaging | Logging | — | — | [Talk in Chinese, November 2019](https://youtu.be/T-iVQRuw-QY?t=5050) |
|
||||||
| <a href="https://trafficstars.com/" class="favicon">Traffic Stars</a> | AD network | — | — | — | [Slides in Russian, May 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup15/lightning/ninja.pdf) |
|
| <a href="https://trafficstars.com/" class="favicon">Traffic Stars</a> | AD network | — | — | — | [Slides in Russian, May 2018](https://github.com/ClickHouse/clickhouse-presentations/blob/master/meetup15/lightning/ninja.pdf) |
|
||||||
|
@ -305,6 +305,10 @@ When enabled, replace empty input fields in TSV with default values. For complex
|
|||||||
|
|
||||||
Disabled by default.
|
Disabled by default.
|
||||||
|
|
||||||
|
## input_format_tsv_enum_as_number {#settings-input_format_tsv_enum_as_number}
|
||||||
|
|
||||||
|
For TSV input format switches to parsing enum values as enum ids.
|
||||||
|
|
||||||
## input_format_null_as_default {#settings-input-format-null-as-default}
|
## input_format_null_as_default {#settings-input-format-null-as-default}
|
||||||
|
|
||||||
Enables or disables using default values if input data contain `NULL`, but the data type of the corresponding column in not `Nullable(T)` (for text input formats).
|
Enables or disables using default values if input data contain `NULL`, but the data type of the corresponding column in not `Nullable(T)` (for text input formats).
|
||||||
@ -1161,6 +1165,10 @@ The character is interpreted as a delimiter in the CSV data. By default, the del
|
|||||||
|
|
||||||
For CSV input format enables or disables parsing of unquoted `NULL` as literal (synonym for `\N`).
|
For CSV input format enables or disables parsing of unquoted `NULL` as literal (synonym for `\N`).
|
||||||
|
|
||||||
|
## input_format_csv_enum_as_number {#settings-input_format_csv_enum_as_number}
|
||||||
|
|
||||||
|
For CSV input format switches to parsing enum values as enum ids.
|
||||||
|
|
||||||
## output_format_csv_crlf_end_of_line {#settings-output-format-csv-crlf-end-of-line}
|
## output_format_csv_crlf_end_of_line {#settings-output-format-csv-crlf-end-of-line}
|
||||||
|
|
||||||
Use DOS/Windows-style line separator (CRLF) in CSV instead of Unix style (LF).
|
Use DOS/Windows-style line separator (CRLF) in CSV instead of Unix style (LF).
|
||||||
@ -1398,6 +1406,17 @@ Possible values:
|
|||||||
|
|
||||||
Default value: 0
|
Default value: 0
|
||||||
|
|
||||||
|
## allow_nondeterministic_optimize_skip_unused_shards {#allow-nondeterministic-optimize-skip-unused-shards}
|
||||||
|
|
||||||
|
Allow nondeterministic (like `rand` or `dictGet`, since later has some caveats with updates) functions in sharding key.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
- 0 — Disallowed.
|
||||||
|
- 1 — Allowed.
|
||||||
|
|
||||||
|
Default value: 0
|
||||||
|
|
||||||
## optimize_skip_unused_shards_nesting {#optimize-skip-unused-shards-nesting}
|
## optimize_skip_unused_shards_nesting {#optimize-skip-unused-shards-nesting}
|
||||||
|
|
||||||
Controls [`optimize_skip_unused_shards`](#optimize-skip-unused-shards) (hence still requires [`optimize_skip_unused_shards`](#optimize-skip-unused-shards)) depends on the nesting level of the distributed query (case when you have `Distributed` table that look into another `Distributed` table).
|
Controls [`optimize_skip_unused_shards`](#optimize-skip-unused-shards) (hence still requires [`optimize_skip_unused_shards`](#optimize-skip-unused-shards)) depends on the nesting level of the distributed query (case when you have `Distributed` table that look into another `Distributed` table).
|
||||||
|
@ -461,6 +461,66 @@ For other regular expressions, the code is the same as for the ‘match’ funct
|
|||||||
|
|
||||||
The same thing as ‘like’, but negative.
|
The same thing as ‘like’, but negative.
|
||||||
|
|
||||||
|
## ilike {#ilike}
|
||||||
|
|
||||||
|
Case insensitive variant of [like](https://clickhouse.tech/docs/en/sql-reference/functions/string-search-functions/#function-like) function. You can use `ILIKE` operator instead of the `ilike` function.
|
||||||
|
|
||||||
|
**Syntax**
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
ilike(haystack, pattern)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
- `haystack` — Input string. [String](../../sql-reference/syntax.md#syntax-string-literal).
|
||||||
|
- `pattern` — If `pattern` doesn't contain percent signs or underscores, then the `pattern` only represents the string itself. An underscore (`_`) in `pattern` stands for (matches) any single character. A percent sign (`%`) matches any sequence of zero or more characters.
|
||||||
|
|
||||||
|
Some `pattern` examples:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
'abc' ILIKE 'abc' true
|
||||||
|
'abc' ILIKE 'a%' true
|
||||||
|
'abc' ILIKE '_b_' true
|
||||||
|
'abc' ILIKE 'c' false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returned values**
|
||||||
|
|
||||||
|
- True, if the string matches `pattern`.
|
||||||
|
- False, if the string doesn't match `pattern`.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
Input table:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─id─┬─name─────┬─days─┐
|
||||||
|
│ 1 │ January │ 31 │
|
||||||
|
│ 2 │ February │ 29 │
|
||||||
|
│ 3 │ March │ 31 │
|
||||||
|
│ 4 │ April │ 30 │
|
||||||
|
└────┴──────────┴──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Query:
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM Months WHERE ilike(name, '%j%')
|
||||||
|
```
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─id─┬─name────┬─days─┐
|
||||||
|
│ 1 │ January │ 31 │
|
||||||
|
└────┴─────────┴──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**See Also**
|
||||||
|
|
||||||
|
- [like](https://clickhouse.tech/docs/en/sql-reference/functions/string-search-functions/#function-like) <!--hide-->
|
||||||
|
|
||||||
## ngramDistance(haystack, needle) {#ngramdistancehaystack-needle}
|
## ngramDistance(haystack, needle) {#ngramdistancehaystack-needle}
|
||||||
|
|
||||||
Calculates the 4-gram distance between `haystack` and `needle`: counts the symmetric difference between two multisets of 4-grams and normalizes it by the sum of their cardinalities. Returns float number from 0 to 1 – the closer to zero, the more strings are similar to each other. If the constant `needle` or `haystack` is more than 32Kb, throws an exception. If some of the non-constant `haystack` or `needle` strings are more than 32Kb, the distance is always one.
|
Calculates the 4-gram distance between `haystack` and `needle`: counts the symmetric difference between two multisets of 4-grams and normalizes it by the sum of their cardinalities. Returns float number from 0 to 1 – the closer to zero, the more strings are similar to each other. If the constant `needle` or `haystack` is more than 32Kb, throws an exception. If some of the non-constant `haystack` or `needle` strings are more than 32Kb, the distance is always one.
|
||||||
|
@ -53,6 +53,8 @@ ClickHouse transforms operators to their corresponding functions at the query pa
|
|||||||
|
|
||||||
`a NOT LIKE s` – The `notLike(a, b)` function.
|
`a NOT LIKE s` – The `notLike(a, b)` function.
|
||||||
|
|
||||||
|
`a ILIKE s` – The `ilike(a, b)` function.
|
||||||
|
|
||||||
`a BETWEEN b AND c` – The same as `a >= b AND a <= c`.
|
`a BETWEEN b AND c` – The same as `a >= b AND a <= c`.
|
||||||
|
|
||||||
`a NOT BETWEEN b AND c` – The same as `a < b OR a > c`.
|
`a NOT BETWEEN b AND c` – The same as `a < b OR a > c`.
|
||||||
|
@ -139,7 +139,7 @@ ENGINE = <Engine>
|
|||||||
```
|
```
|
||||||
|
|
||||||
The `Default` codec can be specified to reference default compression which may dependend on different settings (and properties of data) in runtime.
|
The `Default` codec can be specified to reference default compression which may dependend on different settings (and properties of data) in runtime.
|
||||||
Example: `value UInt64 CODEC(Default)` - the same as lack of codec specification.
|
Example: `value UInt64 CODEC(Default)` — the same as lack of codec specification.
|
||||||
|
|
||||||
Also you can remove current CODEC from the column and use default compression from config.xml:
|
Also you can remove current CODEC from the column and use default compression from config.xml:
|
||||||
|
|
||||||
|
67
docs/en/sql-reference/table-functions/view.md
Normal file
67
docs/en/sql-reference/table-functions/view.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
toc_priority: 51
|
||||||
|
toc_title: view
|
||||||
|
---
|
||||||
|
|
||||||
|
## view {#view}
|
||||||
|
|
||||||
|
Turns a subquery into a table. The function implements views (see [CREATE VIEW](https://clickhouse.tech/docs/en/sql-reference/statements/create/view/#create-view)). The resulting table doesn't store data, but only stores the specified `SELECT` query. When reading from the table, ClickHouse executes the query and deletes all unnecessary columns from the result.
|
||||||
|
|
||||||
|
**Syntax**
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
view(subquery)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
- `subquery` — `SELECT` query.
|
||||||
|
|
||||||
|
**Returned value**
|
||||||
|
|
||||||
|
- A table.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
Input table:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─id─┬─name─────┬─days─┐
|
||||||
|
│ 1 │ January │ 31 │
|
||||||
|
│ 2 │ February │ 29 │
|
||||||
|
│ 3 │ March │ 31 │
|
||||||
|
│ 4 │ April │ 30 │
|
||||||
|
└────┴──────────┴──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Query:
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM view(SELECT name FROM months)
|
||||||
|
```
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─name─────┐
|
||||||
|
│ January │
|
||||||
|
│ February │
|
||||||
|
│ March │
|
||||||
|
│ April │
|
||||||
|
└──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use the `view` function as a parameter of the [remote](https://clickhouse.tech/docs/en/sql-reference/table-functions/remote/#remote-remotesecure) and [cluster](https://clickhouse.tech/docs/en/sql-reference/table-functions/cluster/#cluster-clusterallreplicas) table functions:
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM remote(`127.0.0.1`, view(SELECT a, b, c FROM table_name))
|
||||||
|
```
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM cluster(`cluster_name`, view(SELECT a, b, c FROM table_name))
|
||||||
|
```
|
||||||
|
|
||||||
|
**See Also**
|
||||||
|
|
||||||
|
- [View Table Engine](https://clickhouse.tech/docs/en/engines/table-engines/special/view/)
|
||||||
|
[Original article](https://clickhouse.tech/docs/en/query_language/table_functions/view/) <!--hide-->
|
@ -34,6 +34,7 @@ ClickHouse не удаляет данные из таблица автомати
|
|||||||
- `event_date` ([Date](../../sql-reference/data-types/date.md)) — дата начала запроса.
|
- `event_date` ([Date](../../sql-reference/data-types/date.md)) — дата начала запроса.
|
||||||
- `event_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — время начала запроса.
|
- `event_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — время начала запроса.
|
||||||
- `query_start_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — время начала обработки запроса.
|
- `query_start_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — время начала обработки запроса.
|
||||||
|
- `query_start_time_microseconds` ([DateTime64](../../sql-reference/data-types/datetime64.md)) — время начала обработки запроса с точностью до микросекунд.
|
||||||
- `query_duration_ms` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — длительность выполнения запроса в миллисекундах.
|
- `query_duration_ms` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — длительность выполнения запроса в миллисекундах.
|
||||||
- `read_rows` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — Общее количество строк, считанных из всех таблиц и табличных функций, участвующих в запросе. Включает в себя обычные подзапросы, подзапросы для `IN` и `JOIN`. Для распределенных запросов `read_rows` включает в себя общее количество строк, прочитанных на всех репликах. Каждая реплика передает собственное значение `read_rows`, а сервер-инициатор запроса суммирует все полученные и локальные значения. Объемы кэша не учитываюся.
|
- `read_rows` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — Общее количество строк, считанных из всех таблиц и табличных функций, участвующих в запросе. Включает в себя обычные подзапросы, подзапросы для `IN` и `JOIN`. Для распределенных запросов `read_rows` включает в себя общее количество строк, прочитанных на всех репликах. Каждая реплика передает собственное значение `read_rows`, а сервер-инициатор запроса суммирует все полученные и локальные значения. Объемы кэша не учитываюся.
|
||||||
- `read_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — Общее количество байтов, считанных из всех таблиц и табличных функций, участвующих в запросе. Включает в себя обычные подзапросы, подзапросы для `IN` и `JOIN`. Для распределенных запросов `read_bytes` включает в себя общее количество байтов, прочитанных на всех репликах. Каждая реплика передает собственное значение `read_bytes`, а сервер-инициатор запроса суммирует все полученные и локальные значения. Объемы кэша не учитываюся.
|
- `read_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — Общее количество байтов, считанных из всех таблиц и табличных функций, участвующих в запросе. Включает в себя обычные подзапросы, подзапросы для `IN` и `JOIN`. Для распределенных запросов `read_bytes` включает в себя общее количество байтов, прочитанных на всех репликах. Каждая реплика передает собственное значение `read_bytes`, а сервер-инициатор запроса суммирует все полученные и локальные значения. Объемы кэша не учитываюся.
|
||||||
|
@ -16,6 +16,7 @@ ClickHouse не удаляет данные из таблицы автомати
|
|||||||
- `event_date` ([Date](../../sql-reference/data-types/date.md)) — дата завершения выполнения запроса потоком.
|
- `event_date` ([Date](../../sql-reference/data-types/date.md)) — дата завершения выполнения запроса потоком.
|
||||||
- `event_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — дата и время завершения выполнения запроса потоком.
|
- `event_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — дата и время завершения выполнения запроса потоком.
|
||||||
- `query_start_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — время начала обработки запроса.
|
- `query_start_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — время начала обработки запроса.
|
||||||
|
- `query_start_time_microseconds` ([DateTime64](../../sql-reference/data-types/datetime64.md)) — время начала обработки запроса с точностью до микросекунд.
|
||||||
- `query_duration_ms` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — длительность обработки запроса в миллисекундах.
|
- `query_duration_ms` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — длительность обработки запроса в миллисекундах.
|
||||||
- `read_rows` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — количество прочитанных строк.
|
- `read_rows` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — количество прочитанных строк.
|
||||||
- `read_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — количество прочитанных байтов.
|
- `read_bytes` ([UInt64](../../sql-reference/data-types/int-uint.md#uint-ranges)) — количество прочитанных байтов.
|
||||||
|
@ -442,6 +442,66 @@ SELECT extractAllGroupsVertical('abc=111, def=222, ghi=333', '("[^"]+"|\\w+)=("[
|
|||||||
|
|
||||||
То же, что like, но с отрицанием.
|
То же, что like, но с отрицанием.
|
||||||
|
|
||||||
|
## ilike {#ilike}
|
||||||
|
|
||||||
|
Нечувствительный к регистру вариант функции [like](https://clickhouse.tech/docs/ru/sql-reference/functions/string-search-functions/#function-like). Вы можете использовать оператор `ILIKE` вместо функции `ilike`.
|
||||||
|
|
||||||
|
**Синтаксис**
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
ilike(haystack, pattern)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Параметры**
|
||||||
|
|
||||||
|
- `haystack` — Входная строка. [String](../../sql-reference/syntax.md#syntax-string-literal).
|
||||||
|
- `pattern` — Если `pattern` не содержит процента или нижнего подчеркивания, тогда `pattern` представляет саму строку. Нижнее подчеркивание (`_`) в `pattern` обозначает любой отдельный символ. Знак процента (`%`) соответствует последовательности из любого количества символов: от нуля и более.
|
||||||
|
|
||||||
|
Некоторые примеры `pattern`:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
'abc' ILIKE 'abc' true
|
||||||
|
'abc' ILIKE 'a%' true
|
||||||
|
'abc' ILIKE '_b_' true
|
||||||
|
'abc' ILIKE 'c' false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Возвращаемые значения**
|
||||||
|
|
||||||
|
- Правда, если строка соответствует `pattern`.
|
||||||
|
- Ложь, если строка не соответствует `pattern`.
|
||||||
|
|
||||||
|
**Пример**
|
||||||
|
|
||||||
|
Входная таблица:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─id─┬─name─────┬─days─┐
|
||||||
|
│ 1 │ January │ 31 │
|
||||||
|
│ 2 │ February │ 29 │
|
||||||
|
│ 3 │ March │ 31 │
|
||||||
|
│ 4 │ April │ 30 │
|
||||||
|
└────┴──────────┴──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Запрос:
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM Months WHERE ilike(name, '%j%')
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─id─┬─name────┬─days─┐
|
||||||
|
│ 1 │ January │ 31 │
|
||||||
|
└────┴─────────┴──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Смотрите также**
|
||||||
|
|
||||||
|
- [like](https://clickhouse.tech/docs/ru/sql-reference/functions/string-search-functions/#function-like) <!--hide-->
|
||||||
|
|
||||||
## ngramDistance(haystack, needle) {#ngramdistancehaystack-needle}
|
## ngramDistance(haystack, needle) {#ngramdistancehaystack-needle}
|
||||||
|
|
||||||
Вычисление 4-граммного расстояния между `haystack` и `needle`: считается симметрическая разность между двумя мультимножествами 4-грамм и нормализуется на сумму их мощностей. Возвращает число float от 0 до 1 – чем ближе к нулю, тем больше строки похожи друг на друга. Если константный `needle` или `haystack` больше чем 32КБ, кидается исключение. Если некоторые строки из неконстантного `haystack` или `needle` больше 32КБ, расстояние всегда равно единице.
|
Вычисление 4-граммного расстояния между `haystack` и `needle`: считается симметрическая разность между двумя мультимножествами 4-грамм и нормализуется на сумму их мощностей. Возвращает число float от 0 до 1 – чем ближе к нулю, тем больше строки похожи друг на друга. Если константный `needle` или `haystack` больше чем 32КБ, кидается исключение. Если некоторые строки из неконстантного `haystack` или `needle` больше 32КБ, расстояние всегда равно единице.
|
||||||
|
@ -49,6 +49,8 @@
|
|||||||
|
|
||||||
`a NOT LIKE s` - функция `notLike(a, b)`
|
`a NOT LIKE s` - функция `notLike(a, b)`
|
||||||
|
|
||||||
|
`a ILIKE s` – функция `ilike(a, b)`
|
||||||
|
|
||||||
`a BETWEEN b AND c` - равнозначно `a >= b AND a <= c`
|
`a BETWEEN b AND c` - равнозначно `a >= b AND a <= c`
|
||||||
|
|
||||||
`a NOT BETWEEN b AND c` - равнозначно `a < b OR a > c`
|
`a NOT BETWEEN b AND c` - равнозначно `a < b OR a > c`
|
||||||
|
@ -119,7 +119,18 @@ ENGINE = <Engine>
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
Если задать кодек для столбца, то кодек по умолчанию не применяется. Кодеки можно последовательно комбинировать, например, `CODEC(Delta, ZSTD)`. Чтобы выбрать наиболее подходящую для вашего проекта комбинацию кодеков, необходимо провести сравнительные тесты, подобные тем, что описаны в статье Altinity [New Encodings to Improve ClickHouse Efficiency](https://www.altinity.com/blog/2019/7/new-encodings-to-improve-clickhouse).
|
Если кодек `Default` задан для столбца, используется сжатие по умолчанию, которое может зависеть от различных настроек (и свойств данных) во время выполнения.
|
||||||
|
Пример: `value UInt64 CODEC(Default)` — то же самое, что не указать кодек.
|
||||||
|
|
||||||
|
Также можно подменить кодек столбца сжатием по умолчанию, определенным в config.xml:
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
ALTER TABLE codec_example MODIFY COLUMN float_value CODEC(Default);
|
||||||
|
```
|
||||||
|
|
||||||
|
Кодеки можно последовательно комбинировать, например, `CODEC(Delta, Default)`.
|
||||||
|
|
||||||
|
Чтобы выбрать наиболее подходящую для вашего проекта комбинацию кодеков, необходимо провести сравнительные тесты, подобные тем, что описаны в статье Altinity [New Encodings to Improve ClickHouse Efficiency](https://www.altinity.com/blog/2019/7/new-encodings-to-improve-clickhouse). Для столбцов типа `ALIAS` кодеки не применяются.
|
||||||
|
|
||||||
!!! warning "Предупреждение"
|
!!! warning "Предупреждение"
|
||||||
Нельзя распаковать базу данных ClickHouse с помощью сторонних утилит наподобие `lz4`. Необходимо использовать специальную утилиту [clickhouse-compressor](https://github.com/ClickHouse/ClickHouse/tree/master/programs/compressor).
|
Нельзя распаковать базу данных ClickHouse с помощью сторонних утилит наподобие `lz4`. Необходимо использовать специальную утилиту [clickhouse-compressor](https://github.com/ClickHouse/ClickHouse/tree/master/programs/compressor).
|
||||||
|
62
docs/ru/sql-reference/table-functions/view.md
Normal file
62
docs/ru/sql-reference/table-functions/view.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
## view {#view}
|
||||||
|
|
||||||
|
Преобразовывает подзапрос в таблицу. Функция реализовывает представления (смотрите [CREATE VIEW](https://clickhouse.tech/docs/ru/sql-reference/statements/create/view/#create-view)). Результирующая таблица не хранит данные, а только сохраняет указанный запрос `SELECT`. При чтении из таблицы, ClickHouse выполняет запрос и удаляет все ненужные столбцы из результата.
|
||||||
|
|
||||||
|
**Синтаксис**
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
view(subquery)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Входные параметры**
|
||||||
|
|
||||||
|
- `subquery` — запрос `SELECT`.
|
||||||
|
|
||||||
|
**Возвращаемое значение**
|
||||||
|
|
||||||
|
- Таблица.
|
||||||
|
|
||||||
|
**Пример**
|
||||||
|
|
||||||
|
Входная таблица:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─id─┬─name─────┬─days─┐
|
||||||
|
│ 1 │ January │ 31 │
|
||||||
|
│ 2 │ February │ 29 │
|
||||||
|
│ 3 │ March │ 31 │
|
||||||
|
│ 4 │ April │ 30 │
|
||||||
|
└────┴──────────┴──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Запрос:
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM view(SELECT name FROM months)
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
``` text
|
||||||
|
┌─name─────┐
|
||||||
|
│ January │
|
||||||
|
│ February │
|
||||||
|
│ March │
|
||||||
|
│ April │
|
||||||
|
└──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Вы можете использовать функцию `view` как параметр табличных функций [remote](https://clickhouse.tech/docs/ru/sql-reference/table-functions/remote/#remote-remotesecure) и [cluster](https://clickhouse.tech/docs/ru/sql-reference/table-functions/cluster/#cluster-clusterallreplicas):
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM remote(`127.0.0.1`, view(SELECT a, b, c FROM table_name))
|
||||||
|
```
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
SELECT * FROM cluster(`cluster_name`, view(SELECT a, b, c FROM table_name))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Смотрите также**
|
||||||
|
|
||||||
|
- [view](https://clickhouse.tech/docs/ru/engines/table-engines/special/view/#table_engines-view)
|
||||||
|
[Оригинальная статья](https://clickhouse.tech/docs/ru/query_language/table_functions/view/) <!--hide-->
|
@ -14,7 +14,7 @@ Jinja2==2.11.2
|
|||||||
jinja2-highlight==0.6.1
|
jinja2-highlight==0.6.1
|
||||||
jsmin==2.2.2
|
jsmin==2.2.2
|
||||||
livereload==2.6.2
|
livereload==2.6.2
|
||||||
Markdown==3.2.1
|
Markdown==3.3.2
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
mkdocs==1.1.2
|
mkdocs==1.1.2
|
||||||
mkdocs-htmlproofer-plugin==0.0.3
|
mkdocs-htmlproofer-plugin==0.0.3
|
||||||
|
@ -548,11 +548,27 @@ int mainEntryClickHouseInstall(int argc, char ** argv)
|
|||||||
users_config_file.string(), users_d.string());
|
users_config_file.string(), users_d.string());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set capabilities for the binary.
|
/** Set capabilities for the binary.
|
||||||
|
*
|
||||||
|
* 1. Check that "setcap" tool exists.
|
||||||
|
* 2. Check that an arbitrary program with installed capabilities can run.
|
||||||
|
* 3. Set the capabilities.
|
||||||
|
*
|
||||||
|
* The second is important for Docker and systemd-nspawn.
|
||||||
|
* When the container has no capabilities,
|
||||||
|
* but the executable file inside the container has capabilities,
|
||||||
|
* then attempt to run this file will end up with a cryptic "Operation not permitted" message.
|
||||||
|
*/
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
fmt::print("Setting capabilities for clickhouse binary. This is optional.\n");
|
fmt::print("Setting capabilities for clickhouse binary. This is optional.\n");
|
||||||
std::string command = fmt::format("command -v setcap && setcap 'cap_net_admin,cap_ipc_lock,cap_sys_nice+ep' {}", main_bin_path.string());
|
std::string command = fmt::format("command -v setcap >/dev/null"
|
||||||
|
" && echo > {0} && chmod a+x {0} && {0} && setcap 'cap_net_admin,cap_ipc_lock,cap_sys_nice+ep' {0} && {0} && rm {0}"
|
||||||
|
" && setcap 'cap_net_admin,cap_ipc_lock,cap_sys_nice+ep' {1}"
|
||||||
|
" || echo \"Cannot set 'net_admin' or 'ipc_lock' or 'sys_nice' capability for clickhouse binary."
|
||||||
|
" This is optional. Taskstats accounting will be disabled."
|
||||||
|
" To enable taskstats accounting you may add the required capability later manually.\"",
|
||||||
|
"/tmp/test_setcap.sh", main_bin_path.string());
|
||||||
fmt::print(" {}\n", command);
|
fmt::print(" {}\n", command);
|
||||||
executeScript(command);
|
executeScript(command);
|
||||||
#endif
|
#endif
|
||||||
|
@ -4,7 +4,7 @@ set(CLICKHOUSE_SERVER_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (OS_LINUX)
|
if (OS_LINUX)
|
||||||
set (LINK_CONFIG_LIB INTERFACE "-Wl,${WHOLE_ARCHIVE} $<TARGET_FILE:clickhouse_server_configs> -Wl,${NO_WHOLE_ARCHIVE}")
|
set (LINK_RESOURCE_LIB INTERFACE "-Wl,${WHOLE_ARCHIVE} $<TARGET_FILE:clickhouse_server_configs> -Wl,${NO_WHOLE_ARCHIVE}")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set (CLICKHOUSE_SERVER_LINK
|
set (CLICKHOUSE_SERVER_LINK
|
||||||
@ -20,7 +20,7 @@ set (CLICKHOUSE_SERVER_LINK
|
|||||||
clickhouse_table_functions
|
clickhouse_table_functions
|
||||||
string_utils
|
string_utils
|
||||||
|
|
||||||
${LINK_CONFIG_LIB}
|
${LINK_RESOURCE_LIB}
|
||||||
|
|
||||||
PUBLIC
|
PUBLIC
|
||||||
daemon
|
daemon
|
||||||
@ -37,20 +37,20 @@ if (OS_LINUX)
|
|||||||
# 1. Allow to run the binary without download of any other files.
|
# 1. Allow to run the binary without download of any other files.
|
||||||
# 2. Allow to implement "sudo clickhouse install" tool.
|
# 2. Allow to implement "sudo clickhouse install" tool.
|
||||||
|
|
||||||
foreach(CONFIG_FILE config users embedded)
|
foreach(RESOURCE_FILE config.xml users.xml embedded.xml play.html)
|
||||||
set(CONFIG_OBJ ${CONFIG_FILE}.o)
|
set(RESOURCE_OBJ ${RESOURCE_FILE}.o)
|
||||||
set(CONFIG_OBJS ${CONFIG_OBJS} ${CONFIG_OBJ})
|
set(RESOURCE_OBJS ${RESOURCE_OBJS} ${RESOURCE_OBJ})
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/14776463/compile-and-add-an-object-file-from-a-binary-with-cmake
|
# https://stackoverflow.com/questions/14776463/compile-and-add-an-object-file-from-a-binary-with-cmake
|
||||||
add_custom_command(OUTPUT ${CONFIG_OBJ}
|
add_custom_command(OUTPUT ${RESOURCE_OBJ}
|
||||||
COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${OBJCOPY_PATH} -I binary ${OBJCOPY_ARCH_OPTIONS} ${CONFIG_FILE}.xml ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ}
|
COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${OBJCOPY_PATH} -I binary ${OBJCOPY_ARCH_OPTIONS} ${RESOURCE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ}
|
||||||
COMMAND ${OBJCOPY_PATH} --rename-section .data=.rodata,alloc,load,readonly,data,contents
|
COMMAND ${OBJCOPY_PATH} --rename-section .data=.rodata,alloc,load,readonly,data,contents
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ} ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ})
|
${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ} ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ})
|
||||||
|
|
||||||
set_source_files_properties(${CONFIG_OBJ} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
|
set_source_files_properties(${RESOURCE_OBJ} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
|
||||||
endforeach(CONFIG_FILE)
|
endforeach(RESOURCE_FILE)
|
||||||
|
|
||||||
add_library(clickhouse_server_configs STATIC ${CONFIG_OBJS})
|
add_library(clickhouse_server_configs STATIC ${RESOURCE_OBJS})
|
||||||
set_target_properties(clickhouse_server_configs PROPERTIES LINKER_LANGUAGE C)
|
set_target_properties(clickhouse_server_configs PROPERTIES LINKER_LANGUAGE C)
|
||||||
|
|
||||||
# whole-archive prevents symbols from being discarded for unknown reason
|
# whole-archive prevents symbols from being discarded for unknown reason
|
||||||
|
@ -212,22 +212,10 @@
|
|||||||
<!-- Directory with user provided files that are accessible by 'file' table function. -->
|
<!-- Directory with user provided files that are accessible by 'file' table function. -->
|
||||||
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
|
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
|
||||||
|
|
||||||
<!-- Sources to read users, roles, access rights, profiles of settings, quotas. -->
|
<!-- LDAP server definitions. -->
|
||||||
<user_directories>
|
|
||||||
<users_xml>
|
|
||||||
<!-- Path to configuration file with predefined users. -->
|
|
||||||
<path>users.xml</path>
|
|
||||||
</users_xml>
|
|
||||||
<local_directory>
|
|
||||||
<!-- Path to folder where users created by SQL commands are stored. -->
|
|
||||||
<path>/var/lib/clickhouse/access/</path>
|
|
||||||
</local_directory>
|
|
||||||
</user_directories>
|
|
||||||
|
|
||||||
<!-- External user directories (LDAP). -->
|
|
||||||
<ldap_servers>
|
<ldap_servers>
|
||||||
<!-- List LDAP servers with their connection parameters here to later use them as authenticators for dedicated users,
|
<!-- List LDAP servers with their connection parameters here to later 1) use them as authenticators for dedicated local users,
|
||||||
who have 'ldap' authentication mechanism specified instead of 'password'.
|
who have 'ldap' authentication mechanism specified instead of 'password', or to 2) use them as remote user directories.
|
||||||
Parameters:
|
Parameters:
|
||||||
host - LDAP server hostname or IP, this parameter is mandatory and cannot be empty.
|
host - LDAP server hostname or IP, this parameter is mandatory and cannot be empty.
|
||||||
port - LDAP server port, default is 636 if enable_tls is set to true, 389 otherwise.
|
port - LDAP server port, default is 636 if enable_tls is set to true, 389 otherwise.
|
||||||
@ -246,7 +234,7 @@
|
|||||||
tls_key_file - path to certificate key file.
|
tls_key_file - path to certificate key file.
|
||||||
tls_ca_cert_file - path to CA certificate file.
|
tls_ca_cert_file - path to CA certificate file.
|
||||||
tls_ca_cert_dir - path to the directory containing CA certificates.
|
tls_ca_cert_dir - path to the directory containing CA certificates.
|
||||||
tls_cipher_suite - allowed cipher suite.
|
tls_cipher_suite - allowed cipher suite (in OpenSSL notation).
|
||||||
Example:
|
Example:
|
||||||
<my_ldap_server>
|
<my_ldap_server>
|
||||||
<host>localhost</host>
|
<host>localhost</host>
|
||||||
@ -265,6 +253,36 @@
|
|||||||
-->
|
-->
|
||||||
</ldap_servers>
|
</ldap_servers>
|
||||||
|
|
||||||
|
<!-- Sources to read users, roles, access rights, profiles of settings, quotas. -->
|
||||||
|
<user_directories>
|
||||||
|
<users_xml>
|
||||||
|
<!-- Path to configuration file with predefined users. -->
|
||||||
|
<path>users.xml</path>
|
||||||
|
</users_xml>
|
||||||
|
<local_directory>
|
||||||
|
<!-- Path to folder where users created by SQL commands are stored. -->
|
||||||
|
<path>/var/lib/clickhouse/access/</path>
|
||||||
|
</local_directory>
|
||||||
|
|
||||||
|
<!-- To add an LDAP server as a remote user directory of users that are not defined locally, define a single 'ldap' section
|
||||||
|
with the following parameters:
|
||||||
|
server - one of LDAP server names defined in 'ldap_servers' config section above.
|
||||||
|
This parameter is mandatory and cannot be empty.
|
||||||
|
roles - section with a list of locally defined roles that will be assigned to each user retrieved from the LDAP server.
|
||||||
|
If no roles are specified, user will not be able to perform any actions after authentication.
|
||||||
|
If any of the listed roles is not defined locally at the time of authentication, the authenthication attept
|
||||||
|
will fail as if the provided password was incorrect.
|
||||||
|
Example:
|
||||||
|
<ldap>
|
||||||
|
<server>my_ldap_server</server>
|
||||||
|
<roles>
|
||||||
|
<my_local_role1 />
|
||||||
|
<my_local_role2 />
|
||||||
|
</roles>
|
||||||
|
</ldap>
|
||||||
|
-->
|
||||||
|
</user_directories>
|
||||||
|
|
||||||
<!-- Default profile of settings. -->
|
<!-- Default profile of settings. -->
|
||||||
<default_profile>default</default_profile>
|
<default_profile>default</default_profile>
|
||||||
|
|
||||||
@ -704,18 +722,22 @@
|
|||||||
-->
|
-->
|
||||||
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
|
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
|
||||||
|
|
||||||
<!-- Uncomment to use query masking rules.
|
<!-- Default query masking rules, matching lines would be replaced with something else in the logs
|
||||||
|
(both text logs and system.query_log).
|
||||||
name - name for the rule (optional)
|
name - name for the rule (optional)
|
||||||
regexp - RE2 compatible regular expression (mandatory)
|
regexp - RE2 compatible regular expression (mandatory)
|
||||||
replace - substitution string for sensitive data (optional, by default - six asterisks)
|
replace - substitution string for sensitive data (optional, by default - six asterisks)
|
||||||
|
-->
|
||||||
<query_masking_rules>
|
<query_masking_rules>
|
||||||
<rule>
|
<rule>
|
||||||
<name>hide SSN</name>
|
<name>hide encrypt/decrypt arguments</name>
|
||||||
<regexp>\b\d{3}-\d{2}-\d{4}\b</regexp>
|
<regexp>((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\)</regexp>
|
||||||
<replace>000-00-0000</replace>
|
<!-- or more secure, but also more invasive:
|
||||||
|
(aes_\w+)\s*\(.*\)
|
||||||
|
-->
|
||||||
|
<replace>\1(???)</replace>
|
||||||
</rule>
|
</rule>
|
||||||
</query_masking_rules>
|
</query_masking_rules>
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Uncomment to use custom http handlers.
|
<!-- Uncomment to use custom http handlers.
|
||||||
rules are checked from top to bottom, first match runs the handler
|
rules are checked from top to bottom, first match runs the handler
|
||||||
|
448
programs/server/play.html
Normal file
448
programs/server/play.html
Normal file
@ -0,0 +1,448 @@
|
|||||||
|
<html> <!-- TODO If I write DOCTYPE HTML something changes but I don't know what. -->
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>ClickHouse Query</title>
|
||||||
|
|
||||||
|
<!-- Code style:
|
||||||
|
|
||||||
|
Do not use any JavaScript or CSS frameworks or preprocessors.
|
||||||
|
This HTML page should not require any build systems (node.js, npm, gulp, etc.)
|
||||||
|
This HTML page should not be minified, instead it should be reasonably minimalistic by itself.
|
||||||
|
This HTML page should not load any external resources
|
||||||
|
(CSS and JavaScript must be embedded directly to the page. No external fonts or images should be loaded).
|
||||||
|
This UI should look as lightweight, clean and fast as possible.
|
||||||
|
All UI elements must be aligned in pixel-perfect way.
|
||||||
|
There should not be any animations.
|
||||||
|
No unexpected changes in positions of elements while the page is loading.
|
||||||
|
Navigation by keyboard should work.
|
||||||
|
64-bit numbers must display correctly.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
:root {
|
||||||
|
--background-color: #DDF8FF; /* Or #FFFBEF; actually many pastel colors look great for light theme. */
|
||||||
|
--element-background-color: #FFF;
|
||||||
|
--border-color: #EEE;
|
||||||
|
--shadow-color: rgba(0, 0, 0, 0.1);
|
||||||
|
--button-color: #FFAA00; /* Orange on light-cyan is especially good. */
|
||||||
|
--text-color: #000;
|
||||||
|
--button-active-color: #F00;
|
||||||
|
--button-active-text-color: #FFF;
|
||||||
|
--misc-text-color: #888;
|
||||||
|
--error-color: #FEE; /* Light-pink on light-cyan is so neat, I even want to trigger errors to see this cool combination of colors. */
|
||||||
|
--table-header-color: #F8F8F8;
|
||||||
|
--table-hover-color: #FFF8EF;
|
||||||
|
--null-color: #A88;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--background-color: #000;
|
||||||
|
--element-background-color: #102030;
|
||||||
|
--border-color: #111;
|
||||||
|
--shadow-color: rgba(255, 255, 255, 0.1);
|
||||||
|
--text-color: #CCC;
|
||||||
|
--button-color: #FFAA00;
|
||||||
|
--button-text-color: #000;
|
||||||
|
--button-active-color: #F00;
|
||||||
|
--button-active-text-color: #FFF;
|
||||||
|
--misc-text-color: #888;
|
||||||
|
--error-color: #400;
|
||||||
|
--table-header-color: #102020;
|
||||||
|
--table-hover-color: #003333;
|
||||||
|
--null-color: #A88;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body
|
||||||
|
{
|
||||||
|
/* Personal choice. */
|
||||||
|
font-family: Sans-Serif;
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise Webkit based browsers will display ugly border on focus. */
|
||||||
|
textarea, input, button
|
||||||
|
{
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise scrollbar may appear dynamically and it will alter viewport height,
|
||||||
|
then relative heights of elements will change suddenly, and it will break overall impression. */
|
||||||
|
/* html
|
||||||
|
{
|
||||||
|
overflow-x: scroll;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
div
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monospace
|
||||||
|
{
|
||||||
|
/* Prefer fonts that have full hinting info. This is important for non-retina displays.
|
||||||
|
Also I personally dislike "Ubuntu" font due to the similarity of 'r' and 'г' (it looks very ignorant).
|
||||||
|
*/
|
||||||
|
font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, Monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow
|
||||||
|
{
|
||||||
|
box-shadow: 0 0 1rem var(--shadow-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea
|
||||||
|
{
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
/* The font must be not too small (to be inclusive) and not too large (it's less practical and make general feel of insecurity) */
|
||||||
|
font-size: 11pt;
|
||||||
|
padding: 0.25rem;
|
||||||
|
background-color: var(--element-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#query
|
||||||
|
{
|
||||||
|
/* Make enough space for even huge queries. */
|
||||||
|
height: 20%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputs
|
||||||
|
{
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#url
|
||||||
|
{
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#user
|
||||||
|
{
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#password
|
||||||
|
{
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#run_div
|
||||||
|
{
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#run
|
||||||
|
{
|
||||||
|
color: var(--button-text-color);
|
||||||
|
background-color: var(--button-color);
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 100%; /* Otherwise button element will have lower font size. */
|
||||||
|
}
|
||||||
|
|
||||||
|
#run:hover, #run:focus
|
||||||
|
{
|
||||||
|
color: var(--button-active-text-color);
|
||||||
|
background-color: var(--button-active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#stats
|
||||||
|
{
|
||||||
|
float: right;
|
||||||
|
color: var(--misc-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#toggle-light, #toggle-dark
|
||||||
|
{
|
||||||
|
float: right;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint
|
||||||
|
{
|
||||||
|
color: var(--misc-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#data_div
|
||||||
|
{
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#data-table
|
||||||
|
{
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
/* I need pixel-perfect alignment but not sure the following is correct, please help */
|
||||||
|
min-width: calc(100vw - 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Will be displayed when user specified custom format. */
|
||||||
|
#data-unparsed
|
||||||
|
{
|
||||||
|
background-color: var(--element-background-color);
|
||||||
|
margin-top: 0rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td
|
||||||
|
{
|
||||||
|
background-color: var(--element-background-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
/* For wide tables any individual column will be no more than 50% of page width. */
|
||||||
|
max-width: 50vw;
|
||||||
|
/* The content is cut unless you hover. */
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.right
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
th
|
||||||
|
{
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
text-align: middle;
|
||||||
|
background-color: var(--table-header-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The row under mouse pointer is highlight for better legibility. */
|
||||||
|
tr:hover, tr:hover td
|
||||||
|
{
|
||||||
|
background-color: var(--table-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover
|
||||||
|
{
|
||||||
|
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#error
|
||||||
|
{
|
||||||
|
background: var(--error-color);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When mouse pointer is over table cell, will display full text (with wrap) instead of cut.
|
||||||
|
TODO Find a way to make it work on touch devices. */
|
||||||
|
td.left:hover
|
||||||
|
{
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The style for SQL NULL */
|
||||||
|
.null
|
||||||
|
{
|
||||||
|
color: var(--null-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="inputs">
|
||||||
|
<input class="monospace shadow" id="url" type="text" value="http://localhost:8123/" /><input class="monospace shadow" id="user" type="text" value="default" /><input class="monospace shadow" id="password" type="password" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea autofocus spellcheck="false" class="monospace shadow" id="query"></textarea>
|
||||||
|
</div>
|
||||||
|
<div id="run_div">
|
||||||
|
<button class="shadow" id="run">Run</button>
|
||||||
|
<span class="hint"> (Ctrl+Enter)</span>
|
||||||
|
<span id="stats"></span>
|
||||||
|
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
|
||||||
|
</div>
|
||||||
|
<div id="data_div">
|
||||||
|
<table class="monospace shadow" id="data-table"></table>
|
||||||
|
<pre class="monospace shadow" id="data-unparsed"></pre>
|
||||||
|
</div>
|
||||||
|
<p id="error" class="monospace shadow">
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
/// Substitute the address of the server where the page is served.
|
||||||
|
if (location.protocol != 'file:') {
|
||||||
|
document.getElementById('url').value = location.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function post()
|
||||||
|
{
|
||||||
|
/// TODO: Avoid race condition on subsequent requests when responses may come out of order.
|
||||||
|
/// TODO: Check if URL already contains query string (append parameters).
|
||||||
|
|
||||||
|
var url = document.getElementById('url').value +
|
||||||
|
/// Ask server to allow cross-domain requests.
|
||||||
|
'?add_http_cors_header=1' +
|
||||||
|
'&user=' + encodeURIComponent(document.getElementById('user').value) +
|
||||||
|
'&password=' + encodeURIComponent(document.getElementById('password').value) +
|
||||||
|
'&default_format=JSONCompact' +
|
||||||
|
/// Safety settings to prevent results that browser cannot display.
|
||||||
|
'&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break';
|
||||||
|
|
||||||
|
var query = document.getElementById('query').value;
|
||||||
|
var xhr = new XMLHttpRequest;
|
||||||
|
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.send(query);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function()
|
||||||
|
{
|
||||||
|
if (this.readyState === XMLHttpRequest.DONE) {
|
||||||
|
if (this.status === 200) {
|
||||||
|
var json;
|
||||||
|
try { json = JSON.parse(this.response); } catch (e) {}
|
||||||
|
if (json !== undefined && json.statistics !== undefined) {
|
||||||
|
renderResult(json);
|
||||||
|
} else {
|
||||||
|
renderUnparsedResult(this.response);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/// TODO: Proper rendering of network errors.
|
||||||
|
renderError(this.response);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//console.log(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('run').onclick = function()
|
||||||
|
{
|
||||||
|
post();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('query').onkeypress = function(event)
|
||||||
|
{
|
||||||
|
/// Firefox has code 13 for Enter and Chromium has code 10.
|
||||||
|
if (event.ctrlKey && (event.charCode == 13 || event.charCode == 10)) {
|
||||||
|
post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear()
|
||||||
|
{
|
||||||
|
var table = document.getElementById('data-table');
|
||||||
|
while (table.firstChild) {
|
||||||
|
table.removeChild(table.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('data-unparsed').innerText = '';
|
||||||
|
document.getElementById('data-unparsed').style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById('error').innerText = '';
|
||||||
|
document.getElementById('error').style.display = 'none';
|
||||||
|
|
||||||
|
document.getElementById('stats').innerText = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResult(response)
|
||||||
|
{
|
||||||
|
//console.log(response);
|
||||||
|
clear();
|
||||||
|
|
||||||
|
var stats = document.getElementById('stats');
|
||||||
|
stats.innerText = 'Elapsed: ' + response.statistics.elapsed.toFixed(3) + " sec, read " + response.statistics.rows_read + " rows.";
|
||||||
|
|
||||||
|
var thead = document.createElement('thead');
|
||||||
|
for (var idx in response.meta) {
|
||||||
|
var th = document.createElement('th');
|
||||||
|
var name = document.createTextNode(response.meta[idx].name);
|
||||||
|
th.appendChild(name);
|
||||||
|
thead.appendChild(th);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To prevent hanging the browser, limit the number of cells in a table.
|
||||||
|
/// It's important to have the limit on number of cells, not just rows, because tables may be wide or narrow.
|
||||||
|
var max_rows = 10000 / response.meta.length;
|
||||||
|
var row_num = 0;
|
||||||
|
|
||||||
|
var tbody = document.createElement('tbody');
|
||||||
|
for (var row_idx in response.data) {
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
for (var col_idx in response.data[row_idx]) {
|
||||||
|
var td = document.createElement('td');
|
||||||
|
var cell = response.data[row_idx][col_idx];
|
||||||
|
var is_null = (cell === null);
|
||||||
|
var content = document.createTextNode(is_null ? 'ᴺᵁᴸᴸ' : cell);
|
||||||
|
td.appendChild(content);
|
||||||
|
/// TODO: Execute regexp only once for each column.
|
||||||
|
td.className = response.meta[col_idx].type.match(/^(U?Int|Decimal|Float)/) ? 'right' : 'left';
|
||||||
|
if (is_null) {
|
||||||
|
td.className += ' null';
|
||||||
|
}
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
|
||||||
|
++row_num;
|
||||||
|
if (row_num >= max_rows) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = document.getElementById('data-table');
|
||||||
|
table.appendChild(thead);
|
||||||
|
table.appendChild(tbody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function to render raw data when non-default format is specified.
|
||||||
|
function renderUnparsedResult(response)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
var data = document.getElementById('data-unparsed')
|
||||||
|
|
||||||
|
if (response === '') {
|
||||||
|
/// TODO: Fade or remove previous result when new request will be performed.
|
||||||
|
response = 'Ok.';
|
||||||
|
}
|
||||||
|
|
||||||
|
data.innerText = response;
|
||||||
|
/// inline-block make width adjust to the size of content.
|
||||||
|
data.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderError(response)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
document.getElementById('error').innerText = response;
|
||||||
|
document.getElementById('error').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setColorTheme(theme)
|
||||||
|
{
|
||||||
|
window.localStorage.setItem('theme', theme);
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The choice of color theme is saved in browser.
|
||||||
|
var theme = window.localStorage.getItem('theme');
|
||||||
|
if (theme) {
|
||||||
|
setColorTheme(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('toggle-light').onclick = function()
|
||||||
|
{
|
||||||
|
setColorTheme('light');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('toggle-dark').onclick = function()
|
||||||
|
{
|
||||||
|
setColorTheme('dark');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</html>
|
@ -3,6 +3,7 @@
|
|||||||
#include <Access/MemoryAccessStorage.h>
|
#include <Access/MemoryAccessStorage.h>
|
||||||
#include <Access/UsersConfigAccessStorage.h>
|
#include <Access/UsersConfigAccessStorage.h>
|
||||||
#include <Access/DiskAccessStorage.h>
|
#include <Access/DiskAccessStorage.h>
|
||||||
|
#include <Access/LDAPAccessStorage.h>
|
||||||
#include <Access/ContextAccess.h>
|
#include <Access/ContextAccess.h>
|
||||||
#include <Access/RoleCache.h>
|
#include <Access/RoleCache.h>
|
||||||
#include <Access/RowPolicyCache.h>
|
#include <Access/RowPolicyCache.h>
|
||||||
@ -253,6 +254,12 @@ void AccessControlManager::addMemoryStorage(const String & storage_name_)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AccessControlManager::addLDAPStorage(const String & storage_name_, const Poco::Util::AbstractConfiguration & config_, const String & prefix_)
|
||||||
|
{
|
||||||
|
addStorage(std::make_shared<LDAPAccessStorage>(storage_name_, this, config_, prefix_));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AccessControlManager::addStoragesFromUserDirectoriesConfig(
|
void AccessControlManager::addStoragesFromUserDirectoriesConfig(
|
||||||
const Poco::Util::AbstractConfiguration & config,
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
const String & key,
|
const String & key,
|
||||||
@ -275,6 +282,8 @@ void AccessControlManager::addStoragesFromUserDirectoriesConfig(
|
|||||||
type = UsersConfigAccessStorage::STORAGE_TYPE;
|
type = UsersConfigAccessStorage::STORAGE_TYPE;
|
||||||
else if ((type == "local") || (type == "local_directory"))
|
else if ((type == "local") || (type == "local_directory"))
|
||||||
type = DiskAccessStorage::STORAGE_TYPE;
|
type = DiskAccessStorage::STORAGE_TYPE;
|
||||||
|
else if (type == "ldap")
|
||||||
|
type = LDAPAccessStorage::STORAGE_TYPE;
|
||||||
|
|
||||||
String name = config.getString(prefix + ".name", type);
|
String name = config.getString(prefix + ".name", type);
|
||||||
|
|
||||||
@ -295,6 +304,10 @@ void AccessControlManager::addStoragesFromUserDirectoriesConfig(
|
|||||||
bool readonly = config.getBool(prefix + ".readonly", false);
|
bool readonly = config.getBool(prefix + ".readonly", false);
|
||||||
addDiskStorage(name, path, readonly);
|
addDiskStorage(name, path, readonly);
|
||||||
}
|
}
|
||||||
|
else if (type == LDAPAccessStorage::STORAGE_TYPE)
|
||||||
|
{
|
||||||
|
addLDAPStorage(name, config, prefix);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
throw Exception("Unknown storage type '" + type + "' at " + prefix + " in config", ErrorCodes::UNKNOWN_ELEMENT_IN_CONFIG);
|
throw Exception("Unknown storage type '" + type + "' at " + prefix + " in config", ErrorCodes::UNKNOWN_ELEMENT_IN_CONFIG);
|
||||||
}
|
}
|
||||||
@ -346,7 +359,7 @@ UUID AccessControlManager::login(const String & user_name, const String & passwo
|
|||||||
|
|
||||||
void AccessControlManager::setExternalAuthenticatorsConfig(const Poco::Util::AbstractConfiguration & config)
|
void AccessControlManager::setExternalAuthenticatorsConfig(const Poco::Util::AbstractConfiguration & config)
|
||||||
{
|
{
|
||||||
external_authenticators->setConfig(config, getLogger());
|
external_authenticators->setConfiguration(config, getLogger());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +82,9 @@ public:
|
|||||||
void addMemoryStorage();
|
void addMemoryStorage();
|
||||||
void addMemoryStorage(const String & storage_name_);
|
void addMemoryStorage(const String & storage_name_);
|
||||||
|
|
||||||
|
/// Adds LDAPAccessStorage which allows querying remote LDAP server for user info.
|
||||||
|
void addLDAPStorage(const String & storage_name_, const Poco::Util::AbstractConfiguration & config_, const String & prefix_);
|
||||||
|
|
||||||
/// Adds storages from <users_directories> config.
|
/// Adds storages from <users_directories> config.
|
||||||
void addStoragesFromUserDirectoriesConfig(const Poco::Util::AbstractConfiguration & config,
|
void addStoragesFromUserDirectoriesConfig(const Poco::Util::AbstractConfiguration & config,
|
||||||
const String & key,
|
const String & key,
|
||||||
|
@ -156,7 +156,7 @@ void ExternalAuthenticators::reset()
|
|||||||
ldap_server_params.clear();
|
ldap_server_params.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExternalAuthenticators::setConfig(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
|
void ExternalAuthenticators::setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
reset();
|
reset();
|
||||||
|
@ -26,7 +26,7 @@ class ExternalAuthenticators
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void reset();
|
void reset();
|
||||||
void setConfig(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log);
|
void setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log);
|
||||||
|
|
||||||
void setLDAPServerParams(const String & server, const LDAPServerParams & params);
|
void setLDAPServerParams(const String & server, const LDAPServerParams & params);
|
||||||
LDAPServerParams getLDAPServerParams(const String & server) const;
|
LDAPServerParams getLDAPServerParams(const String & server) const;
|
||||||
|
@ -14,6 +14,8 @@ namespace ErrorCodes
|
|||||||
extern const int ACCESS_ENTITY_ALREADY_EXISTS;
|
extern const int ACCESS_ENTITY_ALREADY_EXISTS;
|
||||||
extern const int ACCESS_ENTITY_NOT_FOUND;
|
extern const int ACCESS_ENTITY_NOT_FOUND;
|
||||||
extern const int ACCESS_STORAGE_READONLY;
|
extern const int ACCESS_STORAGE_READONLY;
|
||||||
|
extern const int WRONG_PASSWORD;
|
||||||
|
extern const int IP_ADDRESS_NOT_ALLOWED;
|
||||||
extern const int AUTHENTICATION_FAILED;
|
extern const int AUTHENTICATION_FAILED;
|
||||||
extern const int LOGICAL_ERROR;
|
extern const int LOGICAL_ERROR;
|
||||||
}
|
}
|
||||||
@ -418,9 +420,21 @@ UUID IAccessStorage::login(
|
|||||||
const String & user_name,
|
const String & user_name,
|
||||||
const String & password,
|
const String & password,
|
||||||
const Poco::Net::IPAddress & address,
|
const Poco::Net::IPAddress & address,
|
||||||
const ExternalAuthenticators & external_authenticators) const
|
const ExternalAuthenticators & external_authenticators,
|
||||||
|
bool replace_exception_with_cannot_authenticate) const
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
return loginImpl(user_name, password, address, external_authenticators);
|
return loginImpl(user_name, password, address, external_authenticators);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
if (!replace_exception_with_cannot_authenticate)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
tryLogCurrentException(getLogger(), user_name + ": Authentication failed");
|
||||||
|
throwCannotAuthenticate(user_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -434,11 +448,16 @@ UUID IAccessStorage::loginImpl(
|
|||||||
{
|
{
|
||||||
if (auto user = tryRead<User>(*id))
|
if (auto user = tryRead<User>(*id))
|
||||||
{
|
{
|
||||||
if (isPasswordCorrectImpl(*user, password, external_authenticators) && isAddressAllowedImpl(*user, address))
|
if (!isPasswordCorrectImpl(*user, password, external_authenticators))
|
||||||
|
throwInvalidPassword();
|
||||||
|
|
||||||
|
if (!isAddressAllowedImpl(*user, address))
|
||||||
|
throwAddressNotAllowed(address);
|
||||||
|
|
||||||
return *id;
|
return *id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throwCannotAuthenticate(user_name);
|
throwNotFound(EntityType::USER, user_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -554,6 +573,15 @@ void IAccessStorage::throwReadonlyCannotRemove(EntityType type, const String & n
|
|||||||
ErrorCodes::ACCESS_STORAGE_READONLY);
|
ErrorCodes::ACCESS_STORAGE_READONLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IAccessStorage::throwAddressNotAllowed(const Poco::Net::IPAddress & address)
|
||||||
|
{
|
||||||
|
throw Exception("Connections from " + address.toString() + " are not allowed", ErrorCodes::IP_ADDRESS_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAccessStorage::throwInvalidPassword()
|
||||||
|
{
|
||||||
|
throw Exception("Invalid password", ErrorCodes::WRONG_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
void IAccessStorage::throwCannotAuthenticate(const String & user_name)
|
void IAccessStorage::throwCannotAuthenticate(const String & user_name)
|
||||||
{
|
{
|
||||||
|
@ -144,7 +144,7 @@ public:
|
|||||||
|
|
||||||
/// Finds an user, check its password and returns the ID of the user.
|
/// Finds an user, check its password and returns the ID of the user.
|
||||||
/// Throws an exception if no such user or password is incorrect.
|
/// Throws an exception if no such user or password is incorrect.
|
||||||
UUID login(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators) const;
|
UUID login(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators, bool replace_exception_with_cannot_authenticate = true) const;
|
||||||
|
|
||||||
/// Returns the ID of an user who has logged in (maybe on another node).
|
/// Returns the ID of an user who has logged in (maybe on another node).
|
||||||
/// The function assumes that the password has been already checked somehow, so we can skip checking it now.
|
/// The function assumes that the password has been already checked somehow, so we can skip checking it now.
|
||||||
@ -182,6 +182,8 @@ protected:
|
|||||||
[[noreturn]] void throwReadonlyCannotInsert(EntityType type, const String & name) const;
|
[[noreturn]] void throwReadonlyCannotInsert(EntityType type, const String & name) const;
|
||||||
[[noreturn]] void throwReadonlyCannotUpdate(EntityType type, const String & name) const;
|
[[noreturn]] void throwReadonlyCannotUpdate(EntityType type, const String & name) const;
|
||||||
[[noreturn]] void throwReadonlyCannotRemove(EntityType type, const String & name) const;
|
[[noreturn]] void throwReadonlyCannotRemove(EntityType type, const String & name) const;
|
||||||
|
[[noreturn]] static void throwAddressNotAllowed(const Poco::Net::IPAddress & address);
|
||||||
|
[[noreturn]] static void throwInvalidPassword();
|
||||||
[[noreturn]] static void throwCannotAuthenticate(const String & user_name);
|
[[noreturn]] static void throwCannotAuthenticate(const String & user_name);
|
||||||
|
|
||||||
using Notification = std::tuple<OnChangedHandler, UUID, AccessEntityPtr>;
|
using Notification = std::tuple<OnChangedHandler, UUID, AccessEntityPtr>;
|
||||||
|
313
src/Access/LDAPAccessStorage.cpp
Normal file
313
src/Access/LDAPAccessStorage.cpp
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
#include <Access/LDAPAccessStorage.h>
|
||||||
|
#include <Access/AccessControlManager.h>
|
||||||
|
#include <Access/User.h>
|
||||||
|
#include <Access/Role.h>
|
||||||
|
#include <Common/Exception.h>
|
||||||
|
#include <common/logger_useful.h>
|
||||||
|
#include <ext/scope_guard.h>
|
||||||
|
#include <Poco/Util/AbstractConfiguration.h>
|
||||||
|
#include <Poco/JSON/JSON.h>
|
||||||
|
#include <Poco/JSON/Object.h>
|
||||||
|
#include <Poco/JSON/Stringifier.h>
|
||||||
|
#include <boost/range/algorithm/copy.hpp>
|
||||||
|
#include <iterator>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
namespace ErrorCodes
|
||||||
|
{
|
||||||
|
extern const int BAD_ARGUMENTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LDAPAccessStorage::LDAPAccessStorage(const String & storage_name_, AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix)
|
||||||
|
: IAccessStorage(storage_name_)
|
||||||
|
{
|
||||||
|
setConfiguration(access_control_manager_, config, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LDAPAccessStorage::setConfiguration(AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
|
||||||
|
// TODO: switch to passing config as a ConfigurationView and remove this extra prefix once a version of Poco with proper implementation is available.
|
||||||
|
const String prefix_str = (prefix.empty() ? "" : prefix + ".");
|
||||||
|
|
||||||
|
const bool has_server = config.has(prefix_str + "server");
|
||||||
|
const bool has_roles = config.has(prefix_str + "roles");
|
||||||
|
|
||||||
|
if (!has_server)
|
||||||
|
throw Exception("Missing 'server' field for LDAP user directory.", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
const auto ldap_server_cfg = config.getString(prefix_str + "server");
|
||||||
|
if (ldap_server_cfg.empty())
|
||||||
|
throw Exception("Empty 'server' field for LDAP user directory.", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
std::set<String> roles_cfg;
|
||||||
|
if (has_roles)
|
||||||
|
{
|
||||||
|
Poco::Util::AbstractConfiguration::Keys role_names;
|
||||||
|
config.keys(prefix_str + "roles", role_names);
|
||||||
|
|
||||||
|
// Currently, we only extract names of roles from the section names and assign them directly and unconditionally.
|
||||||
|
roles_cfg.insert(role_names.begin(), role_names.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
access_control_manager = access_control_manager_;
|
||||||
|
ldap_server = ldap_server_cfg;
|
||||||
|
default_role_names.swap(roles_cfg);
|
||||||
|
roles_of_interest.clear();
|
||||||
|
role_change_subscription = access_control_manager->subscribeForChanges<Role>(
|
||||||
|
[this] (const UUID & id, const AccessEntityPtr & entity)
|
||||||
|
{
|
||||||
|
return this->processRoleChange(id, entity);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Update `roles_of_interests` with initial values.
|
||||||
|
for (const auto & role_name : default_role_names)
|
||||||
|
{
|
||||||
|
if (auto role_id = access_control_manager->find<Role>(role_name))
|
||||||
|
roles_of_interest.emplace(*role_id, role_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LDAPAccessStorage::processRoleChange(const UUID & id, const AccessEntityPtr & entity)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
|
||||||
|
/// Update `roles_of_interests`.
|
||||||
|
auto role = typeid_cast<std::shared_ptr<const Role>>(entity);
|
||||||
|
bool need_to_update_users = false;
|
||||||
|
|
||||||
|
if (role && default_role_names.count(role->getName()))
|
||||||
|
{
|
||||||
|
/// If a role was created with one of the `default_role_names` or renamed to one of the `default_role_names`,
|
||||||
|
/// then set `need_to_update_users`.
|
||||||
|
need_to_update_users = roles_of_interest.insert_or_assign(id, role->getName()).second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/// If a role was removed or renamed to a name which isn't contained in the `default_role_names`,
|
||||||
|
/// then set `need_to_update_users`.
|
||||||
|
need_to_update_users = roles_of_interest.erase(id) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update users which have been created.
|
||||||
|
if (need_to_update_users)
|
||||||
|
{
|
||||||
|
auto update_func = [this] (const AccessEntityPtr & entity_) -> AccessEntityPtr
|
||||||
|
{
|
||||||
|
if (auto user = typeid_cast<std::shared_ptr<const User>>(entity_))
|
||||||
|
{
|
||||||
|
auto changed_user = typeid_cast<std::shared_ptr<User>>(user->clone());
|
||||||
|
auto & granted_roles = changed_user->granted_roles.roles;
|
||||||
|
granted_roles.clear();
|
||||||
|
boost::range::copy(roles_of_interest | boost::adaptors::map_keys, std::inserter(granted_roles, granted_roles.end()));
|
||||||
|
return changed_user;
|
||||||
|
}
|
||||||
|
return entity_;
|
||||||
|
};
|
||||||
|
memory_storage.update(memory_storage.findAll<User>(), update_func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LDAPAccessStorage::checkAllDefaultRoleNamesFoundNoLock() const
|
||||||
|
{
|
||||||
|
boost::container::flat_set<std::string_view> role_names_of_interest;
|
||||||
|
boost::range::copy(roles_of_interest | boost::adaptors::map_values, std::inserter(role_names_of_interest, role_names_of_interest.end()));
|
||||||
|
|
||||||
|
for (const auto & role_name : default_role_names)
|
||||||
|
{
|
||||||
|
if (!role_names_of_interest.count(role_name))
|
||||||
|
throwDefaultRoleNotFound(role_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char * LDAPAccessStorage::getStorageType() const
|
||||||
|
{
|
||||||
|
return STORAGE_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String LDAPAccessStorage::getStorageParamsJSON() const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
Poco::JSON::Object params_json;
|
||||||
|
|
||||||
|
params_json.set("server", ldap_server);
|
||||||
|
params_json.set("roles", default_role_names);
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
Poco::JSON::Stringifier::stringify(params_json, oss);
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<UUID> LDAPAccessStorage::findImpl(EntityType type, const String & name) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.find(type, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<UUID> LDAPAccessStorage::findAllImpl(EntityType type) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.findAll(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool LDAPAccessStorage::existsImpl(const UUID & id) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.exists(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AccessEntityPtr LDAPAccessStorage::readImpl(const UUID & id) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.read(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String LDAPAccessStorage::readNameImpl(const UUID & id) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.readName(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool LDAPAccessStorage::canInsertImpl(const AccessEntityPtr &) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UUID LDAPAccessStorage::insertImpl(const AccessEntityPtr & entity, bool)
|
||||||
|
{
|
||||||
|
throwReadonlyCannotInsert(entity->getType(), entity->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LDAPAccessStorage::removeImpl(const UUID & id)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
auto entity = read(id);
|
||||||
|
throwReadonlyCannotRemove(entity->getType(), entity->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LDAPAccessStorage::updateImpl(const UUID & id, const UpdateFunc &)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
auto entity = read(id);
|
||||||
|
throwReadonlyCannotUpdate(entity->getType(), entity->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ext::scope_guard LDAPAccessStorage::subscribeForChangesImpl(const UUID & id, const OnChangedHandler & handler) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.subscribeForChanges(id, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ext::scope_guard LDAPAccessStorage::subscribeForChangesImpl(EntityType type, const OnChangedHandler & handler) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.subscribeForChanges(type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool LDAPAccessStorage::hasSubscriptionImpl(const UUID & id) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.hasSubscription(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool LDAPAccessStorage::hasSubscriptionImpl(EntityType type) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
return memory_storage.hasSubscription(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID LDAPAccessStorage::loginImpl(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
auto id = memory_storage.find<User>(user_name);
|
||||||
|
if (id)
|
||||||
|
{
|
||||||
|
auto user = memory_storage.read<User>(*id);
|
||||||
|
|
||||||
|
if (!isPasswordCorrectImpl(*user, password, external_authenticators))
|
||||||
|
throwInvalidPassword();
|
||||||
|
|
||||||
|
if (!isAddressAllowedImpl(*user, address))
|
||||||
|
throwAddressNotAllowed(address);
|
||||||
|
|
||||||
|
return *id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User does not exist, so we create one, and will add it if authentication is successful.
|
||||||
|
auto user = std::make_shared<User>();
|
||||||
|
user->setName(user_name);
|
||||||
|
user->authentication = Authentication(Authentication::Type::LDAP_SERVER);
|
||||||
|
user->authentication.setServerName(ldap_server);
|
||||||
|
|
||||||
|
if (!isPasswordCorrectImpl(*user, password, external_authenticators))
|
||||||
|
throwInvalidPassword();
|
||||||
|
|
||||||
|
if (!isAddressAllowedImpl(*user, address))
|
||||||
|
throwAddressNotAllowed(address);
|
||||||
|
|
||||||
|
checkAllDefaultRoleNamesFoundNoLock();
|
||||||
|
|
||||||
|
auto & granted_roles = user->granted_roles.roles;
|
||||||
|
boost::range::copy(roles_of_interest | boost::adaptors::map_keys, std::inserter(granted_roles, granted_roles.end()));
|
||||||
|
|
||||||
|
return memory_storage.insert(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID LDAPAccessStorage::getIDOfLoggedUserImpl(const String & user_name) const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
auto id = memory_storage.find<User>(user_name);
|
||||||
|
if (id)
|
||||||
|
{
|
||||||
|
return *id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User does not exist, so we create one, and add it pretending that the authentication is successful.
|
||||||
|
auto user = std::make_shared<User>();
|
||||||
|
user->setName(user_name);
|
||||||
|
user->authentication = Authentication(Authentication::Type::LDAP_SERVER);
|
||||||
|
user->authentication.setServerName(ldap_server);
|
||||||
|
|
||||||
|
checkAllDefaultRoleNamesFoundNoLock();
|
||||||
|
|
||||||
|
auto & granted_roles = user->granted_roles.roles;
|
||||||
|
boost::range::copy(roles_of_interest | boost::adaptors::map_keys, std::inserter(granted_roles, granted_roles.end()));
|
||||||
|
|
||||||
|
return memory_storage.insert(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LDAPAccessStorage::throwDefaultRoleNotFound(const String & role_name)
|
||||||
|
{
|
||||||
|
throw Exception("One of the default roles, the role '" + role_name + "', is not found", IAccessEntity::TypeInfo::get(IAccessEntity::Type::ROLE).not_found_error_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
71
src/Access/LDAPAccessStorage.h
Normal file
71
src/Access/LDAPAccessStorage.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Access/MemoryAccessStorage.h>
|
||||||
|
#include <Core/Types.h>
|
||||||
|
#include <ext/scope_guard.h>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Poco
|
||||||
|
{
|
||||||
|
namespace Util
|
||||||
|
{
|
||||||
|
class AbstractConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
class AccessControlManager;
|
||||||
|
|
||||||
|
/// Implementation of IAccessStorage which allows attaching users from a remote LDAP server.
|
||||||
|
/// Currently, any user name will be treated as a name of an existing remote user,
|
||||||
|
/// a user info entity will be created, with LDAP_SERVER authentication type.
|
||||||
|
class LDAPAccessStorage : public IAccessStorage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr char STORAGE_TYPE[] = "ldap";
|
||||||
|
|
||||||
|
explicit LDAPAccessStorage(const String & storage_name_, AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix);
|
||||||
|
virtual ~LDAPAccessStorage() override = default;
|
||||||
|
|
||||||
|
public: // IAccessStorage implementations.
|
||||||
|
virtual const char * getStorageType() const override;
|
||||||
|
virtual String getStorageParamsJSON() const override;
|
||||||
|
|
||||||
|
private: // IAccessStorage implementations.
|
||||||
|
virtual std::optional<UUID> findImpl(EntityType type, const String & name) const override;
|
||||||
|
virtual std::vector<UUID> findAllImpl(EntityType type) const override;
|
||||||
|
virtual bool existsImpl(const UUID & id) const override;
|
||||||
|
virtual AccessEntityPtr readImpl(const UUID & id) const override;
|
||||||
|
virtual String readNameImpl(const UUID & id) const override;
|
||||||
|
virtual bool canInsertImpl(const AccessEntityPtr &) const override;
|
||||||
|
virtual UUID insertImpl(const AccessEntityPtr & entity, bool replace_if_exists) override;
|
||||||
|
virtual void removeImpl(const UUID & id) override;
|
||||||
|
virtual void updateImpl(const UUID & id, const UpdateFunc & update_func) override;
|
||||||
|
virtual ext::scope_guard subscribeForChangesImpl(const UUID & id, const OnChangedHandler & handler) const override;
|
||||||
|
virtual ext::scope_guard subscribeForChangesImpl(EntityType type, const OnChangedHandler & handler) const override;
|
||||||
|
virtual bool hasSubscriptionImpl(const UUID & id) const override;
|
||||||
|
virtual bool hasSubscriptionImpl(EntityType type) const override;
|
||||||
|
virtual UUID loginImpl(const String & user_name, const String & password, const Poco::Net::IPAddress & address, const ExternalAuthenticators & external_authenticators) const override;
|
||||||
|
virtual UUID getIDOfLoggedUserImpl(const String & user_name) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setConfiguration(AccessControlManager * access_control_manager_, const Poco::Util::AbstractConfiguration & config, const String & prefix);
|
||||||
|
void processRoleChange(const UUID & id, const AccessEntityPtr & entity);
|
||||||
|
void checkAllDefaultRoleNamesFoundNoLock() const;
|
||||||
|
|
||||||
|
[[noreturn]] static void throwDefaultRoleNotFound(const String & role_name);
|
||||||
|
|
||||||
|
mutable std::recursive_mutex mutex;
|
||||||
|
AccessControlManager * access_control_manager = nullptr;
|
||||||
|
String ldap_server;
|
||||||
|
std::set<String> default_role_names;
|
||||||
|
std::map<UUID, String> roles_of_interest;
|
||||||
|
ext::scope_guard role_change_subscription;
|
||||||
|
mutable MemoryAccessStorage memory_storage;
|
||||||
|
};
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
#include <Common/Exception.h>
|
#include <Common/Exception.h>
|
||||||
#include <ext/scope_guard.h>
|
#include <ext/scope_guard.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
@ -27,16 +29,13 @@ LDAPClient::~LDAPClient()
|
|||||||
closeConnection();
|
closeConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LDAPClient::openConnection()
|
|
||||||
{
|
|
||||||
const bool graceful_bind_failure = false;
|
|
||||||
diag(openConnection(graceful_bind_failure));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if USE_LDAP
|
#if USE_LDAP
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
std::recursive_mutex ldap_global_mutex;
|
||||||
|
|
||||||
auto escapeForLDAP(const String & src)
|
auto escapeForLDAP(const String & src)
|
||||||
{
|
{
|
||||||
String dest;
|
String dest;
|
||||||
@ -63,10 +62,13 @@ namespace
|
|||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LDAPClient::diag(const int rc)
|
void LDAPClient::diag(const int rc)
|
||||||
{
|
{
|
||||||
|
std::scoped_lock lock(ldap_global_mutex);
|
||||||
|
|
||||||
if (rc != LDAP_SUCCESS)
|
if (rc != LDAP_SUCCESS)
|
||||||
{
|
{
|
||||||
String text;
|
String text;
|
||||||
@ -100,8 +102,10 @@ void LDAPClient::diag(const int rc)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int LDAPClient::openConnection(const bool graceful_bind_failure)
|
void LDAPClient::openConnection()
|
||||||
{
|
{
|
||||||
|
std::scoped_lock lock(ldap_global_mutex);
|
||||||
|
|
||||||
closeConnection();
|
closeConnection();
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -232,8 +236,6 @@ int LDAPClient::openConnection(const bool graceful_bind_failure)
|
|||||||
if (params.enable_tls == LDAPServerParams::TLSEnable::YES_STARTTLS)
|
if (params.enable_tls == LDAPServerParams::TLSEnable::YES_STARTTLS)
|
||||||
diag(ldap_start_tls_s(handle, nullptr, nullptr));
|
diag(ldap_start_tls_s(handle, nullptr, nullptr));
|
||||||
|
|
||||||
int rc = LDAP_OTHER;
|
|
||||||
|
|
||||||
switch (params.sasl_mechanism)
|
switch (params.sasl_mechanism)
|
||||||
{
|
{
|
||||||
case LDAPServerParams::SASLMechanism::SIMPLE:
|
case LDAPServerParams::SASLMechanism::SIMPLE:
|
||||||
@ -244,20 +246,21 @@ int LDAPClient::openConnection(const bool graceful_bind_failure)
|
|||||||
cred.bv_val = const_cast<char *>(params.password.c_str());
|
cred.bv_val = const_cast<char *>(params.password.c_str());
|
||||||
cred.bv_len = params.password.size();
|
cred.bv_len = params.password.size();
|
||||||
|
|
||||||
rc = ldap_sasl_bind_s(handle, dn.c_str(), LDAP_SASL_SIMPLE, &cred, nullptr, nullptr, nullptr);
|
diag(ldap_sasl_bind_s(handle, dn.c_str(), LDAP_SASL_SIMPLE, &cred, nullptr, nullptr, nullptr));
|
||||||
|
|
||||||
if (!graceful_bind_failure)
|
|
||||||
diag(rc);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw Exception("Unknown SASL mechanism", ErrorCodes::LDAP_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LDAPClient::closeConnection() noexcept
|
void LDAPClient::closeConnection() noexcept
|
||||||
{
|
{
|
||||||
|
std::scoped_lock lock(ldap_global_mutex);
|
||||||
|
|
||||||
if (!handle)
|
if (!handle)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -267,42 +270,21 @@ void LDAPClient::closeConnection() noexcept
|
|||||||
|
|
||||||
bool LDAPSimpleAuthClient::check()
|
bool LDAPSimpleAuthClient::check()
|
||||||
{
|
{
|
||||||
if (params.user.empty())
|
std::scoped_lock lock(ldap_global_mutex);
|
||||||
throw Exception("LDAP authentication of a user with an empty name is not allowed", ErrorCodes::BAD_ARGUMENTS);
|
|
||||||
|
|
||||||
|
if (params.user.empty())
|
||||||
|
throw Exception("LDAP authentication of a user with empty name is not allowed", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
// Silently reject authentication attempt if the password is empty as if it didn't match.
|
||||||
if (params.password.empty())
|
if (params.password.empty())
|
||||||
return false; // Silently reject authentication attempt if the password is empty as if it didn't match.
|
return false;
|
||||||
|
|
||||||
SCOPE_EXIT({ closeConnection(); });
|
SCOPE_EXIT({ closeConnection(); });
|
||||||
|
|
||||||
const bool graceful_bind_failure = true;
|
// Will throw on any error, including invalid credentials.
|
||||||
const auto rc = openConnection(graceful_bind_failure);
|
openConnection();
|
||||||
|
|
||||||
bool result = false;
|
return true;
|
||||||
|
|
||||||
switch (rc)
|
|
||||||
{
|
|
||||||
case LDAP_SUCCESS:
|
|
||||||
{
|
|
||||||
result = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case LDAP_INVALID_CREDENTIALS:
|
|
||||||
{
|
|
||||||
result = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
result = false;
|
|
||||||
diag(rc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#else // USE_LDAP
|
#else // USE_LDAP
|
||||||
@ -312,7 +294,7 @@ void LDAPClient::diag(const int)
|
|||||||
throw Exception("ClickHouse was built without LDAP support", ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME);
|
throw Exception("ClickHouse was built without LDAP support", ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
int LDAPClient::openConnection(const bool)
|
void LDAPClient::openConnection()
|
||||||
{
|
{
|
||||||
throw Exception("ClickHouse was built without LDAP support", ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME);
|
throw Exception("ClickHouse was built without LDAP support", ErrorCodes::FEATURE_IS_NOT_ENABLED_AT_BUILD_TIME);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
MAYBE_NORETURN void diag(const int rc);
|
MAYBE_NORETURN void diag(const int rc);
|
||||||
MAYBE_NORETURN void openConnection();
|
MAYBE_NORETURN void openConnection();
|
||||||
int openConnection(const bool graceful_bind_failure = false);
|
|
||||||
void closeConnection() noexcept;
|
void closeConnection() noexcept;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -42,6 +42,7 @@ struct LDAPServerParams
|
|||||||
|
|
||||||
enum class SASLMechanism
|
enum class SASLMechanism
|
||||||
{
|
{
|
||||||
|
UNKNOWN,
|
||||||
SIMPLE
|
SIMPLE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ UUID MemoryAccessStorage::insertImpl(const AccessEntityPtr & new_entity, bool re
|
|||||||
|
|
||||||
UUID id = generateRandomID();
|
UUID id = generateRandomID();
|
||||||
std::lock_guard lock{mutex};
|
std::lock_guard lock{mutex};
|
||||||
insertNoLock(generateRandomID(), new_entity, replace_if_exists, notifications);
|
insertNoLock(id, new_entity, replace_if_exists, notifications);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include <Common/Exception.h>
|
#include <Common/Exception.h>
|
||||||
#include <ext/range.h>
|
#include <ext/range.h>
|
||||||
#include <boost/range/adaptor/map.hpp>
|
#include <boost/range/adaptor/map.hpp>
|
||||||
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
#include <boost/range/algorithm/copy.hpp>
|
#include <boost/range/algorithm/copy.hpp>
|
||||||
#include <boost/range/algorithm/find.hpp>
|
#include <boost/range/algorithm/find.hpp>
|
||||||
|
|
||||||
@ -27,6 +28,15 @@ MultipleAccessStorage::MultipleAccessStorage(const String & storage_name_)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultipleAccessStorage::~MultipleAccessStorage()
|
||||||
|
{
|
||||||
|
/// It's better to remove the storages in the reverse order because they could depend on each other somehow.
|
||||||
|
const auto storages = getStoragesPtr();
|
||||||
|
for (const auto & storage : *storages | boost::adaptors::reversed)
|
||||||
|
{
|
||||||
|
removeStorage(storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MultipleAccessStorage::setStorages(const std::vector<StoragePtr> & storages)
|
void MultipleAccessStorage::setStorages(const std::vector<StoragePtr> & storages)
|
||||||
{
|
{
|
||||||
@ -400,7 +410,7 @@ UUID MultipleAccessStorage::loginImpl(const String & user_name, const String & p
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto id = storage->login(user_name, password, address, external_authenticators);
|
auto id = storage->login(user_name, password, address, external_authenticators, /* replace_exception_with_cannot_authenticate = */ false);
|
||||||
std::lock_guard lock{mutex};
|
std::lock_guard lock{mutex};
|
||||||
ids_cache.set(id, storage);
|
ids_cache.set(id, storage);
|
||||||
return id;
|
return id;
|
||||||
@ -416,7 +426,7 @@ UUID MultipleAccessStorage::loginImpl(const String & user_name, const String & p
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throwCannotAuthenticate(user_name);
|
throwNotFound(EntityType::USER, user_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ public:
|
|||||||
using ConstStoragePtr = std::shared_ptr<const Storage>;
|
using ConstStoragePtr = std::shared_ptr<const Storage>;
|
||||||
|
|
||||||
MultipleAccessStorage(const String & storage_name_ = STORAGE_TYPE);
|
MultipleAccessStorage(const String & storage_name_ = STORAGE_TYPE);
|
||||||
|
~MultipleAccessStorage() override;
|
||||||
|
|
||||||
const char * getStorageType() const override { return STORAGE_TYPE; }
|
const char * getStorageType() const override { return STORAGE_TYPE; }
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ SRCS(
|
|||||||
GrantedRoles.cpp
|
GrantedRoles.cpp
|
||||||
IAccessEntity.cpp
|
IAccessEntity.cpp
|
||||||
IAccessStorage.cpp
|
IAccessStorage.cpp
|
||||||
|
LDAPAccessStorage.cpp
|
||||||
LDAPClient.cpp
|
LDAPClient.cpp
|
||||||
MemoryAccessStorage.cpp
|
MemoryAccessStorage.cpp
|
||||||
MultipleAccessStorage.cpp
|
MultipleAccessStorage.cpp
|
||||||
|
@ -634,4 +634,10 @@ void ColumnString::protect()
|
|||||||
getOffsets().protect();
|
getOffsets().protect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ColumnString::validate() const
|
||||||
|
{
|
||||||
|
if (!offsets.empty() && offsets.back() != chars.size())
|
||||||
|
throw Exception(ErrorCodes::LOGICAL_ERROR, "ColumnString validation failed: size mismatch (internal logical error) {} != {}", offsets.back(), chars.size());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -267,6 +267,9 @@ public:
|
|||||||
|
|
||||||
Offsets & getOffsets() { return offsets; }
|
Offsets & getOffsets() { return offsets; }
|
||||||
const Offsets & getOffsets() const { return offsets; }
|
const Offsets & getOffsets() const { return offsets; }
|
||||||
|
|
||||||
|
// Throws an exception if offsets/chars are messed up
|
||||||
|
void validate() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -510,6 +510,8 @@ namespace ErrorCodes
|
|||||||
extern const int ROW_AND_ROWS_TOGETHER = 544;
|
extern const int ROW_AND_ROWS_TOGETHER = 544;
|
||||||
extern const int FIRST_AND_NEXT_TOGETHER = 545;
|
extern const int FIRST_AND_NEXT_TOGETHER = 545;
|
||||||
extern const int NO_ROW_DELIMITER = 546;
|
extern const int NO_ROW_DELIMITER = 546;
|
||||||
|
extern const int INVALID_RAID_TYPE = 547;
|
||||||
|
extern const int UNKNOWN_VOLUME = 548;
|
||||||
|
|
||||||
extern const int KEEPER_EXCEPTION = 999;
|
extern const int KEEPER_EXCEPTION = 999;
|
||||||
extern const int POCO_EXCEPTION = 1000;
|
extern const int POCO_EXCEPTION = 1000;
|
||||||
|
@ -30,6 +30,8 @@ namespace ProfileEvents
|
|||||||
|
|
||||||
static constexpr size_t log_peak_memory_usage_every = 1ULL << 30;
|
static constexpr size_t log_peak_memory_usage_every = 1ULL << 30;
|
||||||
|
|
||||||
|
thread_local bool MemoryTracker::BlockerInThread::is_blocked = false;
|
||||||
|
|
||||||
MemoryTracker total_memory_tracker(nullptr, VariableContext::Global);
|
MemoryTracker total_memory_tracker(nullptr, VariableContext::Global);
|
||||||
|
|
||||||
|
|
||||||
@ -56,13 +58,15 @@ MemoryTracker::~MemoryTracker()
|
|||||||
void MemoryTracker::logPeakMemoryUsage() const
|
void MemoryTracker::logPeakMemoryUsage() const
|
||||||
{
|
{
|
||||||
const auto * description = description_ptr.load(std::memory_order_relaxed);
|
const auto * description = description_ptr.load(std::memory_order_relaxed);
|
||||||
LOG_DEBUG(&Poco::Logger::get("MemoryTracker"), "Peak memory usage{}: {}.", (description ? " " + std::string(description) : ""), ReadableSize(peak));
|
LOG_DEBUG(&Poco::Logger::get("MemoryTracker"),
|
||||||
|
"Peak memory usage{}: {}.", (description ? " " + std::string(description) : ""), ReadableSize(peak));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryTracker::logMemoryUsage(Int64 current) const
|
void MemoryTracker::logMemoryUsage(Int64 current) const
|
||||||
{
|
{
|
||||||
const auto * description = description_ptr.load(std::memory_order_relaxed);
|
const auto * description = description_ptr.load(std::memory_order_relaxed);
|
||||||
LOG_DEBUG(&Poco::Logger::get("MemoryTracker"), "Current memory usage{}: {}.", (description ? " " + std::string(description) : ""), ReadableSize(current));
|
LOG_DEBUG(&Poco::Logger::get("MemoryTracker"),
|
||||||
|
"Current memory usage{}: {}.", (description ? " " + std::string(description) : ""), ReadableSize(current));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -71,7 +75,7 @@ void MemoryTracker::alloc(Int64 size)
|
|||||||
if (size < 0)
|
if (size < 0)
|
||||||
throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "Negative size ({}) is passed to MemoryTracker. It is a bug.", size);
|
throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "Negative size ({}) is passed to MemoryTracker. It is a bug.", size);
|
||||||
|
|
||||||
if (blocker.isCancelled())
|
if (BlockerInThread::isBlocked())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/** Using memory_order_relaxed means that if allocations are done simultaneously,
|
/** Using memory_order_relaxed means that if allocations are done simultaneously,
|
||||||
@ -86,12 +90,15 @@ void MemoryTracker::alloc(Int64 size)
|
|||||||
Int64 current_hard_limit = hard_limit.load(std::memory_order_relaxed);
|
Int64 current_hard_limit = hard_limit.load(std::memory_order_relaxed);
|
||||||
Int64 current_profiler_limit = profiler_limit.load(std::memory_order_relaxed);
|
Int64 current_profiler_limit = profiler_limit.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
/// Cap the limit to the total_memory_tracker, since it may include some drift.
|
/// Cap the limit to the total_memory_tracker, since it may include some drift
|
||||||
|
/// for user-level memory tracker.
|
||||||
///
|
///
|
||||||
/// And since total_memory_tracker is reset to the process resident
|
/// And since total_memory_tracker is reset to the process resident
|
||||||
/// memory peridically (in AsynchronousMetrics::update()), any limit can be
|
/// memory peridically (in AsynchronousMetrics::update()), any limit can be
|
||||||
/// capped to it, to avoid possible drift.
|
/// capped to it, to avoid possible drift.
|
||||||
if (unlikely(current_hard_limit && will_be > current_hard_limit))
|
if (unlikely(current_hard_limit
|
||||||
|
&& will_be > current_hard_limit
|
||||||
|
&& level == VariableContext::User))
|
||||||
{
|
{
|
||||||
Int64 total_amount = total_memory_tracker.get();
|
Int64 total_amount = total_memory_tracker.get();
|
||||||
if (amount > total_amount)
|
if (amount > total_amount)
|
||||||
@ -104,10 +111,8 @@ void MemoryTracker::alloc(Int64 size)
|
|||||||
std::bernoulli_distribution fault(fault_probability);
|
std::bernoulli_distribution fault(fault_probability);
|
||||||
if (unlikely(fault_probability && fault(thread_local_rng)))
|
if (unlikely(fault_probability && fault(thread_local_rng)))
|
||||||
{
|
{
|
||||||
free(size);
|
|
||||||
|
|
||||||
/// Prevent recursion. Exception::ctor -> std::string -> new[] -> MemoryTracker::alloc
|
/// Prevent recursion. Exception::ctor -> std::string -> new[] -> MemoryTracker::alloc
|
||||||
auto untrack_lock = blocker.cancel(); // NOLINT
|
BlockerInThread untrack_lock;
|
||||||
|
|
||||||
ProfileEvents::increment(ProfileEvents::QueryMemoryLimitExceeded);
|
ProfileEvents::increment(ProfileEvents::QueryMemoryLimitExceeded);
|
||||||
std::stringstream message;
|
std::stringstream message;
|
||||||
@ -118,12 +123,13 @@ void MemoryTracker::alloc(Int64 size)
|
|||||||
<< " (attempt to allocate chunk of " << size << " bytes)"
|
<< " (attempt to allocate chunk of " << size << " bytes)"
|
||||||
<< ", maximum: " << formatReadableSizeWithBinarySuffix(current_hard_limit);
|
<< ", maximum: " << formatReadableSizeWithBinarySuffix(current_hard_limit);
|
||||||
|
|
||||||
|
amount.fetch_sub(size, std::memory_order_relaxed);
|
||||||
throw DB::Exception(message.str(), DB::ErrorCodes::MEMORY_LIMIT_EXCEEDED);
|
throw DB::Exception(message.str(), DB::ErrorCodes::MEMORY_LIMIT_EXCEEDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(current_profiler_limit && will_be > current_profiler_limit))
|
if (unlikely(current_profiler_limit && will_be > current_profiler_limit))
|
||||||
{
|
{
|
||||||
auto no_track = blocker.cancel();
|
BlockerInThread untrack_lock;
|
||||||
DB::TraceCollector::collect(DB::TraceType::Memory, StackTrace(), size);
|
DB::TraceCollector::collect(DB::TraceType::Memory, StackTrace(), size);
|
||||||
setOrRaiseProfilerLimit((will_be + profiler_step - 1) / profiler_step * profiler_step);
|
setOrRaiseProfilerLimit((will_be + profiler_step - 1) / profiler_step * profiler_step);
|
||||||
}
|
}
|
||||||
@ -131,16 +137,14 @@ void MemoryTracker::alloc(Int64 size)
|
|||||||
std::bernoulli_distribution sample(sample_probability);
|
std::bernoulli_distribution sample(sample_probability);
|
||||||
if (unlikely(sample_probability && sample(thread_local_rng)))
|
if (unlikely(sample_probability && sample(thread_local_rng)))
|
||||||
{
|
{
|
||||||
auto no_track = blocker.cancel();
|
BlockerInThread untrack_lock;
|
||||||
DB::TraceCollector::collect(DB::TraceType::MemorySample, StackTrace(), size);
|
DB::TraceCollector::collect(DB::TraceType::MemorySample, StackTrace(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(current_hard_limit && will_be > current_hard_limit))
|
if (unlikely(current_hard_limit && will_be > current_hard_limit))
|
||||||
{
|
{
|
||||||
free(size);
|
|
||||||
|
|
||||||
/// Prevent recursion. Exception::ctor -> std::string -> new[] -> MemoryTracker::alloc
|
/// Prevent recursion. Exception::ctor -> std::string -> new[] -> MemoryTracker::alloc
|
||||||
auto no_track = blocker.cancel(); // NOLINT
|
BlockerInThread untrack_lock;
|
||||||
|
|
||||||
ProfileEvents::increment(ProfileEvents::QueryMemoryLimitExceeded);
|
ProfileEvents::increment(ProfileEvents::QueryMemoryLimitExceeded);
|
||||||
std::stringstream message;
|
std::stringstream message;
|
||||||
@ -151,6 +155,7 @@ void MemoryTracker::alloc(Int64 size)
|
|||||||
<< " (attempt to allocate chunk of " << size << " bytes)"
|
<< " (attempt to allocate chunk of " << size << " bytes)"
|
||||||
<< ", maximum: " << formatReadableSizeWithBinarySuffix(current_hard_limit);
|
<< ", maximum: " << formatReadableSizeWithBinarySuffix(current_hard_limit);
|
||||||
|
|
||||||
|
amount.fetch_sub(size, std::memory_order_relaxed);
|
||||||
throw DB::Exception(message.str(), DB::ErrorCodes::MEMORY_LIMIT_EXCEEDED);
|
throw DB::Exception(message.str(), DB::ErrorCodes::MEMORY_LIMIT_EXCEEDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,13 +182,13 @@ void MemoryTracker::updatePeak(Int64 will_be)
|
|||||||
|
|
||||||
void MemoryTracker::free(Int64 size)
|
void MemoryTracker::free(Int64 size)
|
||||||
{
|
{
|
||||||
if (blocker.isCancelled())
|
if (BlockerInThread::isBlocked())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::bernoulli_distribution sample(sample_probability);
|
std::bernoulli_distribution sample(sample_probability);
|
||||||
if (unlikely(sample_probability && sample(thread_local_rng)))
|
if (unlikely(sample_probability && sample(thread_local_rng)))
|
||||||
{
|
{
|
||||||
auto no_track = blocker.cancel();
|
BlockerInThread untrack_lock;
|
||||||
DB::TraceCollector::collect(DB::TraceType::MemorySample, StackTrace(), -size);
|
DB::TraceCollector::collect(DB::TraceType::MemorySample, StackTrace(), -size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,11 +303,3 @@ namespace CurrentMemoryTracker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::SimpleActionLock getCurrentMemoryTrackerActionLock()
|
|
||||||
{
|
|
||||||
auto * memory_tracker = DB::CurrentThread::getMemoryTracker();
|
|
||||||
if (!memory_tracker)
|
|
||||||
return {};
|
|
||||||
return memory_tracker->blocker.cancel();
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <common/types.h>
|
#include <common/types.h>
|
||||||
#include <Common/CurrentMetrics.h>
|
#include <Common/CurrentMetrics.h>
|
||||||
#include <Common/SimpleActionBlocker.h>
|
|
||||||
#include <Common/VariableContext.h>
|
#include <Common/VariableContext.h>
|
||||||
|
|
||||||
|
|
||||||
@ -131,8 +130,18 @@ public:
|
|||||||
/// Prints info about peak memory consumption into log.
|
/// Prints info about peak memory consumption into log.
|
||||||
void logPeakMemoryUsage() const;
|
void logPeakMemoryUsage() const;
|
||||||
|
|
||||||
/// To be able to temporarily stop memory tracker
|
/// To be able to temporarily stop memory tracking from current thread.
|
||||||
DB::SimpleActionBlocker blocker;
|
struct BlockerInThread
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
BlockerInThread(const BlockerInThread &) = delete;
|
||||||
|
BlockerInThread & operator=(const BlockerInThread &) = delete;
|
||||||
|
static thread_local bool is_blocked;
|
||||||
|
public:
|
||||||
|
BlockerInThread() { is_blocked = true; }
|
||||||
|
~BlockerInThread() { is_blocked = false; }
|
||||||
|
static bool isBlocked() { return is_blocked; }
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MemoryTracker total_memory_tracker;
|
extern MemoryTracker total_memory_tracker;
|
||||||
@ -145,7 +154,3 @@ namespace CurrentMemoryTracker
|
|||||||
void realloc(Int64 old_size, Int64 new_size);
|
void realloc(Int64 old_size, Int64 new_size);
|
||||||
void free(Int64 size);
|
void free(Int64 size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Holding this object will temporarily disable memory tracking.
|
|
||||||
DB::SimpleActionLock getCurrentMemoryTrackerActionLock();
|
|
||||||
|
@ -502,8 +502,8 @@ Float NO_INLINE really_unrolled(const PODArray<UInt8> & keys, const PODArray<Flo
|
|||||||
|
|
||||||
struct State4
|
struct State4
|
||||||
{
|
{
|
||||||
Float sum[4] = {0, 0, 0, 0};
|
Float sum[4]{};
|
||||||
size_t count[4] = {0, 0, 0, 0};
|
size_t count[4]{};
|
||||||
|
|
||||||
template <UInt32 idx>
|
template <UInt32 idx>
|
||||||
void add(Float value)
|
void add(Float value)
|
||||||
@ -522,13 +522,13 @@ Float NO_INLINE another_unrolled_x4(const PODArray<UInt8> & keys, const PODArray
|
|||||||
{
|
{
|
||||||
State4 map[256]{};
|
State4 map[256]{};
|
||||||
|
|
||||||
size_t size = keys.size() & ~size_t(3);
|
size_t size = keys.size() / 4 * 4;
|
||||||
for (size_t i = 0; i < size; i+=4)
|
for (size_t i = 0; i < size; i += 4)
|
||||||
{
|
{
|
||||||
map[keys[i]].add<0>(values[i]);
|
map[keys[i]].add<0>(values[i]);
|
||||||
map[keys[i+1]].add<1>(values[i]);
|
map[keys[i + 1]].add<1>(values[i]);
|
||||||
map[keys[i+2]].add<2>(values[i]);
|
map[keys[i + 2]].add<2>(values[i]);
|
||||||
map[keys[i+3]].add<3>(values[i]);
|
map[keys[i + 3]].add<3>(values[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// tail
|
/// tail
|
||||||
|
@ -111,6 +111,7 @@ class IColumn;
|
|||||||
M(UInt64, distributed_group_by_no_merge, 0, "If 1, Do not merge aggregation states from different servers for distributed query processing - in case it is for certain that there are different keys on different shards. If 2 - same as 1 but also apply ORDER BY and LIMIT stages", 0) \
|
M(UInt64, distributed_group_by_no_merge, 0, "If 1, Do not merge aggregation states from different servers for distributed query processing - in case it is for certain that there are different keys on different shards. If 2 - same as 1 but also apply ORDER BY and LIMIT stages", 0) \
|
||||||
M(Bool, optimize_distributed_group_by_sharding_key, false, "Optimize GROUP BY sharding_key queries (by avodiing costly aggregation on the initiator server).", 0) \
|
M(Bool, optimize_distributed_group_by_sharding_key, false, "Optimize GROUP BY sharding_key queries (by avodiing costly aggregation on the initiator server).", 0) \
|
||||||
M(Bool, optimize_skip_unused_shards, false, "Assumes that data is distributed by sharding_key. Optimization to skip unused shards if SELECT query filters by sharding_key.", 0) \
|
M(Bool, optimize_skip_unused_shards, false, "Assumes that data is distributed by sharding_key. Optimization to skip unused shards if SELECT query filters by sharding_key.", 0) \
|
||||||
|
M(Bool, allow_nondeterministic_optimize_skip_unused_shards, false, "Allow non-deterministic functions (includes dictGet) in sharding_key for optimize_skip_unused_shards", 0) \
|
||||||
M(UInt64, force_optimize_skip_unused_shards, 0, "Throw an exception if unused shards cannot be skipped (1 - throw only if the table has the sharding key, 2 - always throw.", 0) \
|
M(UInt64, force_optimize_skip_unused_shards, 0, "Throw an exception if unused shards cannot be skipped (1 - throw only if the table has the sharding key, 2 - always throw.", 0) \
|
||||||
M(UInt64, optimize_skip_unused_shards_nesting, 0, "Same as optimize_skip_unused_shards, but accept nesting level until which it will work.", 0) \
|
M(UInt64, optimize_skip_unused_shards_nesting, 0, "Same as optimize_skip_unused_shards, but accept nesting level until which it will work.", 0) \
|
||||||
M(UInt64, force_optimize_skip_unused_shards_nesting, 0, "Same as force_optimize_skip_unused_shards, but accept nesting level until which it will work.", 0) \
|
M(UInt64, force_optimize_skip_unused_shards_nesting, 0, "Same as force_optimize_skip_unused_shards, but accept nesting level until which it will work.", 0) \
|
||||||
@ -153,6 +154,7 @@ class IColumn;
|
|||||||
\
|
\
|
||||||
M(DistributedProductMode, distributed_product_mode, DistributedProductMode::DENY, "How are distributed subqueries performed inside IN or JOIN sections?", IMPORTANT) \
|
M(DistributedProductMode, distributed_product_mode, DistributedProductMode::DENY, "How are distributed subqueries performed inside IN or JOIN sections?", IMPORTANT) \
|
||||||
\
|
\
|
||||||
|
M(UInt64, max_concurrent_queries_for_all_users, 0, "The maximum number of concurrent requests for all users.", 0) \
|
||||||
M(UInt64, max_concurrent_queries_for_user, 0, "The maximum number of concurrent requests per user.", 0) \
|
M(UInt64, max_concurrent_queries_for_user, 0, "The maximum number of concurrent requests per user.", 0) \
|
||||||
\
|
\
|
||||||
M(Bool, insert_deduplicate, true, "For INSERT queries in the replicated table, specifies that deduplication of insertings blocks should be performed", 0) \
|
M(Bool, insert_deduplicate, true, "For INSERT queries in the replicated table, specifies that deduplication of insertings blocks should be performed", 0) \
|
||||||
@ -411,12 +413,14 @@ class IColumn;
|
|||||||
M(Bool, format_csv_allow_double_quotes, 1, "If it is set to true, allow strings in double quotes.", 0) \
|
M(Bool, format_csv_allow_double_quotes, 1, "If it is set to true, allow strings in double quotes.", 0) \
|
||||||
M(Bool, output_format_csv_crlf_end_of_line, false, "If it is set true, end of line in CSV format will be \\r\\n instead of \\n.", 0) \
|
M(Bool, output_format_csv_crlf_end_of_line, false, "If it is set true, end of line in CSV format will be \\r\\n instead of \\n.", 0) \
|
||||||
M(Bool, input_format_csv_unquoted_null_literal_as_null, false, "Consider unquoted NULL literal as \\N", 0) \
|
M(Bool, input_format_csv_unquoted_null_literal_as_null, false, "Consider unquoted NULL literal as \\N", 0) \
|
||||||
|
M(Bool, input_format_csv_enum_as_number, false, "Treat inserted enum values in CSV formats as enum indices \\N", 0) \
|
||||||
M(Bool, input_format_skip_unknown_fields, false, "Skip columns with unknown names from input data (it works for JSONEachRow, CSVWithNames, TSVWithNames and TSKV formats).", 0) \
|
M(Bool, input_format_skip_unknown_fields, false, "Skip columns with unknown names from input data (it works for JSONEachRow, CSVWithNames, TSVWithNames and TSKV formats).", 0) \
|
||||||
M(Bool, input_format_with_names_use_header, true, "For TSVWithNames and CSVWithNames input formats this controls whether format parser is to assume that column data appear in the input exactly as they are specified in the header.", 0) \
|
M(Bool, input_format_with_names_use_header, true, "For TSVWithNames and CSVWithNames input formats this controls whether format parser is to assume that column data appear in the input exactly as they are specified in the header.", 0) \
|
||||||
M(Bool, input_format_import_nested_json, false, "Map nested JSON data to nested tables (it works for JSONEachRow format).", 0) \
|
M(Bool, input_format_import_nested_json, false, "Map nested JSON data to nested tables (it works for JSONEachRow format).", 0) \
|
||||||
M(Bool, optimize_aggregators_of_group_by_keys, true, "Eliminates min/max/any/anyLast aggregators of GROUP BY keys in SELECT section", 0) \
|
M(Bool, optimize_aggregators_of_group_by_keys, true, "Eliminates min/max/any/anyLast aggregators of GROUP BY keys in SELECT section", 0) \
|
||||||
M(Bool, input_format_defaults_for_omitted_fields, true, "For input data calculate default expressions for omitted fields (it works for JSONEachRow, CSV and TSV formats).", IMPORTANT) \
|
M(Bool, input_format_defaults_for_omitted_fields, true, "For input data calculate default expressions for omitted fields (it works for JSONEachRow, CSV and TSV formats).", IMPORTANT) \
|
||||||
M(Bool, input_format_tsv_empty_as_default, false, "Treat empty fields in TSV input as default values.", 0) \
|
M(Bool, input_format_tsv_empty_as_default, false, "Treat empty fields in TSV input as default values.", 0) \
|
||||||
|
M(Bool, input_format_tsv_enum_as_number, false, "Treat inserted enum values in TSV formats as enum indices \\N", 0) \
|
||||||
M(Bool, input_format_null_as_default, false, "For text input formats initialize null fields with default values if data type of this field is not nullable", 0) \
|
M(Bool, input_format_null_as_default, false, "For text input formats initialize null fields with default values if data type of this field is not nullable", 0) \
|
||||||
\
|
\
|
||||||
M(DateTimeInputFormat, date_time_input_format, FormatSettings::DateTimeInputFormat::Basic, "Method to read DateTime from text input formats. Possible values: 'basic' and 'best_effort'.", 0) \
|
M(DateTimeInputFormat, date_time_input_format, FormatSettings::DateTimeInputFormat::Basic, "Method to read DateTime from text input formats. Possible values: 'basic' and 'best_effort'.", 0) \
|
||||||
|
@ -146,12 +146,17 @@ void DataTypeEnum<Type>::serializeTextEscaped(const IColumn & column, size_t row
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
void DataTypeEnum<Type>::deserializeTextEscaped(IColumn & column, ReadBuffer & istr, const FormatSettings &) const
|
void DataTypeEnum<Type>::deserializeTextEscaped(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const
|
||||||
{
|
{
|
||||||
|
if (settings.tsv.input_format_enum_as_number)
|
||||||
|
assert_cast<ColumnType &>(column).getData().push_back(readValue(istr));
|
||||||
|
else
|
||||||
|
{
|
||||||
/// NOTE It would be nice to do without creating a temporary object - at least extract std::string out.
|
/// NOTE It would be nice to do without creating a temporary object - at least extract std::string out.
|
||||||
std::string field_name;
|
std::string field_name;
|
||||||
readEscapedString(field_name, istr);
|
readEscapedString(field_name, istr);
|
||||||
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
@ -169,11 +174,16 @@ void DataTypeEnum<Type>::deserializeTextQuoted(IColumn & column, ReadBuffer & is
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
void DataTypeEnum<Type>::deserializeWholeText(IColumn & column, ReadBuffer & istr, const FormatSettings &) const
|
void DataTypeEnum<Type>::deserializeWholeText(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const
|
||||||
{
|
{
|
||||||
|
if (settings.tsv.input_format_enum_as_number)
|
||||||
|
assert_cast<ColumnType &>(column).getData().push_back(readValue(istr));
|
||||||
|
else
|
||||||
|
{
|
||||||
std::string field_name;
|
std::string field_name;
|
||||||
readString(field_name, istr);
|
readString(field_name, istr);
|
||||||
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
@ -191,9 +201,14 @@ void DataTypeEnum<Type>::serializeTextXML(const IColumn & column, size_t row_num
|
|||||||
template <typename Type>
|
template <typename Type>
|
||||||
void DataTypeEnum<Type>::deserializeTextJSON(IColumn & column, ReadBuffer & istr, const FormatSettings &) const
|
void DataTypeEnum<Type>::deserializeTextJSON(IColumn & column, ReadBuffer & istr, const FormatSettings &) const
|
||||||
{
|
{
|
||||||
|
if (!istr.eof() && *istr.position() != '"')
|
||||||
|
assert_cast<ColumnType &>(column).getData().push_back(readValue(istr));
|
||||||
|
else
|
||||||
|
{
|
||||||
std::string field_name;
|
std::string field_name;
|
||||||
readJSONString(field_name, istr);
|
readJSONString(field_name, istr);
|
||||||
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
@ -205,9 +220,14 @@ void DataTypeEnum<Type>::serializeTextCSV(const IColumn & column, size_t row_num
|
|||||||
template <typename Type>
|
template <typename Type>
|
||||||
void DataTypeEnum<Type>::deserializeTextCSV(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const
|
void DataTypeEnum<Type>::deserializeTextCSV(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const
|
||||||
{
|
{
|
||||||
|
if (settings.csv.input_format_enum_as_number)
|
||||||
|
assert_cast<ColumnType &>(column).getData().push_back(readValue(istr));
|
||||||
|
else
|
||||||
|
{
|
||||||
std::string field_name;
|
std::string field_name;
|
||||||
readCSVString(field_name, istr, settings.csv);
|
readCSVString(field_name, istr, settings.csv);
|
||||||
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
assert_cast<ColumnType &>(column).getData().push_back(getValue(StringRef(field_name)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
|
@ -66,13 +66,18 @@ public:
|
|||||||
|
|
||||||
TypeIndex getTypeId() const override { return sizeof(FieldType) == 1 ? TypeIndex::Enum8 : TypeIndex::Enum16; }
|
TypeIndex getTypeId() const override { return sizeof(FieldType) == 1 ? TypeIndex::Enum8 : TypeIndex::Enum16; }
|
||||||
|
|
||||||
const StringRef & getNameForValue(const FieldType & value) const
|
auto findByValue(const FieldType & value) const
|
||||||
{
|
{
|
||||||
const auto it = value_to_name_map.find(value);
|
const auto it = value_to_name_map.find(value);
|
||||||
if (it == std::end(value_to_name_map))
|
if (it == std::end(value_to_name_map))
|
||||||
throw Exception{"Unexpected value " + toString(value) + " for type " + getName(), ErrorCodes::BAD_ARGUMENTS};
|
throw Exception{"Unexpected value " + toString(value) + " for type " + getName(), ErrorCodes::BAD_ARGUMENTS};
|
||||||
|
|
||||||
return it->second;
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StringRef & getNameForValue(const FieldType & value) const
|
||||||
|
{
|
||||||
|
return findByValue(value)->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldType getValue(StringRef field_name) const
|
FieldType getValue(StringRef field_name) const
|
||||||
@ -84,6 +89,13 @@ public:
|
|||||||
return it->getMapped();
|
return it->getMapped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldType readValue(ReadBuffer & istr) const
|
||||||
|
{
|
||||||
|
FieldType x;
|
||||||
|
readText(x, istr);
|
||||||
|
return findByValue(x)->first;
|
||||||
|
}
|
||||||
|
|
||||||
Field castToName(const Field & value_or_name) const override;
|
Field castToName(const Field & value_or_name) const override;
|
||||||
Field castToValue(const Field & value_or_name) const override;
|
Field castToValue(const Field & value_or_name) const override;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ FileDictionarySource::FileDictionarySource(
|
|||||||
{
|
{
|
||||||
const String user_files_path = context.getUserFilesPath();
|
const String user_files_path = context.getUserFilesPath();
|
||||||
if (!startsWith(filepath, user_files_path))
|
if (!startsWith(filepath, user_files_path))
|
||||||
throw Exception("File path " + filepath + " is not inside " + user_files_path, ErrorCodes::PATH_ACCESS_DENIED);
|
throw Exception(ErrorCodes::PATH_ACCESS_DENIED, "File path {} is not inside {}", filepath, user_files_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ BlockInputStreamPtr FileDictionarySource::loadAll()
|
|||||||
|
|
||||||
std::string FileDictionarySource::toString() const
|
std::string FileDictionarySource::toString() const
|
||||||
{
|
{
|
||||||
return "File: " + filepath + ' ' + format;
|
return fmt::format("File: {}, {}", filepath, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,4 +180,9 @@ void DiskDecorator::sync(int fd) const
|
|||||||
delegate->sync(fd);
|
delegate->sync(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Executor & DiskDecorator::getExecutor()
|
||||||
|
{
|
||||||
|
return delegate->getExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** Forwards all methods to another disk.
|
||||||
|
* Methods can be overridden by descendants.
|
||||||
|
*/
|
||||||
class DiskDecorator : public IDisk
|
class DiskDecorator : public IDisk
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -46,6 +50,7 @@ public:
|
|||||||
void close(int fd) const override;
|
void close(int fd) const override;
|
||||||
void sync(int fd) const override;
|
void sync(int fd) const override;
|
||||||
const String getType() const override { return delegate->getType(); }
|
const String getType() const override { return delegate->getType(); }
|
||||||
|
Executor & getExecutor() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DiskPtr delegate;
|
DiskPtr delegate;
|
||||||
|
@ -23,8 +23,11 @@ public:
|
|||||||
DiskSelector(const Poco::Util::AbstractConfiguration & config, const String & config_prefix, const Context & context);
|
DiskSelector(const Poco::Util::AbstractConfiguration & config, const String & config_prefix, const Context & context);
|
||||||
DiskSelector(const DiskSelector & from) : disks(from.disks) { }
|
DiskSelector(const DiskSelector & from) : disks(from.disks) { }
|
||||||
|
|
||||||
DiskSelectorPtr
|
DiskSelectorPtr updateFromConfig(
|
||||||
updateFromConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix, const Context & context) const;
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
const Context & context
|
||||||
|
) const;
|
||||||
|
|
||||||
/// Get disk by name
|
/// Get disk by name
|
||||||
DiskPtr get(const String & name) const;
|
DiskPtr get(const String & name) const;
|
||||||
|
@ -195,10 +195,10 @@ public:
|
|||||||
/// Invoked when Global Context is shutdown.
|
/// Invoked when Global Context is shutdown.
|
||||||
virtual void shutdown() { }
|
virtual void shutdown() { }
|
||||||
|
|
||||||
private:
|
|
||||||
/// Returns executor to perform asynchronous operations.
|
/// Returns executor to perform asynchronous operations.
|
||||||
Executor & getExecutor() { return *executor; }
|
virtual Executor & getExecutor() { return *executor; }
|
||||||
|
|
||||||
|
private:
|
||||||
std::unique_ptr<Executor> executor;
|
std::unique_ptr<Executor> executor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ namespace DB
|
|||||||
{
|
{
|
||||||
namespace ErrorCodes
|
namespace ErrorCodes
|
||||||
{
|
{
|
||||||
extern const int EXCESSIVE_ELEMENT_IN_CONFIG;
|
extern const int NO_ELEMENTS_IN_CONFIG;
|
||||||
extern const int INCONSISTENT_RESERVATIONS;
|
extern const int INCONSISTENT_RESERVATIONS;
|
||||||
extern const int NO_RESERVATIONS_PROVIDED;
|
extern const int NO_RESERVATIONS_PROVIDED;
|
||||||
extern const int UNKNOWN_VOLUME_TYPE;
|
extern const int UNKNOWN_VOLUME_TYPE;
|
||||||
@ -51,7 +51,7 @@ IVolume::IVolume(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (disks.empty())
|
if (disks.empty())
|
||||||
throw Exception("Volume must contain at least one disk.", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
throw Exception("Volume must contain at least one disk", ErrorCodes::NO_ELEMENTS_IN_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt64 IVolume::getMaxUnreservedFreeSpace() const
|
UInt64 IVolume::getMaxUnreservedFreeSpace() const
|
||||||
|
@ -64,6 +64,12 @@ public:
|
|||||||
virtual DiskPtr getDisk(size_t i) const { return disks[i]; }
|
virtual DiskPtr getDisk(size_t i) const { return disks[i]; }
|
||||||
const Disks & getDisks() const { return disks; }
|
const Disks & getDisks() const { return disks; }
|
||||||
|
|
||||||
|
/// Returns effective value of whether merges are allowed on this volume (true) or not (false).
|
||||||
|
virtual bool areMergesAvoided() const { return false; }
|
||||||
|
|
||||||
|
/// User setting for enabling and disabling merges on volume.
|
||||||
|
virtual void setAvoidMergesUserOverride(bool /*avoid*/) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Disks disks;
|
Disks disks;
|
||||||
const String name;
|
const String name;
|
||||||
|
@ -8,7 +8,7 @@ namespace DB
|
|||||||
class SingleDiskVolume : public IVolume
|
class SingleDiskVolume : public IVolume
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SingleDiskVolume(const String & name_, DiskPtr disk): IVolume(name_, {disk})
|
SingleDiskVolume(const String & name_, DiskPtr disk, size_t max_data_part_size_ = 0): IVolume(name_, {disk}, max_data_part_size_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,13 @@
|
|||||||
#include <Poco/File.h>
|
#include <Poco/File.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const auto DEFAULT_STORAGE_POLICY_NAME = "default";
|
||||||
|
const auto DEFAULT_VOLUME_NAME = "default";
|
||||||
|
const auto DEFAULT_DISK_NAME = "default";
|
||||||
|
}
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -18,11 +25,14 @@ namespace ErrorCodes
|
|||||||
{
|
{
|
||||||
extern const int BAD_ARGUMENTS;
|
extern const int BAD_ARGUMENTS;
|
||||||
extern const int EXCESSIVE_ELEMENT_IN_CONFIG;
|
extern const int EXCESSIVE_ELEMENT_IN_CONFIG;
|
||||||
|
extern const int NO_ELEMENTS_IN_CONFIG;
|
||||||
extern const int UNKNOWN_DISK;
|
extern const int UNKNOWN_DISK;
|
||||||
extern const int UNKNOWN_POLICY;
|
extern const int UNKNOWN_POLICY;
|
||||||
|
extern const int UNKNOWN_VOLUME;
|
||||||
extern const int LOGICAL_ERROR;
|
extern const int LOGICAL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StoragePolicy::StoragePolicy(
|
StoragePolicy::StoragePolicy(
|
||||||
String name_,
|
String name_,
|
||||||
const Poco::Util::AbstractConfiguration & config,
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
@ -30,44 +40,42 @@ StoragePolicy::StoragePolicy(
|
|||||||
DiskSelectorPtr disks)
|
DiskSelectorPtr disks)
|
||||||
: name(std::move(name_))
|
: name(std::move(name_))
|
||||||
{
|
{
|
||||||
String volumes_prefix = config_prefix + ".volumes";
|
|
||||||
if (!config.has(volumes_prefix))
|
|
||||||
throw Exception("StoragePolicy must contain at least one volume (.volumes)", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
|
||||||
|
|
||||||
Poco::Util::AbstractConfiguration::Keys keys;
|
Poco::Util::AbstractConfiguration::Keys keys;
|
||||||
|
String volumes_prefix = config_prefix + ".volumes";
|
||||||
|
|
||||||
|
if (!config.has(volumes_prefix))
|
||||||
|
{
|
||||||
|
if (name != DEFAULT_STORAGE_POLICY_NAME)
|
||||||
|
throw Exception("Storage policy " + backQuote(name) + " must contain at least one volume (.volumes)", ErrorCodes::NO_ELEMENTS_IN_CONFIG);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
config.keys(volumes_prefix, keys);
|
config.keys(volumes_prefix, keys);
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto & attr_name : keys)
|
for (const auto & attr_name : keys)
|
||||||
{
|
{
|
||||||
if (!std::all_of(attr_name.begin(), attr_name.end(), isWordCharASCII))
|
if (!std::all_of(attr_name.begin(), attr_name.end(), isWordCharASCII))
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"Volume name can contain only alphanumeric and '_' (" + attr_name + ")", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
"Volume name can contain only alphanumeric and '_' in storage policy " + backQuote(name) + " (" + attr_name + ")", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
||||||
volumes.push_back(std::make_shared<VolumeJBOD>(attr_name, config, volumes_prefix + "." + attr_name, disks));
|
volumes.emplace_back(createVolumeFromConfig(attr_name, config, volumes_prefix + "." + attr_name, disks));
|
||||||
if (volumes_names.find(attr_name) != volumes_names.end())
|
}
|
||||||
throw Exception("Volumes names must be unique (" + attr_name + " duplicated)", ErrorCodes::UNKNOWN_POLICY);
|
|
||||||
volumes_names[attr_name] = volumes.size() - 1;
|
if (volumes.empty() && name == DEFAULT_STORAGE_POLICY_NAME)
|
||||||
|
{
|
||||||
|
auto default_volume = std::make_shared<VolumeJBOD>(DEFAULT_VOLUME_NAME, std::vector<DiskPtr>{disks->get(DEFAULT_DISK_NAME)}, 0, false);
|
||||||
|
volumes.emplace_back(std::move(default_volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (volumes.empty())
|
if (volumes.empty())
|
||||||
throw Exception("StoragePolicy must contain at least one volume.", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
throw Exception("Storage policy " + backQuote(name) + " must contain at least one volume.", ErrorCodes::NO_ELEMENTS_IN_CONFIG);
|
||||||
|
|
||||||
/// Check that disks are unique in Policy
|
const double default_move_factor = volumes.size() > 1 ? 0.1 : 0.0;
|
||||||
std::set<String> disk_names;
|
move_factor = config.getDouble(config_prefix + ".move_factor", default_move_factor);
|
||||||
for (const auto & volume : volumes)
|
|
||||||
{
|
|
||||||
for (const auto & disk : volume->getDisks())
|
|
||||||
{
|
|
||||||
if (disk_names.find(disk->getName()) != disk_names.end())
|
|
||||||
throw Exception(
|
|
||||||
"Duplicate disk '" + disk->getName() + "' in storage policy '" + name + "'", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
|
||||||
|
|
||||||
disk_names.insert(disk->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
move_factor = config.getDouble(config_prefix + ".move_factor", 0.1);
|
|
||||||
if (move_factor > 1)
|
if (move_factor > 1)
|
||||||
throw Exception("Disk move factor have to be in [0., 1.] interval, but set to " + toString(move_factor), ErrorCodes::LOGICAL_ERROR);
|
throw Exception("Disk move factor have to be in [0., 1.] interval, but set to " + toString(move_factor) + " in storage policy " + backQuote(name), ErrorCodes::LOGICAL_ERROR);
|
||||||
|
|
||||||
|
buildVolumeIndices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -75,16 +83,43 @@ StoragePolicy::StoragePolicy(String name_, Volumes volumes_, double move_factor_
|
|||||||
: volumes(std::move(volumes_)), name(std::move(name_)), move_factor(move_factor_)
|
: volumes(std::move(volumes_)), name(std::move(name_)), move_factor(move_factor_)
|
||||||
{
|
{
|
||||||
if (volumes.empty())
|
if (volumes.empty())
|
||||||
throw Exception("StoragePolicy must contain at least one Volume.", ErrorCodes::UNKNOWN_POLICY);
|
throw Exception("Storage policy " + backQuote(name) + " must contain at least one Volume.", ErrorCodes::NO_ELEMENTS_IN_CONFIG);
|
||||||
|
|
||||||
if (move_factor > 1)
|
if (move_factor > 1)
|
||||||
throw Exception("Disk move factor have to be in [0., 1.] interval, but set to " + toString(move_factor), ErrorCodes::LOGICAL_ERROR);
|
throw Exception("Disk move factor have to be in [0., 1.] interval, but set to " + toString(move_factor) + " in storage policy " + backQuote(name), ErrorCodes::LOGICAL_ERROR);
|
||||||
|
|
||||||
for (size_t i = 0; i < volumes.size(); ++i)
|
buildVolumeIndices();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StoragePolicy::StoragePolicy(const StoragePolicy & storage_policy,
|
||||||
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
DiskSelectorPtr disks)
|
||||||
|
: StoragePolicy(storage_policy.getName(), config, config_prefix, disks)
|
||||||
|
{
|
||||||
|
for (auto & volume : volumes)
|
||||||
{
|
{
|
||||||
if (volumes_names.find(volumes[i]->getName()) != volumes_names.end())
|
if (storage_policy.volume_index_by_volume_name.count(volume->getName()) > 0)
|
||||||
throw Exception("Volumes names must be unique (" + volumes[i]->getName() + " duplicated).", ErrorCodes::UNKNOWN_POLICY);
|
{
|
||||||
volumes_names[volumes[i]->getName()] = i;
|
auto old_volume = storage_policy.getVolumeByName(volume->getName());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto new_volume = updateVolumeFromConfig(old_volume, config, config_prefix + ".volumes." + volume->getName(), disks);
|
||||||
|
volume = std::move(new_volume);
|
||||||
|
}
|
||||||
|
catch (Exception & e)
|
||||||
|
{
|
||||||
|
/// Default policies are allowed to be missed in configuration.
|
||||||
|
if (e.code() != ErrorCodes::NO_ELEMENTS_IN_CONFIG || storage_policy.getName() != DEFAULT_STORAGE_POLICY_NAME)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
Poco::Util::AbstractConfiguration::Keys keys;
|
||||||
|
config.keys(config_prefix, keys);
|
||||||
|
if (!keys.empty())
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,20 +128,20 @@ bool StoragePolicy::isDefaultPolicy() const
|
|||||||
{
|
{
|
||||||
/// Guessing if this policy is default, not 100% correct though.
|
/// Guessing if this policy is default, not 100% correct though.
|
||||||
|
|
||||||
if (getName() != "default")
|
if (getName() != DEFAULT_STORAGE_POLICY_NAME)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (volumes.size() != 1)
|
if (volumes.size() != 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (volumes[0]->getName() != "default")
|
if (volumes[0]->getName() != DEFAULT_VOLUME_NAME)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto & disks = volumes[0]->getDisks();
|
const auto & disks = volumes[0]->getDisks();
|
||||||
if (disks.size() != 1)
|
if (disks.size() != 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (disks[0]->getName() != "default")
|
if (disks[0]->getName() != DEFAULT_DISK_NAME)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -128,10 +163,10 @@ DiskPtr StoragePolicy::getAnyDisk() const
|
|||||||
/// StoragePolicy must contain at least one Volume
|
/// StoragePolicy must contain at least one Volume
|
||||||
/// Volume must contain at least one Disk
|
/// Volume must contain at least one Disk
|
||||||
if (volumes.empty())
|
if (volumes.empty())
|
||||||
throw Exception("StoragePolicy has no volumes. It's a bug.", ErrorCodes::LOGICAL_ERROR);
|
throw Exception("Storage policy " + backQuote(name) + " has no volumes. It's a bug.", ErrorCodes::LOGICAL_ERROR);
|
||||||
|
|
||||||
if (volumes[0]->getDisks().empty())
|
if (volumes[0]->getDisks().empty())
|
||||||
throw Exception("Volume '" + volumes[0]->getName() + "' has no disks. It's a bug.", ErrorCodes::LOGICAL_ERROR);
|
throw Exception("Volume " + backQuote(name) + "." + backQuote(volumes[0]->getName()) + " has no disks. It's a bug.", ErrorCodes::LOGICAL_ERROR);
|
||||||
|
|
||||||
return volumes[0]->getDisks()[0];
|
return volumes[0]->getDisks()[0];
|
||||||
}
|
}
|
||||||
@ -195,6 +230,24 @@ ReservationPtr StoragePolicy::makeEmptyReservationOnLargestDisk() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VolumePtr StoragePolicy::getVolume(size_t index) const
|
||||||
|
{
|
||||||
|
if (index < volume_index_by_volume_name.size())
|
||||||
|
return volumes[index];
|
||||||
|
else
|
||||||
|
throw Exception("No volume with index " + std::to_string(index) + " in storage policy " + backQuote(name), ErrorCodes::UNKNOWN_VOLUME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VolumePtr StoragePolicy::getVolumeByName(const String & volume_name) const
|
||||||
|
{
|
||||||
|
auto it = volume_index_by_volume_name.find(volume_name);
|
||||||
|
if (it == volume_index_by_volume_name.end())
|
||||||
|
throw Exception("No such volume " + backQuote(volume_name) + " in storage policy " + backQuote(name), ErrorCodes::UNKNOWN_VOLUME);
|
||||||
|
return getVolume(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void StoragePolicy::checkCompatibleWith(const StoragePolicyPtr & new_storage_policy) const
|
void StoragePolicy::checkCompatibleWith(const StoragePolicyPtr & new_storage_policy) const
|
||||||
{
|
{
|
||||||
std::unordered_set<String> new_volume_names;
|
std::unordered_set<String> new_volume_names;
|
||||||
@ -204,7 +257,7 @@ void StoragePolicy::checkCompatibleWith(const StoragePolicyPtr & new_storage_pol
|
|||||||
for (const auto & volume : getVolumes())
|
for (const auto & volume : getVolumes())
|
||||||
{
|
{
|
||||||
if (new_volume_names.count(volume->getName()) == 0)
|
if (new_volume_names.count(volume->getName()) == 0)
|
||||||
throw Exception("New storage policy shall contain volumes of old one", ErrorCodes::BAD_ARGUMENTS);
|
throw Exception("New storage policy " + backQuote(name) + " shall contain volumes of old one", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
std::unordered_set<String> new_disk_names;
|
std::unordered_set<String> new_disk_names;
|
||||||
for (const auto & disk : new_storage_policy->getVolumeByName(volume->getName())->getDisks())
|
for (const auto & disk : new_storage_policy->getVolumeByName(volume->getName())->getDisks())
|
||||||
@ -212,21 +265,46 @@ void StoragePolicy::checkCompatibleWith(const StoragePolicyPtr & new_storage_pol
|
|||||||
|
|
||||||
for (const auto & disk : volume->getDisks())
|
for (const auto & disk : volume->getDisks())
|
||||||
if (new_disk_names.count(disk->getName()) == 0)
|
if (new_disk_names.count(disk->getName()) == 0)
|
||||||
throw Exception("New storage policy shall contain disks of old one", ErrorCodes::BAD_ARGUMENTS);
|
throw Exception("New storage policy " + backQuote(name) + " shall contain disks of old one", ErrorCodes::BAD_ARGUMENTS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
size_t StoragePolicy::getVolumeIndexByDisk(const DiskPtr & disk_ptr) const
|
size_t StoragePolicy::getVolumeIndexByDisk(const DiskPtr & disk_ptr) const
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < volumes.size(); ++i)
|
auto it = volume_index_by_disk_name.find(disk_ptr->getName());
|
||||||
|
if (it != volume_index_by_disk_name.end())
|
||||||
|
return it->second;
|
||||||
|
else
|
||||||
|
throw Exception("No disk " + backQuote(disk_ptr->getName()) + " in policy " + backQuote(name), ErrorCodes::UNKNOWN_DISK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void StoragePolicy::buildVolumeIndices()
|
||||||
|
{
|
||||||
|
for (size_t index = 0; index < volumes.size(); ++index)
|
||||||
{
|
{
|
||||||
const auto & volume = volumes[i];
|
const VolumePtr & volume = volumes[index];
|
||||||
|
|
||||||
|
if (volume_index_by_volume_name.find(volume->getName()) != volume_index_by_volume_name.end())
|
||||||
|
throw Exception("Volume names must be unique in storage policy "
|
||||||
|
+ backQuote(name) + " (" + backQuote(volume->getName()) + " is duplicated)"
|
||||||
|
, ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
||||||
|
|
||||||
|
volume_index_by_volume_name[volume->getName()] = index;
|
||||||
|
|
||||||
for (const auto & disk : volume->getDisks())
|
for (const auto & disk : volume->getDisks())
|
||||||
if (disk->getName() == disk_ptr->getName())
|
{
|
||||||
return i;
|
const String & disk_name = disk->getName();
|
||||||
|
|
||||||
|
if (volume_index_by_disk_name.find(disk_name) != volume_index_by_disk_name.end())
|
||||||
|
throw Exception("Disk names must be unique in storage policy "
|
||||||
|
+ backQuote(name) + " (" + backQuote(disk_name) + " is duplicated)"
|
||||||
|
, ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
||||||
|
|
||||||
|
volume_index_by_disk_name[disk_name] = index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw Exception("No disk " + disk_ptr->getName() + " in policy " + name, ErrorCodes::UNKNOWN_DISK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -242,44 +320,40 @@ StoragePolicySelector::StoragePolicySelector(
|
|||||||
{
|
{
|
||||||
if (!std::all_of(name.begin(), name.end(), isWordCharASCII))
|
if (!std::all_of(name.begin(), name.end(), isWordCharASCII))
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"StoragePolicy name can contain only alphanumeric and '_' (" + name + ")", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
"Storage policy name can contain only alphanumeric and '_' (" + backQuote(name) + ")", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG);
|
||||||
|
|
||||||
policies.emplace(name, std::make_shared<StoragePolicy>(name, config, config_prefix + "." + name, disks));
|
policies.emplace(name, std::make_shared<StoragePolicy>(name, config, config_prefix + "." + name, disks));
|
||||||
LOG_INFO(&Poco::Logger::get("StoragePolicySelector"), "Storage policy {} loaded", backQuote(name));
|
LOG_INFO(&Poco::Logger::get("StoragePolicySelector"), "Storage policy {} loaded", backQuote(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr auto default_storage_policy_name = "default";
|
/// Add default policy if it isn't explicitly specified.
|
||||||
constexpr auto default_volume_name = "default";
|
if (policies.find(DEFAULT_STORAGE_POLICY_NAME) == policies.end())
|
||||||
constexpr auto default_disk_name = "default";
|
|
||||||
|
|
||||||
/// Add default policy if it's not specified explicetly
|
|
||||||
if (policies.find(default_storage_policy_name) == policies.end())
|
|
||||||
{
|
{
|
||||||
auto default_volume = std::make_shared<VolumeJBOD>(default_volume_name, std::vector<DiskPtr>{disks->get(default_disk_name)}, 0);
|
auto default_policy = std::make_shared<StoragePolicy>(DEFAULT_STORAGE_POLICY_NAME, config, config_prefix + "." + DEFAULT_STORAGE_POLICY_NAME, disks);
|
||||||
|
policies.emplace(DEFAULT_STORAGE_POLICY_NAME, std::move(default_policy));
|
||||||
auto default_policy = std::make_shared<StoragePolicy>(default_storage_policy_name, Volumes{default_volume}, 0.0);
|
|
||||||
policies.emplace(default_storage_policy_name, default_policy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StoragePolicySelectorPtr StoragePolicySelector::updateFromConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix, DiskSelectorPtr disks) const
|
StoragePolicySelectorPtr StoragePolicySelector::updateFromConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix, DiskSelectorPtr disks) const
|
||||||
{
|
{
|
||||||
Poco::Util::AbstractConfiguration::Keys keys;
|
|
||||||
config.keys(config_prefix, keys);
|
|
||||||
|
|
||||||
std::shared_ptr<StoragePolicySelector> result = std::make_shared<StoragePolicySelector>(config, config_prefix, disks);
|
std::shared_ptr<StoragePolicySelector> result = std::make_shared<StoragePolicySelector>(config, config_prefix, disks);
|
||||||
|
|
||||||
constexpr auto default_storage_policy_name = "default";
|
/// First pass, check.
|
||||||
|
|
||||||
for (const auto & [name, policy] : policies)
|
for (const auto & [name, policy] : policies)
|
||||||
{
|
{
|
||||||
if (name != default_storage_policy_name && result->policies.count(name) == 0)
|
if (result->policies.count(name) == 0)
|
||||||
throw Exception("Storage policy " + backQuote(name) + " is missing in new configuration", ErrorCodes::BAD_ARGUMENTS);
|
throw Exception("Storage policy " + backQuote(name) + " is missing in new configuration", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
policy->checkCompatibleWith(result->policies[name]);
|
policy->checkCompatibleWith(result->policies[name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Second pass, load.
|
||||||
|
for (const auto & [name, policy] : policies)
|
||||||
|
{
|
||||||
|
result->policies[name] = std::make_shared<StoragePolicy>(*policy, config, config_prefix + "." + name, disks);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +362,7 @@ StoragePolicyPtr StoragePolicySelector::get(const String & name) const
|
|||||||
{
|
{
|
||||||
auto it = policies.find(name);
|
auto it = policies.find(name);
|
||||||
if (it == policies.end())
|
if (it == policies.end())
|
||||||
throw Exception("Unknown StoragePolicy " + name, ErrorCodes::UNKNOWN_POLICY);
|
throw Exception("Unknown storage policy " + backQuote(name), ErrorCodes::UNKNOWN_POLICY);
|
||||||
|
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
#include <Poco/Util/AbstractConfiguration.h>
|
#include <Poco/Util/AbstractConfiguration.h>
|
||||||
@ -36,6 +37,13 @@ public:
|
|||||||
|
|
||||||
StoragePolicy(String name_, Volumes volumes_, double move_factor_);
|
StoragePolicy(String name_, Volumes volumes_, double move_factor_);
|
||||||
|
|
||||||
|
StoragePolicy(
|
||||||
|
const StoragePolicy & storage_policy,
|
||||||
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
DiskSelectorPtr disks
|
||||||
|
);
|
||||||
|
|
||||||
bool isDefaultPolicy() const;
|
bool isDefaultPolicy() const;
|
||||||
|
|
||||||
/// Returns disks ordered by volumes priority
|
/// Returns disks ordered by volumes priority
|
||||||
@ -72,16 +80,10 @@ public:
|
|||||||
/// which should be kept with help of background moves
|
/// which should be kept with help of background moves
|
||||||
double getMoveFactor() const { return move_factor; }
|
double getMoveFactor() const { return move_factor; }
|
||||||
|
|
||||||
/// Get volume by index from storage_policy
|
/// Get volume by index.
|
||||||
VolumePtr getVolume(size_t i) const { return (i < volumes_names.size() ? volumes[i] : VolumePtr()); }
|
VolumePtr getVolume(size_t index) const;
|
||||||
|
|
||||||
VolumePtr getVolumeByName(const String & volume_name) const
|
VolumePtr getVolumeByName(const String & volume_name) const;
|
||||||
{
|
|
||||||
auto it = volumes_names.find(volume_name);
|
|
||||||
if (it == volumes_names.end())
|
|
||||||
return {};
|
|
||||||
return getVolume(it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if storage policy can be replaced by another one.
|
/// Checks if storage policy can be replaced by another one.
|
||||||
void checkCompatibleWith(const StoragePolicyPtr & new_storage_policy) const;
|
void checkCompatibleWith(const StoragePolicyPtr & new_storage_policy) const;
|
||||||
@ -89,12 +91,15 @@ public:
|
|||||||
private:
|
private:
|
||||||
Volumes volumes;
|
Volumes volumes;
|
||||||
const String name;
|
const String name;
|
||||||
std::map<String, size_t> volumes_names;
|
std::unordered_map<String, size_t> volume_index_by_volume_name;
|
||||||
|
std::unordered_map<String, size_t> volume_index_by_disk_name;
|
||||||
|
|
||||||
/// move_factor from interval [0., 1.]
|
/// move_factor from interval [0., 1.]
|
||||||
/// We move something if disk from this policy
|
/// We move something if disk from this policy
|
||||||
/// filled more than total_size * move_factor
|
/// filled more than total_size * move_factor
|
||||||
double move_factor = 0.1; /// by default move factor is 10%
|
double move_factor = 0.1; /// by default move factor is 10%
|
||||||
|
|
||||||
|
void buildVolumeIndices();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,11 +56,23 @@ VolumeJBOD::VolumeJBOD(
|
|||||||
|
|
||||||
/// Default value is 'true' due to backward compatibility.
|
/// Default value is 'true' due to backward compatibility.
|
||||||
perform_ttl_move_on_insert = config.getBool(config_prefix + ".perform_ttl_move_on_insert", true);
|
perform_ttl_move_on_insert = config.getBool(config_prefix + ".perform_ttl_move_on_insert", true);
|
||||||
|
|
||||||
|
are_merges_avoided = config.getBool(config_prefix + ".prefer_not_to_merge", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
VolumeJBOD::VolumeJBOD(const VolumeJBOD & volume_jbod,
|
||||||
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
DiskSelectorPtr disk_selector)
|
||||||
|
: VolumeJBOD(volume_jbod.name, config, config_prefix, disk_selector)
|
||||||
|
{
|
||||||
|
are_merges_avoided_user_override = volume_jbod.are_merges_avoided_user_override.load(std::memory_order_relaxed);
|
||||||
|
last_used = volume_jbod.last_used.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
DiskPtr VolumeJBOD::getDisk(size_t /* index */) const
|
DiskPtr VolumeJBOD::getDisk(size_t /* index */) const
|
||||||
{
|
{
|
||||||
size_t start_from = last_used.fetch_add(1u, std::memory_order_relaxed);
|
size_t start_from = last_used.fetch_add(1u, std::memory_order_acq_rel);
|
||||||
size_t index = start_from % disks.size();
|
size_t index = start_from % disks.size();
|
||||||
return disks[index];
|
return disks[index];
|
||||||
}
|
}
|
||||||
@ -73,7 +85,7 @@ ReservationPtr VolumeJBOD::reserve(UInt64 bytes)
|
|||||||
if (max_data_part_size != 0 && bytes > max_data_part_size)
|
if (max_data_part_size != 0 && bytes > max_data_part_size)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
size_t start_from = last_used.fetch_add(1u, std::memory_order_relaxed);
|
size_t start_from = last_used.fetch_add(1u, std::memory_order_acq_rel);
|
||||||
size_t disks_num = disks.size();
|
size_t disks_num = disks.size();
|
||||||
for (size_t i = 0; i < disks_num; ++i)
|
for (size_t i = 0; i < disks_num; ++i)
|
||||||
{
|
{
|
||||||
@ -87,4 +99,19 @@ ReservationPtr VolumeJBOD::reserve(UInt64 bytes)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VolumeJBOD::areMergesAvoided() const
|
||||||
|
{
|
||||||
|
auto are_merges_avoided_user_override_value = are_merges_avoided_user_override.load(std::memory_order_acquire);
|
||||||
|
if (are_merges_avoided_user_override_value)
|
||||||
|
return *are_merges_avoided_user_override_value;
|
||||||
|
else
|
||||||
|
return are_merges_avoided;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VolumeJBOD::setAvoidMergesUserOverride(bool avoid)
|
||||||
|
{
|
||||||
|
are_merges_avoided_user_override.store(avoid, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <Disks/IVolume.h>
|
#include <Disks/IVolume.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
|
class VolumeJBOD;
|
||||||
|
|
||||||
|
using VolumeJBODPtr = std::shared_ptr<VolumeJBOD>;
|
||||||
|
using VolumesJBOD = std::vector<VolumeJBODPtr>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements something similar to JBOD (https://en.wikipedia.org/wiki/Non-RAID_drive_architectures#JBOD).
|
* Implements something similar to JBOD (https://en.wikipedia.org/wiki/Non-RAID_drive_architectures#JBOD).
|
||||||
* When MergeTree engine wants to write part — it requests VolumeJBOD to reserve space on the next available
|
* When MergeTree engine wants to write part — it requests VolumeJBOD to reserve space on the next available
|
||||||
@ -13,8 +22,9 @@ namespace DB
|
|||||||
class VolumeJBOD : public IVolume
|
class VolumeJBOD : public IVolume
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VolumeJBOD(String name_, Disks disks_, UInt64 max_data_part_size_)
|
VolumeJBOD(String name_, Disks disks_, UInt64 max_data_part_size_, bool are_merges_avoided_)
|
||||||
: IVolume(name_, disks_, max_data_part_size_)
|
: IVolume(name_, disks_, max_data_part_size_)
|
||||||
|
, are_merges_avoided(are_merges_avoided_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +35,13 @@ public:
|
|||||||
DiskSelectorPtr disk_selector
|
DiskSelectorPtr disk_selector
|
||||||
);
|
);
|
||||||
|
|
||||||
|
VolumeJBOD(
|
||||||
|
const VolumeJBOD & volume_jbod,
|
||||||
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
DiskSelectorPtr disk_selector
|
||||||
|
);
|
||||||
|
|
||||||
VolumeType getType() const override { return VolumeType::JBOD; }
|
VolumeType getType() const override { return VolumeType::JBOD; }
|
||||||
|
|
||||||
/// Always returns next disk (round-robin), ignores argument.
|
/// Always returns next disk (round-robin), ignores argument.
|
||||||
@ -38,11 +55,19 @@ public:
|
|||||||
/// Returns valid reservation or nullptr if there is no space left on any disk.
|
/// Returns valid reservation or nullptr if there is no space left on any disk.
|
||||||
ReservationPtr reserve(UInt64 bytes) override;
|
ReservationPtr reserve(UInt64 bytes) override;
|
||||||
|
|
||||||
|
bool areMergesAvoided() const override;
|
||||||
|
|
||||||
|
void setAvoidMergesUserOverride(bool avoid) override;
|
||||||
|
|
||||||
|
/// True if parts on this volume participate in merges according to configuration.
|
||||||
|
bool are_merges_avoided = true;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Index of last used disk.
|
||||||
mutable std::atomic<size_t> last_used = 0;
|
mutable std::atomic<size_t> last_used = 0;
|
||||||
|
|
||||||
|
/// True if parts on this volume participate in merges according to START/STOP MERGES ON VOLUME.
|
||||||
|
std::atomic<std::optional<bool>> are_merges_avoided_user_override{std::nullopt};
|
||||||
};
|
};
|
||||||
|
|
||||||
using VolumeJBODPtr = std::shared_ptr<VolumeJBOD>;
|
|
||||||
using VolumesJBOD = std::vector<VolumeJBODPtr>;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,23 @@
|
|||||||
#include <Disks/createVolume.h>
|
#include <Disks/createVolume.h>
|
||||||
#include <Disks/VolumeJBOD.h>
|
#include <Disks/VolumeJBOD.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Volume which reserserves space on each underlying disk.
|
class VolumeRAID1;
|
||||||
|
|
||||||
|
using VolumeRAID1Ptr = std::shared_ptr<VolumeRAID1>;
|
||||||
|
|
||||||
|
/// Volume which reserves space on each underlying disk.
|
||||||
///
|
///
|
||||||
/// NOTE: Just interface implementation, doesn't used in codebase,
|
/// NOTE: Just interface implementation, doesn't used in codebase,
|
||||||
/// also not available for user.
|
/// also not available for user.
|
||||||
class VolumeRAID1 : public VolumeJBOD
|
class VolumeRAID1 : public VolumeJBOD
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VolumeRAID1(String name_, Disks disks_, UInt64 max_data_part_size_)
|
VolumeRAID1(String name_, Disks disks_, UInt64 max_data_part_size_, bool are_merges_avoided_in_config_)
|
||||||
: VolumeJBOD(name_, disks_, max_data_part_size_)
|
: VolumeJBOD(name_, disks_, max_data_part_size_, are_merges_avoided_in_config_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,11 +32,18 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VolumeRAID1(
|
||||||
|
VolumeRAID1 & volume_raid1,
|
||||||
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
DiskSelectorPtr disk_selector)
|
||||||
|
: VolumeJBOD(volume_raid1, config, config_prefix, disk_selector)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
VolumeType getType() const override { return VolumeType::RAID1; }
|
VolumeType getType() const override { return VolumeType::RAID1; }
|
||||||
|
|
||||||
ReservationPtr reserve(UInt64 bytes) override;
|
ReservationPtr reserve(UInt64 bytes) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
using VolumeRAID1Ptr = std::shared_ptr<VolumeRAID1>;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ namespace DB
|
|||||||
namespace ErrorCodes
|
namespace ErrorCodes
|
||||||
{
|
{
|
||||||
extern const int UNKNOWN_RAID_TYPE;
|
extern const int UNKNOWN_RAID_TYPE;
|
||||||
|
extern const int INVALID_RAID_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
VolumePtr createVolumeFromReservation(const ReservationPtr & reservation, VolumePtr other_volume)
|
VolumePtr createVolumeFromReservation(const ReservationPtr & reservation, VolumePtr other_volume)
|
||||||
@ -20,12 +21,12 @@ VolumePtr createVolumeFromReservation(const ReservationPtr & reservation, Volume
|
|||||||
{
|
{
|
||||||
/// Since reservation on JBOD chooses one of disks and makes reservation there, volume
|
/// Since reservation on JBOD chooses one of disks and makes reservation there, volume
|
||||||
/// for such type of reservation will be with one disk.
|
/// for such type of reservation will be with one disk.
|
||||||
return std::make_shared<SingleDiskVolume>(other_volume->getName(), reservation->getDisk());
|
return std::make_shared<SingleDiskVolume>(other_volume->getName(), reservation->getDisk(), other_volume->max_data_part_size);
|
||||||
}
|
}
|
||||||
if (other_volume->getType() == VolumeType::RAID1)
|
if (other_volume->getType() == VolumeType::RAID1)
|
||||||
{
|
{
|
||||||
auto volume = std::dynamic_pointer_cast<VolumeRAID1>(other_volume);
|
auto volume = std::dynamic_pointer_cast<VolumeRAID1>(other_volume);
|
||||||
return std::make_shared<VolumeRAID1>(volume->getName(), reservation->getDisks(), volume->max_data_part_size);
|
return std::make_shared<VolumeRAID1>(volume->getName(), reservation->getDisks(), volume->max_data_part_size, volume->are_merges_avoided);
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -37,17 +38,31 @@ VolumePtr createVolumeFromConfig(
|
|||||||
DiskSelectorPtr disk_selector
|
DiskSelectorPtr disk_selector
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
auto has_raid_type = config.has(config_prefix + ".raid_type");
|
String raid_type = config.getString(config_prefix + ".raid_type", "JBOD");
|
||||||
if (!has_raid_type)
|
|
||||||
{
|
|
||||||
return std::make_shared<VolumeJBOD>(name, config, config_prefix, disk_selector);
|
|
||||||
}
|
|
||||||
String raid_type = config.getString(config_prefix + ".raid_type");
|
|
||||||
if (raid_type == "JBOD")
|
if (raid_type == "JBOD")
|
||||||
{
|
{
|
||||||
return std::make_shared<VolumeJBOD>(name, config, config_prefix, disk_selector);
|
return std::make_shared<VolumeJBOD>(name, config, config_prefix, disk_selector);
|
||||||
}
|
}
|
||||||
throw Exception("Unknown raid type '" + raid_type + "'", ErrorCodes::UNKNOWN_RAID_TYPE);
|
throw Exception("Unknown RAID type '" + raid_type + "'", ErrorCodes::UNKNOWN_RAID_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
VolumePtr updateVolumeFromConfig(
|
||||||
|
VolumePtr volume,
|
||||||
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
DiskSelectorPtr & disk_selector
|
||||||
|
)
|
||||||
|
{
|
||||||
|
String raid_type = config.getString(config_prefix + ".raid_type", "JBOD");
|
||||||
|
if (raid_type == "JBOD")
|
||||||
|
{
|
||||||
|
VolumeJBODPtr volume_jbod = std::dynamic_pointer_cast<VolumeJBOD>(volume);
|
||||||
|
if (!volume_jbod)
|
||||||
|
throw Exception("Invalid RAID type '" + raid_type + "', shall be JBOD", ErrorCodes::INVALID_RAID_TYPE);
|
||||||
|
|
||||||
|
return std::make_shared<VolumeJBOD>(*volume_jbod, config, config_prefix, disk_selector);
|
||||||
|
}
|
||||||
|
throw Exception("Unknown RAID type '" + raid_type + "'", ErrorCodes::UNKNOWN_RAID_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ namespace DB
|
|||||||
{
|
{
|
||||||
|
|
||||||
VolumePtr createVolumeFromReservation(const ReservationPtr & reservation, VolumePtr other_volume);
|
VolumePtr createVolumeFromReservation(const ReservationPtr & reservation, VolumePtr other_volume);
|
||||||
|
|
||||||
VolumePtr createVolumeFromConfig(
|
VolumePtr createVolumeFromConfig(
|
||||||
String name_,
|
String name_,
|
||||||
const Poco::Util::AbstractConfiguration & config,
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
@ -13,4 +14,11 @@ VolumePtr createVolumeFromConfig(
|
|||||||
DiskSelectorPtr disk_selector
|
DiskSelectorPtr disk_selector
|
||||||
);
|
);
|
||||||
|
|
||||||
|
VolumePtr updateVolumeFromConfig(
|
||||||
|
VolumePtr volume,
|
||||||
|
const Poco::Util::AbstractConfiguration & config,
|
||||||
|
const String & config_prefix,
|
||||||
|
DiskSelectorPtr & disk_selector
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ static FormatSettings getInputFormatSetting(const Settings & settings, const Con
|
|||||||
format_settings.csv.allow_double_quotes = settings.format_csv_allow_double_quotes;
|
format_settings.csv.allow_double_quotes = settings.format_csv_allow_double_quotes;
|
||||||
format_settings.csv.unquoted_null_literal_as_null = settings.input_format_csv_unquoted_null_literal_as_null;
|
format_settings.csv.unquoted_null_literal_as_null = settings.input_format_csv_unquoted_null_literal_as_null;
|
||||||
format_settings.csv.empty_as_default = settings.input_format_defaults_for_omitted_fields;
|
format_settings.csv.empty_as_default = settings.input_format_defaults_for_omitted_fields;
|
||||||
|
format_settings.csv.input_format_enum_as_number = settings.input_format_csv_enum_as_number;
|
||||||
format_settings.null_as_default = settings.input_format_null_as_default;
|
format_settings.null_as_default = settings.input_format_null_as_default;
|
||||||
format_settings.values.interpret_expressions = settings.input_format_values_interpret_expressions;
|
format_settings.values.interpret_expressions = settings.input_format_values_interpret_expressions;
|
||||||
format_settings.values.deduce_templates_of_expressions = settings.input_format_values_deduce_templates_of_expressions;
|
format_settings.values.deduce_templates_of_expressions = settings.input_format_values_deduce_templates_of_expressions;
|
||||||
@ -63,6 +64,7 @@ static FormatSettings getInputFormatSetting(const Settings & settings, const Con
|
|||||||
format_settings.template_settings.row_format = settings.format_template_row;
|
format_settings.template_settings.row_format = settings.format_template_row;
|
||||||
format_settings.template_settings.row_between_delimiter = settings.format_template_rows_between_delimiter;
|
format_settings.template_settings.row_between_delimiter = settings.format_template_rows_between_delimiter;
|
||||||
format_settings.tsv.empty_as_default = settings.input_format_tsv_empty_as_default;
|
format_settings.tsv.empty_as_default = settings.input_format_tsv_empty_as_default;
|
||||||
|
format_settings.tsv.input_format_enum_as_number = settings.input_format_tsv_enum_as_number;
|
||||||
format_settings.schema.format_schema = settings.format_schema;
|
format_settings.schema.format_schema = settings.format_schema;
|
||||||
format_settings.schema.format_schema_path = context.getFormatSchemaPath();
|
format_settings.schema.format_schema_path = context.getFormatSchemaPath();
|
||||||
format_settings.schema.is_server = context.hasGlobalContext() && (context.getGlobalContext().getApplicationType() == Context::ApplicationType::SERVER);
|
format_settings.schema.is_server = context.hasGlobalContext() && (context.getGlobalContext().getApplicationType() == Context::ApplicationType::SERVER);
|
||||||
|
@ -34,6 +34,7 @@ struct FormatSettings
|
|||||||
bool unquoted_null_literal_as_null = false;
|
bool unquoted_null_literal_as_null = false;
|
||||||
bool empty_as_default = false;
|
bool empty_as_default = false;
|
||||||
bool crlf_end_of_line = false;
|
bool crlf_end_of_line = false;
|
||||||
|
bool input_format_enum_as_number = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
CSV csv;
|
CSV csv;
|
||||||
@ -81,6 +82,7 @@ struct FormatSettings
|
|||||||
bool empty_as_default = false;
|
bool empty_as_default = false;
|
||||||
bool crlf_end_of_line = false;
|
bool crlf_end_of_line = false;
|
||||||
String null_representation = "\\N";
|
String null_representation = "\\N";
|
||||||
|
bool input_format_enum_as_number = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
TSV tsv;
|
TSV tsv;
|
||||||
|
@ -137,7 +137,7 @@ void validateArgumentsImpl(const IFunction & func,
|
|||||||
const auto & arg = arguments[i + argument_offset];
|
const auto & arg = arguments[i + argument_offset];
|
||||||
const auto descriptor = descriptors[i];
|
const auto descriptor = descriptors[i];
|
||||||
if (int error_code = descriptor.isValid(arg.type, arg.column); error_code != 0)
|
if (int error_code = descriptor.isValid(arg.type, arg.column); error_code != 0)
|
||||||
throw Exception("Illegal type of argument #" + std::to_string(i)
|
throw Exception("Illegal type of argument #" + std::to_string(argument_offset + i + 1) // +1 is for human-friendly 1-based indexing
|
||||||
+ (descriptor.argument_name ? " '" + std::string(descriptor.argument_name) + "'" : String{})
|
+ (descriptor.argument_name ? " '" + std::string(descriptor.argument_name) + "'" : String{})
|
||||||
+ " of function " + func.getName()
|
+ " of function " + func.getName()
|
||||||
+ (descriptor.expected_type_description ? String(", expected ") + descriptor.expected_type_description : String{})
|
+ (descriptor.expected_type_description ? String(", expected ") + descriptor.expected_type_description : String{})
|
||||||
|
63
src/Functions/FunctionsAES.cpp
Normal file
63
src/Functions/FunctionsAES.cpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#include <Functions/FunctionsAES.h>
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
namespace ErrorCodes
|
||||||
|
{
|
||||||
|
extern const int OPENSSL_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace OpenSSLDetails
|
||||||
|
{
|
||||||
|
void onError(std::string error_message)
|
||||||
|
{
|
||||||
|
error_message += ". OpenSSL error code: " + std::to_string(ERR_get_error());
|
||||||
|
throw DB::Exception(error_message, DB::ErrorCodes::OPENSSL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringRef foldEncryptionKeyInMySQLCompatitableMode(size_t cipher_key_size, const StringRef & key, std::array<char, EVP_MAX_KEY_LENGTH> & folded_key)
|
||||||
|
{
|
||||||
|
assert(cipher_key_size <= EVP_MAX_KEY_LENGTH);
|
||||||
|
memcpy(folded_key.data(), key.data, cipher_key_size);
|
||||||
|
|
||||||
|
for (size_t i = cipher_key_size; i < key.size; ++i)
|
||||||
|
{
|
||||||
|
folded_key[i % cipher_key_size] ^= key.data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringRef(folded_key.data(), cipher_key_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVP_CIPHER * getCipherByName(const StringRef & cipher_name)
|
||||||
|
{
|
||||||
|
const auto * evp_cipher = EVP_get_cipherbyname(cipher_name.data);
|
||||||
|
if (evp_cipher == nullptr)
|
||||||
|
{
|
||||||
|
// For some reasons following ciphers can't be found by name.
|
||||||
|
if (cipher_name == "aes-128-cfb128")
|
||||||
|
evp_cipher = EVP_aes_128_cfb128();
|
||||||
|
else if (cipher_name == "aes-192-cfb128")
|
||||||
|
evp_cipher = EVP_aes_192_cfb128();
|
||||||
|
else if (cipher_name == "aes-256-cfb128")
|
||||||
|
evp_cipher = EVP_aes_256_cfb128();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: cipher obtained not via EVP_CIPHER_fetch() would cause extra work on each context reset
|
||||||
|
// with EVP_CIPHER_CTX_reset() or EVP_EncryptInit_ex(), but using EVP_CIPHER_fetch()
|
||||||
|
// causes data race, so we stick to the slower but safer alternative here.
|
||||||
|
return evp_cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
693
src/Functions/FunctionsAES.h
Normal file
693
src/Functions/FunctionsAES.h
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined(ARCADIA_BUILD)
|
||||||
|
# include <Common/config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
#include <DataTypes/DataTypeString.h>
|
||||||
|
#include <Columns/ColumnString.h>
|
||||||
|
#include <Functions/IFunction.h>
|
||||||
|
#include <Functions/FunctionFactory.h>
|
||||||
|
#include <Functions/FunctionHelpers.h>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/engine.h>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <functional>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
namespace ErrorCodes
|
||||||
|
{
|
||||||
|
extern const int BAD_ARGUMENTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace OpenSSLDetails
|
||||||
|
{
|
||||||
|
[[noreturn]] void onError(std::string error_message);
|
||||||
|
StringRef foldEncryptionKeyInMySQLCompatitableMode(size_t cipher_key_size, const StringRef & key, std::array<char, EVP_MAX_KEY_LENGTH> & folded_key);
|
||||||
|
|
||||||
|
const EVP_CIPHER * getCipherByName(const StringRef & name);
|
||||||
|
|
||||||
|
enum class CompatibilityMode
|
||||||
|
{
|
||||||
|
MySQL,
|
||||||
|
OpenSSL
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CipherMode
|
||||||
|
{
|
||||||
|
MySQLCompatibility, // with key folding
|
||||||
|
OpenSSLCompatibility, // just as regular openssl's enc application does (AEAD modes, like GCM and CCM are not supported)
|
||||||
|
RFC5116_AEAD_AES_GCM // AEAD GCM with custom IV length and tag (HMAC) appended to the ciphertext, see https://tools.ietf.org/html/rfc5116#section-5.1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <CipherMode mode>
|
||||||
|
struct KeyHolder
|
||||||
|
{
|
||||||
|
inline StringRef setKey(size_t cipher_key_size, const StringRef & key) const
|
||||||
|
{
|
||||||
|
if (key.size != cipher_key_size)
|
||||||
|
throw DB::Exception(fmt::format("Invalid key size: {} expected {}", key.size, cipher_key_size),
|
||||||
|
DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct KeyHolder<CipherMode::MySQLCompatibility>
|
||||||
|
{
|
||||||
|
inline StringRef setKey(size_t cipher_key_size, const StringRef & key)
|
||||||
|
{
|
||||||
|
if (key.size < cipher_key_size)
|
||||||
|
throw DB::Exception(fmt::format("Invalid key size: {} expected {}", key.size, cipher_key_size),
|
||||||
|
DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
// MySQL does something fancy with the keys that are too long,
|
||||||
|
// ruining compatibility with OpenSSL and not improving security.
|
||||||
|
// But we have to do the same to be compatitable with MySQL.
|
||||||
|
// see https://github.com/mysql/mysql-server/blob/8.0/router/src/harness/src/my_aes_openssl.cc#L71
|
||||||
|
// (my_aes_create_key function)
|
||||||
|
return foldEncryptionKeyInMySQLCompatitableMode(cipher_key_size, key, folded_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
~KeyHolder()
|
||||||
|
{
|
||||||
|
OPENSSL_cleanse(folded_key.data(), folded_key.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<char, EVP_MAX_KEY_LENGTH> folded_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <CompatibilityMode compatibility_mode>
|
||||||
|
inline void validateCipherMode(const EVP_CIPHER * evp_cipher)
|
||||||
|
{
|
||||||
|
if constexpr (compatibility_mode == CompatibilityMode::MySQL)
|
||||||
|
{
|
||||||
|
switch (EVP_CIPHER_mode(evp_cipher))
|
||||||
|
{
|
||||||
|
case EVP_CIPH_ECB_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_CBC_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_CFB_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_OFB_MODE:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (compatibility_mode == CompatibilityMode::OpenSSL)
|
||||||
|
{
|
||||||
|
switch (EVP_CIPHER_mode(evp_cipher))
|
||||||
|
{
|
||||||
|
case EVP_CIPH_ECB_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_CBC_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_CFB_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_OFB_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_CTR_MODE: [[fallthrough]];
|
||||||
|
case EVP_CIPH_GCM_MODE:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw DB::Exception("Unsupported cipher mode " + std::string(EVP_CIPHER_name(evp_cipher)), DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <CipherMode mode>
|
||||||
|
inline void validateIV(const StringRef & iv_value, const size_t cipher_iv_size)
|
||||||
|
{
|
||||||
|
// In MySQL mode we don't care if IV is longer than expected, only if shorter.
|
||||||
|
if ((mode == CipherMode::MySQLCompatibility && iv_value.size != 0 && iv_value.size < cipher_iv_size)
|
||||||
|
|| (mode == CipherMode::OpenSSLCompatibility && iv_value.size != 0 && iv_value.size != cipher_iv_size))
|
||||||
|
throw DB::Exception(fmt::format("Invalid IV size: {} expected {}", iv_value.size, cipher_iv_size),
|
||||||
|
DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
template <typename Impl>
|
||||||
|
class FunctionEncrypt : public IFunction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr OpenSSLDetails::CompatibilityMode compatibility_mode = Impl::compatibility_mode;
|
||||||
|
static constexpr auto name = Impl::name;
|
||||||
|
static FunctionPtr create(const Context &) { return std::make_shared<FunctionEncrypt>(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
using CipherMode = OpenSSLDetails::CipherMode;
|
||||||
|
|
||||||
|
String getName() const override { return name; }
|
||||||
|
bool isVariadic() const override { return true; }
|
||||||
|
size_t getNumberOfArguments() const override { return 0; }
|
||||||
|
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0}; }
|
||||||
|
bool useDefaultImplementationForConstants() const override { return true; }
|
||||||
|
|
||||||
|
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
|
||||||
|
{
|
||||||
|
auto optional_args = FunctionArgumentDescriptors{
|
||||||
|
{"IV", isStringOrFixedString, nullptr, "Initialization vector binary string"},
|
||||||
|
};
|
||||||
|
|
||||||
|
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::OpenSSL)
|
||||||
|
{
|
||||||
|
optional_args.emplace_back(FunctionArgumentDescriptor{
|
||||||
|
"AAD", isStringOrFixedString, nullptr, "Additional authenticated data binary string for GCM mode"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validateFunctionArgumentTypes(*this, arguments,
|
||||||
|
FunctionArgumentDescriptors{
|
||||||
|
{"mode", isStringOrFixedString, isColumnConst, "encryption mode string"},
|
||||||
|
{"input", nullptr, nullptr, "plaintext"},
|
||||||
|
{"key", isStringOrFixedString, nullptr, "encryption key binary string"},
|
||||||
|
},
|
||||||
|
optional_args
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::make_shared<DataTypeString>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeImpl(DB::ColumnsWithTypeAndName & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) const override
|
||||||
|
{
|
||||||
|
using namespace OpenSSLDetails;
|
||||||
|
|
||||||
|
const auto mode = block[arguments[0]].column->getDataAt(0);
|
||||||
|
|
||||||
|
if (mode.size == 0 || !std::string_view(mode).starts_with("aes-"))
|
||||||
|
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
auto evp_cipher = getCipherByName(mode);
|
||||||
|
if (evp_cipher == nullptr)
|
||||||
|
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
const auto cipher_mode = EVP_CIPHER_mode(evp_cipher);
|
||||||
|
|
||||||
|
const auto input_column = block[arguments[1]].column;
|
||||||
|
const auto key_column = block[arguments[2]].column;
|
||||||
|
|
||||||
|
OpenSSLDetails::validateCipherMode<compatibility_mode>(evp_cipher);
|
||||||
|
|
||||||
|
ColumnPtr result_column;
|
||||||
|
if (arguments.size() <= 3)
|
||||||
|
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto iv_column = block[arguments[3]].column;
|
||||||
|
if (compatibility_mode != OpenSSLDetails::CompatibilityMode::MySQL && EVP_CIPHER_iv_length(evp_cipher) == 0)
|
||||||
|
throw Exception(mode.toString() + " does not support IV", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
if (arguments.size() <= 4)
|
||||||
|
{
|
||||||
|
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cipher_mode != EVP_CIPH_GCM_MODE)
|
||||||
|
throw Exception("AAD can be only set for GCM-mode", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
const auto aad_column = block[arguments[4]].column;
|
||||||
|
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block[result].column = std::move(result_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
|
||||||
|
static ColumnPtr doEncrypt(const EVP_CIPHER * evp_cipher,
|
||||||
|
size_t input_rows_count,
|
||||||
|
const InputColumnType & input_column,
|
||||||
|
const KeyColumnType & key_column,
|
||||||
|
const IvColumnType & iv_column,
|
||||||
|
const AadColumnType & aad_column)
|
||||||
|
{
|
||||||
|
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL)
|
||||||
|
{
|
||||||
|
return doEncryptImpl<CipherMode::MySQLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (EVP_CIPHER_mode(evp_cipher) == EVP_CIPH_GCM_MODE)
|
||||||
|
{
|
||||||
|
return doEncryptImpl<CipherMode::RFC5116_AEAD_AES_GCM>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return doEncryptImpl<CipherMode::OpenSSLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <CipherMode mode, typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
|
||||||
|
static ColumnPtr doEncryptImpl(const EVP_CIPHER * evp_cipher,
|
||||||
|
size_t input_rows_count,
|
||||||
|
const InputColumnType & input_column,
|
||||||
|
const KeyColumnType & key_column,
|
||||||
|
[[maybe_unused]] const IvColumnType & iv_column,
|
||||||
|
[[maybe_unused]] const AadColumnType & aad_column)
|
||||||
|
{
|
||||||
|
using namespace OpenSSLDetails;
|
||||||
|
|
||||||
|
auto evp_ctx_ptr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
|
||||||
|
auto evp_ctx = evp_ctx_ptr.get();
|
||||||
|
|
||||||
|
const auto block_size = static_cast<size_t>(EVP_CIPHER_block_size(evp_cipher));
|
||||||
|
const auto key_size = static_cast<size_t>(EVP_CIPHER_key_length(evp_cipher));
|
||||||
|
[[maybe_unused]] const auto iv_size = static_cast<size_t>(EVP_CIPHER_iv_length(evp_cipher));
|
||||||
|
const auto tag_size = 16; // https://tools.ietf.org/html/rfc5116#section-5.1
|
||||||
|
|
||||||
|
auto encrypted_result_column = ColumnString::create();
|
||||||
|
auto & encrypted_result_column_data = encrypted_result_column->getChars();
|
||||||
|
auto & encrypted_result_column_offsets = encrypted_result_column->getOffsets();
|
||||||
|
|
||||||
|
{
|
||||||
|
size_t resulting_size = 0;
|
||||||
|
// for modes with block_size > 1, plaintext is padded up to a block_size,
|
||||||
|
// which may result in allocating to much for block_size = 1.
|
||||||
|
// That may lead later to reading unallocated data from underlying PaddedPODArray
|
||||||
|
// due to assumption that it is safe to read up to 15 bytes past end.
|
||||||
|
const auto pad_to_next_block = block_size == 1 ? 0 : 1;
|
||||||
|
for (size_t r = 0; r < input_rows_count; ++r)
|
||||||
|
{
|
||||||
|
resulting_size += (input_column->getDataAt(r).size / block_size + pad_to_next_block) * block_size + 1;
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
|
||||||
|
resulting_size += tag_size;
|
||||||
|
}
|
||||||
|
#if defined(MEMORY_SANITIZER)
|
||||||
|
encrypted_result_column_data.resize_fill(resulting_size, 0xFF);
|
||||||
|
#else
|
||||||
|
encrypted_result_column_data.resize(resulting_size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto encrypted = encrypted_result_column_data.data();
|
||||||
|
|
||||||
|
KeyHolder<mode> key_holder;
|
||||||
|
|
||||||
|
for (size_t r = 0; r < input_rows_count; ++r)
|
||||||
|
{
|
||||||
|
const auto key_value = key_holder.setKey(key_size, key_column->getDataAt(r));
|
||||||
|
auto iv_value = StringRef{};
|
||||||
|
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<IvColumnType>>)
|
||||||
|
{
|
||||||
|
iv_value = iv_column->getDataAt(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto input_value = input_column->getDataAt(r);
|
||||||
|
auto aad_value = StringRef{};
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM && !std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
|
||||||
|
{
|
||||||
|
aad_value = aad_column->getDataAt(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (mode != CipherMode::MySQLCompatibility)
|
||||||
|
{
|
||||||
|
// in GCM mode IV can be of arbitrary size (>0), IV is optional for other modes.
|
||||||
|
if (mode == CipherMode::RFC5116_AEAD_AES_GCM && iv_value.size == 0)
|
||||||
|
{
|
||||||
|
throw Exception("Invalid IV size " + std::to_string(iv_value.size) + " != expected size " + std::to_string(iv_size),
|
||||||
|
DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode != CipherMode::RFC5116_AEAD_AES_GCM && key_value.size != key_size)
|
||||||
|
{
|
||||||
|
throw Exception("Invalid key size " + std::to_string(key_value.size) + " != expected size " + std::to_string(key_size),
|
||||||
|
DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid extra work on empty ciphertext/plaintext for some ciphers
|
||||||
|
if (!(input_value.size == 0 && block_size == 1 && mode != CipherMode::RFC5116_AEAD_AES_GCM))
|
||||||
|
{
|
||||||
|
// 1: Init CTX
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
|
||||||
|
{
|
||||||
|
// 1.a.1: Init CTX with custom IV length and optionally with AAD
|
||||||
|
if (EVP_EncryptInit_ex(evp_ctx, evp_cipher, nullptr, nullptr, nullptr) != 1)
|
||||||
|
onError("Failed to initialize encryption context with cipher");
|
||||||
|
|
||||||
|
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_value.size, nullptr) != 1)
|
||||||
|
onError("Failed to set custom IV length to " + std::to_string(iv_value.size));
|
||||||
|
|
||||||
|
if (EVP_EncryptInit_ex(evp_ctx, nullptr, nullptr,
|
||||||
|
reinterpret_cast<const unsigned char*>(key_value.data),
|
||||||
|
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
|
||||||
|
onError("Failed to set key and IV");
|
||||||
|
|
||||||
|
// 1.a.2 Set AAD
|
||||||
|
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
|
||||||
|
{
|
||||||
|
const auto aad_data = aad_column->getDataAt(r);
|
||||||
|
int tmp_len = 0;
|
||||||
|
if (aad_data.size != 0 && EVP_EncryptUpdate(evp_ctx, nullptr, &tmp_len,
|
||||||
|
reinterpret_cast<const unsigned char *>(aad_data.data), aad_data.size) != 1)
|
||||||
|
onError("Failed to set AAD data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 1.b: Init CTX
|
||||||
|
validateIV<mode>(iv_value, iv_size);
|
||||||
|
|
||||||
|
if (EVP_EncryptInit_ex(evp_ctx, evp_cipher, nullptr,
|
||||||
|
reinterpret_cast<const unsigned char*>(key_value.data),
|
||||||
|
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
|
||||||
|
onError("Failed to initialize cipher context");
|
||||||
|
}
|
||||||
|
|
||||||
|
int output_len = 0;
|
||||||
|
// 2: Feed the data to the cipher
|
||||||
|
if (EVP_EncryptUpdate(evp_ctx,
|
||||||
|
reinterpret_cast<unsigned char*>(encrypted), &output_len,
|
||||||
|
reinterpret_cast<const unsigned char*>(input_value.data), static_cast<int>(input_value.size)) != 1)
|
||||||
|
onError("Failed to encrypt");
|
||||||
|
encrypted += output_len;
|
||||||
|
|
||||||
|
// 3: retrieve encrypted data (ciphertext)
|
||||||
|
if (EVP_EncryptFinal_ex(evp_ctx,
|
||||||
|
reinterpret_cast<unsigned char*>(encrypted), &output_len) != 1)
|
||||||
|
onError("Failed to fetch ciphertext");
|
||||||
|
encrypted += output_len;
|
||||||
|
|
||||||
|
// 4: optionally retrieve a tag and append it to the ciphertext (RFC5116):
|
||||||
|
// https://tools.ietf.org/html/rfc5116#section-5.1
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
|
||||||
|
{
|
||||||
|
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_GET_TAG, tag_size, encrypted) != 1)
|
||||||
|
onError("Failed to retrieve GCM tag");
|
||||||
|
encrypted += tag_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*encrypted = '\0';
|
||||||
|
++encrypted;
|
||||||
|
|
||||||
|
encrypted_result_column_offsets.push_back(encrypted - encrypted_result_column_data.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case of block size of 1, we overestimate buffer required for encrypted data, fix it up.
|
||||||
|
if (!encrypted_result_column_offsets.empty() && encrypted_result_column_data.size() > encrypted_result_column_offsets.back())
|
||||||
|
{
|
||||||
|
encrypted_result_column_data.resize(encrypted_result_column_offsets.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted_result_column->validate();
|
||||||
|
return encrypted_result_column;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// AES_decrypt(string, key, block_mode[, init_vector])
|
||||||
|
template <typename Impl>
|
||||||
|
class FunctionDecrypt : public IFunction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr OpenSSLDetails::CompatibilityMode compatibility_mode = Impl::compatibility_mode;
|
||||||
|
static constexpr auto name = Impl::name;
|
||||||
|
static FunctionPtr create(const Context &) { return std::make_shared<FunctionDecrypt>(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
using CipherMode = OpenSSLDetails::CipherMode;
|
||||||
|
|
||||||
|
String getName() const override { return name; }
|
||||||
|
bool isVariadic() const override { return true; }
|
||||||
|
size_t getNumberOfArguments() const override { return 0; }
|
||||||
|
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0}; }
|
||||||
|
bool useDefaultImplementationForConstants() const override { return true; }
|
||||||
|
|
||||||
|
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
|
||||||
|
{
|
||||||
|
auto optional_args = FunctionArgumentDescriptors{
|
||||||
|
{"IV", isStringOrFixedString, nullptr, "Initialization vector binary string"},
|
||||||
|
};
|
||||||
|
|
||||||
|
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::OpenSSL)
|
||||||
|
{
|
||||||
|
optional_args.emplace_back(FunctionArgumentDescriptor{
|
||||||
|
"AAD", isStringOrFixedString, nullptr, "Additional authenticated data binary string for GCM mode"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validateFunctionArgumentTypes(*this, arguments,
|
||||||
|
FunctionArgumentDescriptors{
|
||||||
|
{"mode", isStringOrFixedString, isColumnConst, "decryption mode string"},
|
||||||
|
{"input", nullptr, nullptr, "ciphertext"},
|
||||||
|
{"key", isStringOrFixedString, nullptr, "decryption key binary string"},
|
||||||
|
},
|
||||||
|
optional_args
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::make_shared<DataTypeString>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeImpl(DB::ColumnsWithTypeAndName & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) const override
|
||||||
|
{
|
||||||
|
using namespace OpenSSLDetails;
|
||||||
|
|
||||||
|
const auto mode = block[arguments[0]].column->getDataAt(0);
|
||||||
|
if (mode.size == 0 || !std::string_view(mode).starts_with("aes-"))
|
||||||
|
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
auto evp_cipher = getCipherByName(mode);
|
||||||
|
if (evp_cipher == nullptr)
|
||||||
|
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
OpenSSLDetails::validateCipherMode<compatibility_mode>(evp_cipher);
|
||||||
|
|
||||||
|
const auto input_column = block[arguments[1]].column;
|
||||||
|
const auto key_column = block[arguments[2]].column;
|
||||||
|
|
||||||
|
ColumnPtr result_column;
|
||||||
|
if (arguments.size() <= 3)
|
||||||
|
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto iv_column = block[arguments[3]].column;
|
||||||
|
if (compatibility_mode != OpenSSLDetails::CompatibilityMode::MySQL && EVP_CIPHER_iv_length(evp_cipher) == 0)
|
||||||
|
throw Exception(mode.toString() + " does not support IV", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
if (arguments.size() <= 4)
|
||||||
|
{
|
||||||
|
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (EVP_CIPHER_mode(evp_cipher) != EVP_CIPH_GCM_MODE)
|
||||||
|
throw Exception("AAD can be only set for GCM-mode", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
|
const auto aad_column = block[arguments[4]].column;
|
||||||
|
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block[result].column = std::move(result_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
|
||||||
|
static ColumnPtr doDecrypt(const EVP_CIPHER * evp_cipher,
|
||||||
|
size_t input_rows_count,
|
||||||
|
const InputColumnType & input_column,
|
||||||
|
const KeyColumnType & key_column,
|
||||||
|
const IvColumnType & iv_column,
|
||||||
|
const AadColumnType & aad_column)
|
||||||
|
{
|
||||||
|
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL)
|
||||||
|
{
|
||||||
|
return doDecryptImpl<CipherMode::MySQLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto cipher_mode = EVP_CIPHER_mode(evp_cipher);
|
||||||
|
if (cipher_mode == EVP_CIPH_GCM_MODE)
|
||||||
|
{
|
||||||
|
return doDecryptImpl<CipherMode::RFC5116_AEAD_AES_GCM>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return doDecryptImpl<CipherMode::OpenSSLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <CipherMode mode, typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
|
||||||
|
static ColumnPtr doDecryptImpl(const EVP_CIPHER * evp_cipher,
|
||||||
|
size_t input_rows_count,
|
||||||
|
const InputColumnType & input_column,
|
||||||
|
const KeyColumnType & key_column,
|
||||||
|
[[maybe_unused]] const IvColumnType & iv_column,
|
||||||
|
[[maybe_unused]] const AadColumnType & aad_column)
|
||||||
|
{
|
||||||
|
using namespace OpenSSLDetails;
|
||||||
|
|
||||||
|
auto evp_ctx_ptr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
|
||||||
|
auto evp_ctx = evp_ctx_ptr.get();
|
||||||
|
|
||||||
|
[[maybe_unused]] const auto block_size = static_cast<size_t>(EVP_CIPHER_block_size(evp_cipher));
|
||||||
|
[[maybe_unused]] const auto iv_size = static_cast<size_t>(EVP_CIPHER_iv_length(evp_cipher));
|
||||||
|
const auto key_size = static_cast<size_t>(EVP_CIPHER_key_length(evp_cipher));
|
||||||
|
const auto tag_size = 16; // https://tools.ietf.org/html/rfc5116#section-5.1
|
||||||
|
|
||||||
|
auto decrypted_result_column = ColumnString::create();
|
||||||
|
auto & decrypted_result_column_data = decrypted_result_column->getChars();
|
||||||
|
auto & decrypted_result_column_offsets = decrypted_result_column->getOffsets();
|
||||||
|
|
||||||
|
{
|
||||||
|
size_t resulting_size = 0;
|
||||||
|
for (size_t r = 0; r < input_rows_count; ++r)
|
||||||
|
{
|
||||||
|
resulting_size += input_column->getDataAt(r).size + 1;
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
|
||||||
|
resulting_size -= tag_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(MEMORY_SANITIZER)
|
||||||
|
// Pre-fill result column with values to prevent MSAN from dropping dead on
|
||||||
|
// aes-X-ecb mode with "WARNING: MemorySanitizer: use-of-uninitialized-value".
|
||||||
|
// This is most likely to be caused by the underlying assembler implementation:
|
||||||
|
// see crypto/aes/aesni-x86_64.s, function aesni_ecb_encrypt
|
||||||
|
// which msan seems to fail instrument correctly.
|
||||||
|
decrypted_result_column_data.resize_fill(resulting_size, 0xFF);
|
||||||
|
#else
|
||||||
|
decrypted_result_column_data.resize(resulting_size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
auto decrypted = decrypted_result_column_data.data();
|
||||||
|
|
||||||
|
KeyHolder<mode> key_holder;
|
||||||
|
for (size_t r = 0; r < input_rows_count; ++r)
|
||||||
|
{
|
||||||
|
// 0: prepare key if required
|
||||||
|
auto key_value = key_holder.setKey(key_size, key_column->getDataAt(r));
|
||||||
|
auto iv_value = StringRef{};
|
||||||
|
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<IvColumnType>>)
|
||||||
|
{
|
||||||
|
iv_value = iv_column->getDataAt(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto input_value = input_column->getDataAt(r);
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
|
||||||
|
{
|
||||||
|
// empty plaintext results in empty ciphertext + tag, means there should be atleast tag_size bytes.
|
||||||
|
if (input_value.size < tag_size)
|
||||||
|
throw Exception(fmt::format("Encrypted data is too short: only {} bytes, "
|
||||||
|
"should contain at least {} bytes of a tag.",
|
||||||
|
input_value.size, block_size, tag_size), ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
input_value.size -= tag_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (mode != CipherMode::MySQLCompatibility)
|
||||||
|
{
|
||||||
|
// in GCM mode IV can be of arbitrary size (>0), for other modes IV is optional.
|
||||||
|
if (mode == CipherMode::RFC5116_AEAD_AES_GCM && iv_value.size == 0)
|
||||||
|
{
|
||||||
|
throw Exception("Invalid IV size " + std::to_string(iv_value.size) + " != expected size " + std::to_string(iv_size),
|
||||||
|
DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key_value.size != key_size)
|
||||||
|
{
|
||||||
|
throw Exception("Invalid key size " + std::to_string(key_value.size) + " != expected size " + std::to_string(key_size),
|
||||||
|
DB::ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid extra work on empty ciphertext/plaintext for some ciphers
|
||||||
|
if (!(input_value.size == 0 && block_size == 1 && mode != CipherMode::RFC5116_AEAD_AES_GCM))
|
||||||
|
{
|
||||||
|
// 1: Init CTX
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
|
||||||
|
{
|
||||||
|
if (EVP_DecryptInit_ex(evp_ctx, evp_cipher, nullptr, nullptr, nullptr) != 1)
|
||||||
|
onError("Failed to initialize cipher context 1");
|
||||||
|
|
||||||
|
// 1.a.1 : Set custom IV length
|
||||||
|
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_value.size, nullptr) != 1)
|
||||||
|
onError("Failed to set custom IV length to " + std::to_string(iv_value.size));
|
||||||
|
|
||||||
|
// 1.a.1 : Init CTX with key and IV
|
||||||
|
if (EVP_DecryptInit_ex(evp_ctx, nullptr, nullptr,
|
||||||
|
reinterpret_cast<const unsigned char*>(key_value.data),
|
||||||
|
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
|
||||||
|
onError("Failed to set key and IV");
|
||||||
|
|
||||||
|
// 1.a.2: Set AAD if present
|
||||||
|
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
|
||||||
|
{
|
||||||
|
const auto aad_data = aad_column->getDataAt(r);
|
||||||
|
int tmp_len = 0;
|
||||||
|
if (aad_data.size != 0 && EVP_DecryptUpdate(evp_ctx, nullptr, &tmp_len,
|
||||||
|
reinterpret_cast<const unsigned char *>(aad_data.data), aad_data.size) != 1)
|
||||||
|
onError("Failed to sed AAD data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 1.b: Init CTX
|
||||||
|
validateIV<mode>(iv_value, iv_size);
|
||||||
|
|
||||||
|
if (EVP_DecryptInit_ex(evp_ctx, evp_cipher, nullptr,
|
||||||
|
reinterpret_cast<const unsigned char*>(key_value.data),
|
||||||
|
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
|
||||||
|
onError("Failed to initialize cipher context");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2: Feed the data to the cipher
|
||||||
|
int output_len = 0;
|
||||||
|
if (EVP_DecryptUpdate(evp_ctx,
|
||||||
|
reinterpret_cast<unsigned char*>(decrypted), &output_len,
|
||||||
|
reinterpret_cast<const unsigned char*>(input_value.data), static_cast<int>(input_value.size)) != 1)
|
||||||
|
onError("Failed to decrypt");
|
||||||
|
decrypted += output_len;
|
||||||
|
|
||||||
|
// 3: optionally get tag from the ciphertext (RFC5116) and feed it to the context
|
||||||
|
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
|
||||||
|
{
|
||||||
|
void * tag = const_cast<void *>(reinterpret_cast<const void *>(input_value.data + input_value.size));
|
||||||
|
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_TAG, tag_size, tag) != 1)
|
||||||
|
onError("Failed to set tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4: retrieve encrypted data (ciphertext)
|
||||||
|
if (EVP_DecryptFinal_ex(evp_ctx,
|
||||||
|
reinterpret_cast<unsigned char*>(decrypted), &output_len) != 1)
|
||||||
|
onError("Failed to decrypt");
|
||||||
|
decrypted += output_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
*decrypted = '\0';
|
||||||
|
++decrypted;
|
||||||
|
|
||||||
|
decrypted_result_column_offsets.push_back(decrypted - decrypted_result_column_data.data());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case we overestimate buffer required for decrypted data, fix it up.
|
||||||
|
if (!decrypted_result_column_offsets.empty() && decrypted_result_column_data.size() > decrypted_result_column_offsets.back())
|
||||||
|
{
|
||||||
|
decrypted_result_column_data.resize(decrypted_result_column_offsets.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted_result_column->validate();
|
||||||
|
return decrypted_result_column;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -1659,7 +1659,7 @@ public:
|
|||||||
if (!isUInt8(second_argument))
|
if (!isUInt8(second_argument))
|
||||||
throw Exception{"Illegal type " + second_argument->getName()
|
throw Exception{"Illegal type " + second_argument->getName()
|
||||||
+ " of second argument of function " + getName()
|
+ " of second argument of function " + getName()
|
||||||
+ ", expected numeric type.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
|
+ ", expected UInt8", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
|
||||||
|
|
||||||
DataTypePtr element = DataTypeFactory::instance().get("IPv6");
|
DataTypePtr element = DataTypeFactory::instance().get("IPv6");
|
||||||
return std::make_shared<DataTypeTuple>(DataTypes{element, element});
|
return std::make_shared<DataTypeTuple>(DataTypes{element, element});
|
||||||
@ -1673,16 +1673,18 @@ public:
|
|||||||
const auto & col_type_name_ip = columns[arguments[0]];
|
const auto & col_type_name_ip = columns[arguments[0]];
|
||||||
const ColumnPtr & column_ip = col_type_name_ip.column;
|
const ColumnPtr & column_ip = col_type_name_ip.column;
|
||||||
|
|
||||||
|
const auto col_const_ip_in = checkAndGetColumnConst<ColumnFixedString>(column_ip.get());
|
||||||
const auto col_ip_in = checkAndGetColumn<ColumnFixedString>(column_ip.get());
|
const auto col_ip_in = checkAndGetColumn<ColumnFixedString>(column_ip.get());
|
||||||
|
|
||||||
if (!col_ip_in)
|
if (!col_ip_in && !col_const_ip_in)
|
||||||
throw Exception("Illegal column " + columns[arguments[0]].column->getName()
|
throw Exception("Illegal column " + columns[arguments[0]].column->getName()
|
||||||
+ " of argument of function " + getName(),
|
+ " of argument of function " + getName(),
|
||||||
ErrorCodes::ILLEGAL_COLUMN);
|
ErrorCodes::ILLEGAL_COLUMN);
|
||||||
|
|
||||||
if (col_ip_in->getN() != IPV6_BINARY_LENGTH)
|
if ((col_const_ip_in && col_const_ip_in->getValue<String>().size() != IPV6_BINARY_LENGTH) ||
|
||||||
|
(col_ip_in && col_ip_in->getN() != IPV6_BINARY_LENGTH))
|
||||||
throw Exception("Illegal type " + col_type_name_ip.type->getName() +
|
throw Exception("Illegal type " + col_type_name_ip.type->getName() +
|
||||||
" of column " + col_ip_in->getName() +
|
" of column " + column_ip->getName() +
|
||||||
" argument of function " + getName() +
|
" argument of function " + getName() +
|
||||||
", expected FixedString(" + toString(IPV6_BINARY_LENGTH) + ")",
|
", expected FixedString(" + toString(IPV6_BINARY_LENGTH) + ")",
|
||||||
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
||||||
@ -1698,8 +1700,6 @@ public:
|
|||||||
+ " of argument of function " + getName(),
|
+ " of argument of function " + getName(),
|
||||||
ErrorCodes::ILLEGAL_COLUMN);
|
ErrorCodes::ILLEGAL_COLUMN);
|
||||||
|
|
||||||
const auto & vec_in = col_ip_in->getChars();
|
|
||||||
|
|
||||||
auto col_res_lower_range = ColumnFixedString::create(IPV6_BINARY_LENGTH);
|
auto col_res_lower_range = ColumnFixedString::create(IPV6_BINARY_LENGTH);
|
||||||
auto col_res_upper_range = ColumnFixedString::create(IPV6_BINARY_LENGTH);
|
auto col_res_upper_range = ColumnFixedString::create(IPV6_BINARY_LENGTH);
|
||||||
|
|
||||||
@ -1711,14 +1711,24 @@ public:
|
|||||||
|
|
||||||
static constexpr UInt8 max_cidr_mask = IPV6_BINARY_LENGTH * 8;
|
static constexpr UInt8 max_cidr_mask = IPV6_BINARY_LENGTH * 8;
|
||||||
|
|
||||||
|
const String col_const_ip_str = col_const_ip_in ? col_const_ip_in->getValue<String>() : "";
|
||||||
|
const UInt8 * col_const_ip_value = col_const_ip_in ? reinterpret_cast<const UInt8 *>(col_const_ip_str.c_str()) : nullptr;
|
||||||
|
|
||||||
for (size_t offset = 0; offset < input_rows_count; ++offset)
|
for (size_t offset = 0; offset < input_rows_count; ++offset)
|
||||||
{
|
{
|
||||||
const size_t offset_ipv6 = offset * IPV6_BINARY_LENGTH;
|
const size_t offset_ipv6 = offset * IPV6_BINARY_LENGTH;
|
||||||
|
|
||||||
|
const UInt8 * ip = col_const_ip_in
|
||||||
|
? col_const_ip_value
|
||||||
|
: &col_ip_in->getChars()[offset_ipv6];
|
||||||
|
|
||||||
UInt8 cidr = col_const_cidr_in
|
UInt8 cidr = col_const_cidr_in
|
||||||
? col_const_cidr_in->getValue<UInt8>()
|
? col_const_cidr_in->getValue<UInt8>()
|
||||||
: col_cidr_in->getData()[offset];
|
: col_cidr_in->getData()[offset];
|
||||||
|
|
||||||
cidr = std::min(cidr, max_cidr_mask);
|
cidr = std::min(cidr, max_cidr_mask);
|
||||||
applyCIDRMask(&vec_in[offset_ipv6], &vec_res_lower_range[offset_ipv6], &vec_res_upper_range[offset_ipv6], cidr);
|
|
||||||
|
applyCIDRMask(ip, &vec_res_lower_range[offset_ipv6], &vec_res_upper_range[offset_ipv6], cidr);
|
||||||
}
|
}
|
||||||
|
|
||||||
columns[result].column = ColumnTuple::create(Columns{std::move(col_res_lower_range), std::move(col_res_upper_range)});
|
columns[result].column = ColumnTuple::create(Columns{std::move(col_res_lower_range), std::move(col_res_upper_range)});
|
||||||
@ -1763,7 +1773,7 @@ public:
|
|||||||
if (!isUInt8(second_argument))
|
if (!isUInt8(second_argument))
|
||||||
throw Exception{"Illegal type " + second_argument->getName()
|
throw Exception{"Illegal type " + second_argument->getName()
|
||||||
+ " of second argument of function " + getName()
|
+ " of second argument of function " + getName()
|
||||||
+ ", expected numeric type.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
|
+ ", expected UInt8", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
|
||||||
|
|
||||||
DataTypePtr element = DataTypeFactory::instance().get("IPv4");
|
DataTypePtr element = DataTypeFactory::instance().get("IPv4");
|
||||||
return std::make_shared<DataTypeTuple>(DataTypes{element, element});
|
return std::make_shared<DataTypeTuple>(DataTypes{element, element});
|
||||||
@ -1777,8 +1787,9 @@ public:
|
|||||||
const auto & col_type_name_ip = columns[arguments[0]];
|
const auto & col_type_name_ip = columns[arguments[0]];
|
||||||
const ColumnPtr & column_ip = col_type_name_ip.column;
|
const ColumnPtr & column_ip = col_type_name_ip.column;
|
||||||
|
|
||||||
|
const auto col_const_ip_in = checkAndGetColumnConst<ColumnUInt32>(column_ip.get());
|
||||||
const auto col_ip_in = checkAndGetColumn<ColumnUInt32>(column_ip.get());
|
const auto col_ip_in = checkAndGetColumn<ColumnUInt32>(column_ip.get());
|
||||||
if (!col_ip_in)
|
if (!col_const_ip_in && !col_ip_in)
|
||||||
throw Exception("Illegal column " + columns[arguments[0]].column->getName()
|
throw Exception("Illegal column " + columns[arguments[0]].column->getName()
|
||||||
+ " of argument of function " + getName(),
|
+ " of argument of function " + getName(),
|
||||||
ErrorCodes::ILLEGAL_COLUMN);
|
ErrorCodes::ILLEGAL_COLUMN);
|
||||||
@ -1794,8 +1805,6 @@ public:
|
|||||||
+ " of argument of function " + getName(),
|
+ " of argument of function " + getName(),
|
||||||
ErrorCodes::ILLEGAL_COLUMN);
|
ErrorCodes::ILLEGAL_COLUMN);
|
||||||
|
|
||||||
const auto & vec_in = col_ip_in->getData();
|
|
||||||
|
|
||||||
auto col_res_lower_range = ColumnUInt32::create();
|
auto col_res_lower_range = ColumnUInt32::create();
|
||||||
auto col_res_upper_range = ColumnUInt32::create();
|
auto col_res_upper_range = ColumnUInt32::create();
|
||||||
|
|
||||||
@ -1807,11 +1816,15 @@ public:
|
|||||||
|
|
||||||
for (size_t i = 0; i < input_rows_count; ++i)
|
for (size_t i = 0; i < input_rows_count; ++i)
|
||||||
{
|
{
|
||||||
|
UInt32 ip = col_const_ip_in
|
||||||
|
? col_const_ip_in->getValue<UInt32>()
|
||||||
|
: col_ip_in->getData()[i];
|
||||||
|
|
||||||
UInt8 cidr = col_const_cidr_in
|
UInt8 cidr = col_const_cidr_in
|
||||||
? col_const_cidr_in->getValue<UInt8>()
|
? col_const_cidr_in->getValue<UInt8>()
|
||||||
: col_cidr_in->getData()[i];
|
: col_cidr_in->getData()[i];
|
||||||
|
|
||||||
std::tie(vec_res_lower_range[i], vec_res_upper_range[i]) = applyCIDRMask(vec_in[i], cidr);
|
std::tie(vec_res_lower_range[i], vec_res_upper_range[i]) = applyCIDRMask(ip, cidr);
|
||||||
}
|
}
|
||||||
|
|
||||||
columns[result].column = ColumnTuple::create(Columns{std::move(col_res_lower_range), std::move(col_res_upper_range)});
|
columns[result].column = ColumnTuple::create(Columns{std::move(col_res_lower_range), std::move(col_res_upper_range)});
|
||||||
|
31
src/Functions/aes_decrypt_mysql.cpp
Normal file
31
src/Functions/aes_decrypt_mysql.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#if !defined(ARCADIA_BUILD)
|
||||||
|
# include <Common/config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
|
||||||
|
#include <Functions/FunctionFactory.h>
|
||||||
|
#include <Functions/FunctionsAES.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct DecryptMySQLModeImpl
|
||||||
|
{
|
||||||
|
static constexpr auto name = "aes_decrypt_mysql";
|
||||||
|
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::MySQL;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
void registerFunctionAESDecryptMysql(FunctionFactory & factory)
|
||||||
|
{
|
||||||
|
factory.registerFunction<FunctionDecrypt<DecryptMySQLModeImpl>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
31
src/Functions/aes_encrypt_mysql.cpp
Normal file
31
src/Functions/aes_encrypt_mysql.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#if !defined(ARCADIA_BUILD)
|
||||||
|
# include <Common/config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
|
||||||
|
#include <Functions/FunctionFactory.h>
|
||||||
|
#include <Functions/FunctionsAES.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct EncryptMySQLModeImpl
|
||||||
|
{
|
||||||
|
static constexpr auto name = "aes_encrypt_mysql";
|
||||||
|
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::MySQL;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
void registerFunctionAESEncryptMysql(FunctionFactory & factory)
|
||||||
|
{
|
||||||
|
factory.registerFunction<FunctionEncrypt<EncryptMySQLModeImpl>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
31
src/Functions/decrypt.cpp
Normal file
31
src/Functions/decrypt.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#if !defined(ARCADIA_BUILD)
|
||||||
|
# include <Common/config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
|
||||||
|
#include <Functions/FunctionFactory.h>
|
||||||
|
#include <Functions/FunctionsAES.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct DecryptImpl
|
||||||
|
{
|
||||||
|
static constexpr auto name = "decrypt";
|
||||||
|
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::OpenSSL;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
void registerFunctionDecrypt(FunctionFactory & factory)
|
||||||
|
{
|
||||||
|
factory.registerFunction<FunctionDecrypt<DecryptImpl>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
31
src/Functions/encrypt.cpp
Normal file
31
src/Functions/encrypt.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#if !defined(ARCADIA_BUILD)
|
||||||
|
# include <Common/config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
|
||||||
|
#include <Functions/FunctionFactory.h>
|
||||||
|
#include <Functions/FunctionsAES.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct EncryptImpl
|
||||||
|
{
|
||||||
|
static constexpr auto name = "encrypt";
|
||||||
|
static constexpr auto compatibility_mode = OpenSSLDetails::CompatibilityMode::OpenSSL;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
void registerFunctionEncrypt(FunctionFactory & factory)
|
||||||
|
{
|
||||||
|
factory.registerFunction<FunctionEncrypt<EncryptImpl>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -1,3 +1,7 @@
|
|||||||
|
#if !defined(ARCADIA_BUILD)
|
||||||
|
# include <Common/config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <Functions/FunctionFactory.h>
|
#include <Functions/FunctionFactory.h>
|
||||||
|
|
||||||
|
|
||||||
@ -37,12 +41,21 @@ void registerFunctionsNull(FunctionFactory &);
|
|||||||
void registerFunctionsJSON(FunctionFactory &);
|
void registerFunctionsJSON(FunctionFactory &);
|
||||||
void registerFunctionsConsistentHashing(FunctionFactory & factory);
|
void registerFunctionsConsistentHashing(FunctionFactory & factory);
|
||||||
void registerFunctionsUnixTimestamp64(FunctionFactory & factory);
|
void registerFunctionsUnixTimestamp64(FunctionFactory & factory);
|
||||||
|
|
||||||
#if !defined(ARCADIA_BUILD)
|
#if !defined(ARCADIA_BUILD)
|
||||||
void registerFunctionBayesAB(FunctionFactory &);
|
void registerFunctionBayesAB(FunctionFactory &);
|
||||||
#endif
|
#endif
|
||||||
void registerFunctionTid(FunctionFactory & factory);
|
void registerFunctionTid(FunctionFactory & factory);
|
||||||
void registerFunctionLogTrace(FunctionFactory & factory);
|
void registerFunctionLogTrace(FunctionFactory & factory);
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
void registerFunctionEncrypt(FunctionFactory & factory);
|
||||||
|
void registerFunctionDecrypt(FunctionFactory & factory);
|
||||||
|
void registerFunctionAESEncryptMysql(FunctionFactory & factory);
|
||||||
|
void registerFunctionAESDecryptMysql(FunctionFactory & factory);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
void registerFunctions()
|
void registerFunctions()
|
||||||
{
|
{
|
||||||
@ -84,9 +97,17 @@ void registerFunctions()
|
|||||||
registerFunctionsIntrospection(factory);
|
registerFunctionsIntrospection(factory);
|
||||||
registerFunctionsConsistentHashing(factory);
|
registerFunctionsConsistentHashing(factory);
|
||||||
registerFunctionsUnixTimestamp64(factory);
|
registerFunctionsUnixTimestamp64(factory);
|
||||||
|
|
||||||
#if !defined(ARCADIA_BUILD)
|
#if !defined(ARCADIA_BUILD)
|
||||||
registerFunctionBayesAB(factory);
|
registerFunctionBayesAB(factory);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if USE_SSL
|
||||||
|
registerFunctionEncrypt(factory);
|
||||||
|
registerFunctionDecrypt(factory);
|
||||||
|
registerFunctionAESEncryptMysql(factory);
|
||||||
|
registerFunctionAESDecryptMysql(factory);
|
||||||
|
#endif
|
||||||
registerFunctionTid(factory);
|
registerFunctionTid(factory);
|
||||||
registerFunctionLogTrace(factory);
|
registerFunctionLogTrace(factory);
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,8 @@ SRCS(
|
|||||||
addSeconds.cpp
|
addSeconds.cpp
|
||||||
addWeeks.cpp
|
addWeeks.cpp
|
||||||
addYears.cpp
|
addYears.cpp
|
||||||
|
aes_decrypt_mysql.cpp
|
||||||
|
aes_encrypt_mysql.cpp
|
||||||
appendTrailingCharIfAbsent.cpp
|
appendTrailingCharIfAbsent.cpp
|
||||||
array/arrayAll.cpp
|
array/arrayAll.cpp
|
||||||
array/arrayAUC.cpp
|
array/arrayAUC.cpp
|
||||||
@ -141,6 +143,7 @@ SRCS(
|
|||||||
currentUser.cpp
|
currentUser.cpp
|
||||||
dateDiff.cpp
|
dateDiff.cpp
|
||||||
date_trunc.cpp
|
date_trunc.cpp
|
||||||
|
decrypt.cpp
|
||||||
defaultValueOfArgumentType.cpp
|
defaultValueOfArgumentType.cpp
|
||||||
defaultValueOfTypeName.cpp
|
defaultValueOfTypeName.cpp
|
||||||
demange.cpp
|
demange.cpp
|
||||||
@ -148,6 +151,7 @@ SRCS(
|
|||||||
dumpColumnStructure.cpp
|
dumpColumnStructure.cpp
|
||||||
e.cpp
|
e.cpp
|
||||||
empty.cpp
|
empty.cpp
|
||||||
|
encrypt.cpp
|
||||||
endsWith.cpp
|
endsWith.cpp
|
||||||
equals.cpp
|
equals.cpp
|
||||||
erfc.cpp
|
erfc.cpp
|
||||||
@ -176,6 +180,7 @@ SRCS(
|
|||||||
FunctionFQDN.cpp
|
FunctionFQDN.cpp
|
||||||
FunctionHelpers.cpp
|
FunctionHelpers.cpp
|
||||||
FunctionJoinGet.cpp
|
FunctionJoinGet.cpp
|
||||||
|
FunctionsAES.cpp
|
||||||
FunctionsCoding.cpp
|
FunctionsCoding.cpp
|
||||||
FunctionsConversion.cpp
|
FunctionsConversion.cpp
|
||||||
FunctionsEmbeddedDictionaries.cpp
|
FunctionsEmbeddedDictionaries.cpp
|
||||||
|
@ -590,7 +590,7 @@ VolumePtr Context::setTemporaryStorage(const String & path, const String & polic
|
|||||||
shared->tmp_path += '/';
|
shared->tmp_path += '/';
|
||||||
|
|
||||||
auto disk = std::make_shared<DiskLocal>("_tmp_default", shared->tmp_path, 0);
|
auto disk = std::make_shared<DiskLocal>("_tmp_default", shared->tmp_path, 0);
|
||||||
shared->tmp_volume = std::make_shared<SingleDiskVolume>("_tmp_default", disk);
|
shared->tmp_volume = std::make_shared<SingleDiskVolume>("_tmp_default", disk, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -117,11 +117,14 @@ ExpressionAnalyzer::ExpressionAnalyzer(
|
|||||||
const TreeRewriterResultPtr & syntax_analyzer_result_,
|
const TreeRewriterResultPtr & syntax_analyzer_result_,
|
||||||
const Context & context_,
|
const Context & context_,
|
||||||
size_t subquery_depth_,
|
size_t subquery_depth_,
|
||||||
bool do_global)
|
bool do_global,
|
||||||
|
SubqueriesForSets subqueries_for_sets_)
|
||||||
: query(query_), context(context_), settings(context.getSettings())
|
: query(query_), context(context_), settings(context.getSettings())
|
||||||
, subquery_depth(subquery_depth_)
|
, subquery_depth(subquery_depth_)
|
||||||
, syntax(syntax_analyzer_result_)
|
, syntax(syntax_analyzer_result_)
|
||||||
{
|
{
|
||||||
|
subqueries_for_sets = std::move(subqueries_for_sets_);
|
||||||
|
|
||||||
/// external_tables, subqueries_for_sets for global subqueries.
|
/// external_tables, subqueries_for_sets for global subqueries.
|
||||||
/// Replaces global subqueries with the generated names of temporary tables that will be sent to remote servers.
|
/// Replaces global subqueries with the generated names of temporary tables that will be sent to remote servers.
|
||||||
initGlobalSubqueriesAndExternalTables(do_global);
|
initGlobalSubqueriesAndExternalTables(do_global);
|
||||||
|
@ -93,7 +93,7 @@ public:
|
|||||||
const ASTPtr & query_,
|
const ASTPtr & query_,
|
||||||
const TreeRewriterResultPtr & syntax_analyzer_result_,
|
const TreeRewriterResultPtr & syntax_analyzer_result_,
|
||||||
const Context & context_)
|
const Context & context_)
|
||||||
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, 0, false)
|
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, 0, false, {})
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void appendExpression(ExpressionActionsChain & chain, const ASTPtr & expr, bool only_types);
|
void appendExpression(ExpressionActionsChain & chain, const ASTPtr & expr, bool only_types);
|
||||||
@ -124,7 +124,8 @@ protected:
|
|||||||
const TreeRewriterResultPtr & syntax_analyzer_result_,
|
const TreeRewriterResultPtr & syntax_analyzer_result_,
|
||||||
const Context & context_,
|
const Context & context_,
|
||||||
size_t subquery_depth_,
|
size_t subquery_depth_,
|
||||||
bool do_global_);
|
bool do_global_,
|
||||||
|
SubqueriesForSets subqueries_for_sets_);
|
||||||
|
|
||||||
ASTPtr query;
|
ASTPtr query;
|
||||||
const Context & context;
|
const Context & context;
|
||||||
@ -244,8 +245,9 @@ public:
|
|||||||
const StorageMetadataPtr & metadata_snapshot_,
|
const StorageMetadataPtr & metadata_snapshot_,
|
||||||
const NameSet & required_result_columns_ = {},
|
const NameSet & required_result_columns_ = {},
|
||||||
bool do_global_ = false,
|
bool do_global_ = false,
|
||||||
const SelectQueryOptions & options_ = {})
|
const SelectQueryOptions & options_ = {},
|
||||||
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, options_.subquery_depth, do_global_)
|
SubqueriesForSets subqueries_for_sets_ = {})
|
||||||
|
: ExpressionAnalyzer(query_, syntax_analyzer_result_, context_, options_.subquery_depth, do_global_, std::move(subqueries_for_sets_))
|
||||||
, metadata_snapshot(metadata_snapshot_)
|
, metadata_snapshot(metadata_snapshot_)
|
||||||
, required_result_columns(required_result_columns_)
|
, required_result_columns(required_result_columns_)
|
||||||
, query_options(options_)
|
, query_options(options_)
|
||||||
|
@ -413,6 +413,11 @@ BlockIO InterpreterInsertQuery::execute()
|
|||||||
res.out = std::move(out_streams.at(0));
|
res.out = std::move(out_streams.at(0));
|
||||||
|
|
||||||
res.pipeline.addStorageHolder(table);
|
res.pipeline.addStorageHolder(table);
|
||||||
|
if (const auto * mv = dynamic_cast<const StorageMaterializedView *>(table.get()))
|
||||||
|
{
|
||||||
|
if (auto inner_table = mv->tryGetTargetTable())
|
||||||
|
res.pipeline.addStorageHolder(inner_table);
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -304,6 +304,8 @@ InterpreterSelectQuery::InterpreterSelectQuery(
|
|||||||
if (storage)
|
if (storage)
|
||||||
view = dynamic_cast<StorageView *>(storage.get());
|
view = dynamic_cast<StorageView *>(storage.get());
|
||||||
|
|
||||||
|
SubqueriesForSets subquery_for_sets;
|
||||||
|
|
||||||
auto analyze = [&] (bool try_move_to_prewhere)
|
auto analyze = [&] (bool try_move_to_prewhere)
|
||||||
{
|
{
|
||||||
/// Allow push down and other optimizations for VIEW: replace with subquery and rewrite it.
|
/// Allow push down and other optimizations for VIEW: replace with subquery and rewrite it.
|
||||||
@ -344,7 +346,7 @@ InterpreterSelectQuery::InterpreterSelectQuery(
|
|||||||
query_analyzer = std::make_unique<SelectQueryExpressionAnalyzer>(
|
query_analyzer = std::make_unique<SelectQueryExpressionAnalyzer>(
|
||||||
query_ptr, syntax_analyzer_result, *context, metadata_snapshot,
|
query_ptr, syntax_analyzer_result, *context, metadata_snapshot,
|
||||||
NameSet(required_result_column_names.begin(), required_result_column_names.end()),
|
NameSet(required_result_column_names.begin(), required_result_column_names.end()),
|
||||||
!options.only_analyze, options);
|
!options.only_analyze, options, std::move(subquery_for_sets));
|
||||||
|
|
||||||
if (!options.only_analyze)
|
if (!options.only_analyze)
|
||||||
{
|
{
|
||||||
@ -430,6 +432,7 @@ InterpreterSelectQuery::InterpreterSelectQuery(
|
|||||||
|
|
||||||
if (need_analyze_again)
|
if (need_analyze_again)
|
||||||
{
|
{
|
||||||
|
subquery_for_sets = std::move(query_analyzer->getSubqueriesForSets());
|
||||||
/// Do not try move conditions to PREWHERE for the second time.
|
/// Do not try move conditions to PREWHERE for the second time.
|
||||||
/// Otherwise, we won't be able to fallback from inefficient PREWHERE to WHERE later.
|
/// Otherwise, we won't be able to fallback from inefficient PREWHERE to WHERE later.
|
||||||
analyze(/* try_move_to_prewhere = */ false);
|
analyze(/* try_move_to_prewhere = */ false);
|
||||||
|
@ -133,7 +133,11 @@ void InterpreterSystemQuery::startStopAction(StorageActionBlockType action_type,
|
|||||||
auto manager = context.getActionLocksManager();
|
auto manager = context.getActionLocksManager();
|
||||||
manager->cleanExpired();
|
manager->cleanExpired();
|
||||||
|
|
||||||
if (table_id)
|
if (volume_ptr && action_type == ActionLocks::PartsMerge)
|
||||||
|
{
|
||||||
|
volume_ptr->setAvoidMergesUserOverride(!start);
|
||||||
|
}
|
||||||
|
else if (table_id)
|
||||||
{
|
{
|
||||||
context.checkAccess(getRequiredAccessType(action_type), table_id);
|
context.checkAccess(getRequiredAccessType(action_type), table_id);
|
||||||
if (start)
|
if (start)
|
||||||
@ -199,6 +203,10 @@ BlockIO InterpreterSystemQuery::execute()
|
|||||||
if (!query.target_dictionary.empty() && !query.database.empty())
|
if (!query.target_dictionary.empty() && !query.database.empty())
|
||||||
query.target_dictionary = query.database + "." + query.target_dictionary;
|
query.target_dictionary = query.database + "." + query.target_dictionary;
|
||||||
|
|
||||||
|
volume_ptr = {};
|
||||||
|
if (!query.storage_policy.empty() && !query.volume.empty())
|
||||||
|
volume_ptr = context.getStoragePolicy(query.storage_policy)->getVolumeByName(query.volume);
|
||||||
|
|
||||||
switch (query.type)
|
switch (query.type)
|
||||||
{
|
{
|
||||||
case Type::SHUTDOWN:
|
case Type::SHUTDOWN:
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <Storages/IStorage_fwd.h>
|
#include <Storages/IStorage_fwd.h>
|
||||||
#include <Interpreters/StorageID.h>
|
#include <Interpreters/StorageID.h>
|
||||||
#include <Common/ActionLock.h>
|
#include <Common/ActionLock.h>
|
||||||
|
#include <Disks/IVolume.h>
|
||||||
|
|
||||||
|
|
||||||
namespace Poco { class Logger; }
|
namespace Poco { class Logger; }
|
||||||
@ -44,6 +45,7 @@ private:
|
|||||||
Context & context;
|
Context & context;
|
||||||
Poco::Logger * log = nullptr;
|
Poco::Logger * log = nullptr;
|
||||||
StorageID table_id = StorageID::createEmpty(); /// Will be set up if query contains table name
|
StorageID table_id = StorageID::createEmpty(); /// Will be set up if query contains table name
|
||||||
|
VolumePtr volume_ptr;
|
||||||
|
|
||||||
/// Tries to get a replicated table and restart it
|
/// Tries to get a replicated table and restart it
|
||||||
/// Returns pointer to a newly created table if the restart was successful
|
/// Returns pointer to a newly created table if the restart was successful
|
||||||
|
@ -89,6 +89,34 @@ ProcessList::EntryPtr ProcessList::insert(const String & query_, const IAST * as
|
|||||||
throw Exception("Too many simultaneous queries. Maximum: " + toString(max_size), ErrorCodes::TOO_MANY_SIMULTANEOUS_QUERIES);
|
throw Exception("Too many simultaneous queries. Maximum: " + toString(max_size), ErrorCodes::TOO_MANY_SIMULTANEOUS_QUERIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* `max_size` check above is controlled by `max_concurrent_queries` server setting and is a "hard" limit for how many
|
||||||
|
* queries the server can process concurrently. It is configured at startup. When the server is overloaded with queries and the
|
||||||
|
* hard limit is reached it is impossible to connect to the server to run queries for investigation.
|
||||||
|
*
|
||||||
|
* With `max_concurrent_queries_for_all_users` it is possible to configure an additional, runtime configurable, limit for query concurrency.
|
||||||
|
* Usually it should be configured just once for `default_profile` which is inherited by all users. DBAs can override
|
||||||
|
* this setting when connecting to ClickHouse, or it can be configured for a DBA profile to have a value greater than that of
|
||||||
|
* the default profile (or 0 for unlimited).
|
||||||
|
*
|
||||||
|
* One example is to set `max_size=X`, `max_concurrent_queries_for_all_users=X-10` for default profile,
|
||||||
|
* and `max_concurrent_queries_for_all_users=0` for DBAs or accounts that are vital for ClickHouse operations (like metrics
|
||||||
|
* exporters).
|
||||||
|
*
|
||||||
|
* Another creative example is to configure `max_concurrent_queries_for_all_users=50` for "analyst" profiles running adhoc queries
|
||||||
|
* and `max_concurrent_queries_for_all_users=100` for "customer facing" services. This way "analyst" queries will be rejected
|
||||||
|
* once is already processing 50+ concurrent queries (including analysts or any other users).
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!is_unlimited_query && settings.max_concurrent_queries_for_all_users
|
||||||
|
&& processes.size() >= settings.max_concurrent_queries_for_all_users)
|
||||||
|
throw Exception(
|
||||||
|
"Too many simultaneous queries for all users. Current: " + toString(processes.size())
|
||||||
|
+ ", maximum: " + settings.max_concurrent_queries_for_all_users.toString(),
|
||||||
|
ErrorCodes::TOO_MANY_SIMULTANEOUS_QUERIES);
|
||||||
|
}
|
||||||
|
|
||||||
/** Why we use current user?
|
/** Why we use current user?
|
||||||
* Because initial one is passed by client and credentials for it is not verified,
|
* Because initial one is passed by client and credentials for it is not verified,
|
||||||
* and using initial_user for limits will be insecure.
|
* and using initial_user for limits will be insecure.
|
||||||
|
@ -233,7 +233,7 @@ void SystemLog<LogElement>::add(const LogElement & element)
|
|||||||
/// The size of allocation can be in order of a few megabytes.
|
/// The size of allocation can be in order of a few megabytes.
|
||||||
/// But this should not be accounted for query memory usage.
|
/// But this should not be accounted for query memory usage.
|
||||||
/// Otherwise the tests like 01017_uniqCombined_memory_usage.sql will be flacky.
|
/// Otherwise the tests like 01017_uniqCombined_memory_usage.sql will be flacky.
|
||||||
auto temporarily_disable_memory_tracker = getCurrentMemoryTrackerActionLock();
|
MemoryTracker::BlockerInThread temporarily_disable_memory_tracker;
|
||||||
|
|
||||||
/// Should not log messages under mutex.
|
/// Should not log messages under mutex.
|
||||||
bool queue_is_half_full = false;
|
bool queue_is_half_full = false;
|
||||||
|
@ -157,7 +157,7 @@ static void setExceptionStackTrace(QueryLogElement & elem)
|
|||||||
{
|
{
|
||||||
/// Disable memory tracker for stack trace.
|
/// Disable memory tracker for stack trace.
|
||||||
/// Because if exception is "Memory limit (for query) exceed", then we probably can't allocate another one string.
|
/// Because if exception is "Memory limit (for query) exceed", then we probably can't allocate another one string.
|
||||||
auto temporarily_disable_memory_tracker = getCurrentMemoryTrackerActionLock();
|
MemoryTracker::BlockerInThread temporarily_disable_memory_tracker;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -338,28 +338,26 @@ static std::tuple<ASTPtr, BlockIO> executeQueryImpl(
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool ast_modified = false;
|
|
||||||
/// Replace ASTQueryParameter with ASTLiteral for prepared statements.
|
/// Replace ASTQueryParameter with ASTLiteral for prepared statements.
|
||||||
if (context.hasQueryParameters())
|
if (context.hasQueryParameters())
|
||||||
{
|
{
|
||||||
ReplaceQueryParameterVisitor visitor(context.getQueryParameters());
|
ReplaceQueryParameterVisitor visitor(context.getQueryParameters());
|
||||||
visitor.visit(ast);
|
visitor.visit(ast);
|
||||||
ast_modified = true;
|
query = serializeAST(*ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// MUST goes before any modification (except for prepared statements,
|
||||||
|
/// since it substitute parameters and w/o them query does not contains
|
||||||
|
/// parameters), to keep query as-is in query_log and server log.
|
||||||
|
query_for_logging = prepareQueryForLogging(query, context);
|
||||||
|
logQuery(query_for_logging, context, internal);
|
||||||
|
|
||||||
/// Propagate WITH statement to children ASTSelect.
|
/// Propagate WITH statement to children ASTSelect.
|
||||||
if (settings.enable_global_with_statement)
|
if (settings.enable_global_with_statement)
|
||||||
{
|
{
|
||||||
ApplyWithGlobalVisitor().visit(ast);
|
ApplyWithGlobalVisitor().visit(ast);
|
||||||
ast_modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ast_modified)
|
|
||||||
query = serializeAST(*ast);
|
query = serializeAST(*ast);
|
||||||
|
}
|
||||||
query_for_logging = prepareQueryForLogging(query, context);
|
|
||||||
|
|
||||||
logQuery(query_for_logging, context, internal);
|
|
||||||
|
|
||||||
/// Check the limits.
|
/// Check the limits.
|
||||||
checkASTSizeLimits(*ast, settings);
|
checkASTSizeLimits(*ast, settings);
|
||||||
|
@ -118,7 +118,8 @@ void ASTSystemQuery::formatImpl(const FormatSettings & settings, FormatState &,
|
|||||||
<< (settings.hilite ? hilite_none : "");
|
<< (settings.hilite ? hilite_none : "");
|
||||||
};
|
};
|
||||||
|
|
||||||
auto print_drop_replica = [&] {
|
auto print_drop_replica = [&]
|
||||||
|
{
|
||||||
settings.ostr << " " << quoteString(replica);
|
settings.ostr << " " << quoteString(replica);
|
||||||
if (!table.empty())
|
if (!table.empty())
|
||||||
{
|
{
|
||||||
@ -140,6 +141,16 @@ void ASTSystemQuery::formatImpl(const FormatSettings & settings, FormatState &,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto print_on_volume = [&]
|
||||||
|
{
|
||||||
|
settings.ostr << " ON VOLUME "
|
||||||
|
<< (settings.hilite ? hilite_identifier : "") << backQuoteIfNeed(storage_policy)
|
||||||
|
<< (settings.hilite ? hilite_none : "")
|
||||||
|
<< "."
|
||||||
|
<< (settings.hilite ? hilite_identifier : "") << backQuoteIfNeed(volume)
|
||||||
|
<< (settings.hilite ? hilite_none : "");
|
||||||
|
};
|
||||||
|
|
||||||
if (!cluster.empty())
|
if (!cluster.empty())
|
||||||
formatOnCluster(settings);
|
formatOnCluster(settings);
|
||||||
|
|
||||||
@ -160,6 +171,8 @@ void ASTSystemQuery::formatImpl(const FormatSettings & settings, FormatState &,
|
|||||||
{
|
{
|
||||||
if (!table.empty())
|
if (!table.empty())
|
||||||
print_database_table();
|
print_database_table();
|
||||||
|
else if (!volume.empty())
|
||||||
|
print_on_volume();
|
||||||
}
|
}
|
||||||
else if (type == Type::RESTART_REPLICA || type == Type::SYNC_REPLICA || type == Type::FLUSH_DISTRIBUTED)
|
else if (type == Type::RESTART_REPLICA || type == Type::SYNC_REPLICA || type == Type::FLUSH_DISTRIBUTED)
|
||||||
{
|
{
|
||||||
|
@ -65,6 +65,8 @@ public:
|
|||||||
String replica;
|
String replica;
|
||||||
String replica_zk_path;
|
String replica_zk_path;
|
||||||
bool is_drop_whole_replica;
|
bool is_drop_whole_replica;
|
||||||
|
String storage_policy;
|
||||||
|
String volume;
|
||||||
|
|
||||||
String getID(char) const override { return "SYSTEM query"; }
|
String getID(char) const override { return "SYSTEM query"; }
|
||||||
|
|
||||||
|
@ -129,6 +129,33 @@ bool ParserSystemQuery::parseImpl(IParser::Pos & pos, ASTPtr & node, Expected &
|
|||||||
|
|
||||||
case Type::STOP_MERGES:
|
case Type::STOP_MERGES:
|
||||||
case Type::START_MERGES:
|
case Type::START_MERGES:
|
||||||
|
{
|
||||||
|
String storage_policy_str;
|
||||||
|
String volume_str;
|
||||||
|
|
||||||
|
if (ParserKeyword{"ON VOLUME"}.ignore(pos, expected))
|
||||||
|
{
|
||||||
|
ASTPtr ast;
|
||||||
|
if (ParserIdentifier{}.parse(pos, ast, expected))
|
||||||
|
storage_policy_str = ast->as<ASTIdentifier &>().name;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!ParserToken{TokenType::Dot}.ignore(pos, expected))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ParserIdentifier{}.parse(pos, ast, expected))
|
||||||
|
volume_str = ast->as<ASTIdentifier &>().name;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
res->storage_policy = storage_policy_str;
|
||||||
|
res->volume = volume_str;
|
||||||
|
if (res->volume.empty() && res->storage_policy.empty())
|
||||||
|
parseDatabaseAndTableName(pos, expected, res->database, res->table);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case Type::STOP_TTL_MERGES:
|
case Type::STOP_TTL_MERGES:
|
||||||
case Type::START_TTL_MERGES:
|
case Type::START_TTL_MERGES:
|
||||||
case Type::STOP_MOVES:
|
case Type::STOP_MOVES:
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "ReplicasStatusHandler.h"
|
#include "ReplicasStatusHandler.h"
|
||||||
#include "InterserverIOHTTPHandler.h"
|
#include "InterserverIOHTTPHandler.h"
|
||||||
#include "PrometheusRequestHandler.h"
|
#include "PrometheusRequestHandler.h"
|
||||||
|
#include "WebUIRequestHandler.h"
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -78,7 +79,9 @@ static inline auto createHandlersFactoryFromConfig(
|
|||||||
for (const auto & key : keys)
|
for (const auto & key : keys)
|
||||||
{
|
{
|
||||||
if (key == "defaults")
|
if (key == "defaults")
|
||||||
|
{
|
||||||
addDefaultHandlersFactory(*main_handler_factory, server, async_metrics);
|
addDefaultHandlersFactory(*main_handler_factory, server, async_metrics);
|
||||||
|
}
|
||||||
else if (startsWith(key, "rule"))
|
else if (startsWith(key, "rule"))
|
||||||
{
|
{
|
||||||
const auto & handler_type = server.config().getString(prefix + "." + key + ".handler.type", "");
|
const auto & handler_type = server.config().getString(prefix + "." + key + ".handler.type", "");
|
||||||
@ -112,7 +115,9 @@ static inline auto createHandlersFactoryFromConfig(
|
|||||||
static inline Poco::Net::HTTPRequestHandlerFactory * createHTTPHandlerFactory(IServer & server, const std::string & name, AsynchronousMetrics & async_metrics)
|
static inline Poco::Net::HTTPRequestHandlerFactory * createHTTPHandlerFactory(IServer & server, const std::string & name, AsynchronousMetrics & async_metrics)
|
||||||
{
|
{
|
||||||
if (server.config().has("http_handlers"))
|
if (server.config().has("http_handlers"))
|
||||||
|
{
|
||||||
return createHandlersFactoryFromConfig(server, name, "http_handlers", async_metrics);
|
return createHandlersFactoryFromConfig(server, name, "http_handlers", async_metrics);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto factory = std::make_unique<HTTPRequestHandlerFactoryMain>(name);
|
auto factory = std::make_unique<HTTPRequestHandlerFactoryMain>(name);
|
||||||
@ -168,6 +173,10 @@ void addCommonDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IS
|
|||||||
auto replicas_status_handler = std::make_unique<HandlingRuleHTTPHandlerFactory<ReplicasStatusHandler>>(server);
|
auto replicas_status_handler = std::make_unique<HandlingRuleHTTPHandlerFactory<ReplicasStatusHandler>>(server);
|
||||||
replicas_status_handler->attachNonStrictPath("/replicas_status")->allowGetAndHeadRequest();
|
replicas_status_handler->attachNonStrictPath("/replicas_status")->allowGetAndHeadRequest();
|
||||||
factory.addHandler(replicas_status_handler.release());
|
factory.addHandler(replicas_status_handler.release());
|
||||||
|
|
||||||
|
auto web_ui_handler = std::make_unique<HandlingRuleHTTPHandlerFactory<WebUIRequestHandler>>(server, "play.html");
|
||||||
|
web_ui_handler->attachNonStrictPath("/play")->allowGetAndHeadRequest();
|
||||||
|
factory.addHandler(web_ui_handler.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
void addDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IServer & server, AsynchronousMetrics & async_metrics)
|
void addDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IServer & server, AsynchronousMetrics & async_metrics)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "StaticRequestHandler.h"
|
#include "StaticRequestHandler.h"
|
||||||
|
#include "IServer.h"
|
||||||
|
|
||||||
#include "HTTPHandlerFactory.h"
|
#include "HTTPHandlerFactory.h"
|
||||||
#include "HTTPHandlerRequestFilter.h"
|
#include "HTTPHandlerRequestFilter.h"
|
||||||
@ -17,6 +18,8 @@
|
|||||||
#include <Poco/Net/HTTPServerRequest.h>
|
#include <Poco/Net/HTTPServerRequest.h>
|
||||||
#include <Poco/Net/HTTPServerResponse.h>
|
#include <Poco/Net/HTTPServerResponse.h>
|
||||||
#include <Poco/Net/HTTPRequestHandlerFactory.h>
|
#include <Poco/Net/HTTPRequestHandlerFactory.h>
|
||||||
|
#include <Poco/Util/LayeredConfiguration.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IServer.h"
|
|
||||||
|
|
||||||
#include <Poco/Net/HTTPRequestHandler.h>
|
#include <Poco/Net/HTTPRequestHandler.h>
|
||||||
#include <Common/StringUtils/StringUtils.h>
|
|
||||||
#include <common/types.h>
|
#include <common/types.h>
|
||||||
#include <IO/WriteBuffer.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
|
class IServer;
|
||||||
|
class WriteBuffer;
|
||||||
|
|
||||||
/// Response with custom string. Can be used for browser.
|
/// Response with custom string. Can be used for browser.
|
||||||
class StaticRequestHandler : public Poco::Net::HTTPRequestHandler
|
class StaticRequestHandler : public Poco::Net::HTTPRequestHandler
|
||||||
{
|
{
|
||||||
@ -22,7 +21,11 @@ private:
|
|||||||
String response_expression;
|
String response_expression;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StaticRequestHandler(IServer & server, const String & expression, int status_ = 200, const String & content_type_ = "text/html; charset=UTF-8");
|
StaticRequestHandler(
|
||||||
|
IServer & server,
|
||||||
|
const String & expression,
|
||||||
|
int status_ = 200,
|
||||||
|
const String & content_type_ = "text/html; charset=UTF-8");
|
||||||
|
|
||||||
void writeResponse(WriteBuffer & out);
|
void writeResponse(WriteBuffer & out);
|
||||||
|
|
||||||
|
35
src/Server/WebUIRequestHandler.cpp
Normal file
35
src/Server/WebUIRequestHandler.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include "WebUIRequestHandler.h"
|
||||||
|
#include "IServer.h"
|
||||||
|
|
||||||
|
#include <Poco/Net/HTTPServerRequest.h>
|
||||||
|
#include <Poco/Net/HTTPServerResponse.h>
|
||||||
|
#include <Poco/Util/LayeredConfiguration.h>
|
||||||
|
|
||||||
|
#include <IO/HTTPCommon.h>
|
||||||
|
#include <common/getResource.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
WebUIRequestHandler::WebUIRequestHandler(IServer & server_, std::string resource_name_)
|
||||||
|
: server(server_), resource_name(std::move(resource_name_))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void WebUIRequestHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
|
||||||
|
{
|
||||||
|
auto keep_alive_timeout = server.config().getUInt("keep_alive_timeout", 10);
|
||||||
|
|
||||||
|
response.setContentType("text/html; charset=UTF-8");
|
||||||
|
|
||||||
|
if (request.getVersion() == Poco::Net::HTTPServerRequest::HTTP_1_1)
|
||||||
|
response.setChunkedTransferEncoding(true);
|
||||||
|
|
||||||
|
setResponseDefaultHeaders(response, keep_alive_timeout);
|
||||||
|
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_OK);
|
||||||
|
response.send() << getResource(resource_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
src/Server/WebUIRequestHandler.h
Normal file
23
src/Server/WebUIRequestHandler.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Poco/Net/HTTPRequestHandler.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
class IServer;
|
||||||
|
|
||||||
|
/// Response with HTML page that allows to send queries and show results in browser.
|
||||||
|
class WebUIRequestHandler : public Poco::Net::HTTPRequestHandler
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
IServer & server;
|
||||||
|
std::string resource_name;
|
||||||
|
public:
|
||||||
|
WebUIRequestHandler(IServer & server_, std::string resource_name_);
|
||||||
|
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ SRCS(
|
|||||||
ReplicasStatusHandler.cpp
|
ReplicasStatusHandler.cpp
|
||||||
StaticRequestHandler.cpp
|
StaticRequestHandler.cpp
|
||||||
TCPHandler.cpp
|
TCPHandler.cpp
|
||||||
|
WebUIRequestHandler.cpp
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -311,7 +311,7 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToMemory(
|
|||||||
NativeBlockInputStream block_in(in, 0);
|
NativeBlockInputStream block_in(in, 0);
|
||||||
auto block = block_in.read();
|
auto block = block_in.read();
|
||||||
|
|
||||||
auto volume = std::make_shared<SingleDiskVolume>("volume_" + part_name, reservation->getDisk());
|
auto volume = std::make_shared<SingleDiskVolume>("volume_" + part_name, reservation->getDisk(), 0);
|
||||||
MergeTreeData::MutableDataPartPtr new_data_part =
|
MergeTreeData::MutableDataPartPtr new_data_part =
|
||||||
std::make_shared<MergeTreeDataPartInMemory>(data, part_name, volume);
|
std::make_shared<MergeTreeDataPartInMemory>(data, part_name, volume);
|
||||||
|
|
||||||
@ -408,7 +408,7 @@ MergeTreeData::MutableDataPartPtr Fetcher::downloadPartToDisk(
|
|||||||
|
|
||||||
assertEOF(in);
|
assertEOF(in);
|
||||||
|
|
||||||
auto volume = std::make_shared<SingleDiskVolume>("volume_" + part_name, disk);
|
auto volume = std::make_shared<SingleDiskVolume>("volume_" + part_name, disk, 0);
|
||||||
MergeTreeData::MutableDataPartPtr new_data_part = data.createPart(part_name, volume, part_relative_path);
|
MergeTreeData::MutableDataPartPtr new_data_part = data.createPart(part_name, volume, part_relative_path);
|
||||||
new_data_part->is_temp = true;
|
new_data_part->is_temp = true;
|
||||||
new_data_part->modification_time = time(nullptr);
|
new_data_part->modification_time = time(nullptr);
|
||||||
|
@ -408,7 +408,7 @@ void IMergeTreeDataPart::loadColumnsChecksumsIndexes(bool require_columns_checks
|
|||||||
/// Memory should not be limited during ATTACH TABLE query.
|
/// Memory should not be limited during ATTACH TABLE query.
|
||||||
/// This is already true at the server startup but must be also ensured for manual table ATTACH.
|
/// This is already true at the server startup but must be also ensured for manual table ATTACH.
|
||||||
/// Motivation: memory for index is shared between queries - not belong to the query itself.
|
/// Motivation: memory for index is shared between queries - not belong to the query itself.
|
||||||
auto temporarily_disable_memory_tracker = getCurrentMemoryTrackerActionLock();
|
MemoryTracker::BlockerInThread temporarily_disable_memory_tracker;
|
||||||
|
|
||||||
loadColumns(require_columns_checksums);
|
loadColumns(require_columns_checksums);
|
||||||
loadChecksums(require_columns_checksums);
|
loadChecksums(require_columns_checksums);
|
||||||
@ -760,6 +760,16 @@ void IMergeTreeDataPart::loadColumns(bool require)
|
|||||||
column_name_to_position.emplace(column.name, pos++);
|
column_name_to_position.emplace(column.name, pos++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IMergeTreeDataPart::shallParticipateInMerges(const StoragePolicyPtr & storage_policy) const
|
||||||
|
{
|
||||||
|
/// `IMergeTreeDataPart::volume` describes space where current part belongs, and holds
|
||||||
|
/// `SingleDiskVolume` object which does not contain up-to-date settings of corresponding volume.
|
||||||
|
/// Therefore we shall obtain volume from storage policy.
|
||||||
|
auto volume_ptr = storage_policy->getVolume(storage_policy->getVolumeIndexByDisk(volume->getDisk()));
|
||||||
|
|
||||||
|
return !volume_ptr->areMergesAvoided();
|
||||||
|
}
|
||||||
|
|
||||||
UInt64 IMergeTreeDataPart::calculateTotalSizeOnDisk(const DiskPtr & disk_, const String & from)
|
UInt64 IMergeTreeDataPart::calculateTotalSizeOnDisk(const DiskPtr & disk_, const String & from)
|
||||||
{
|
{
|
||||||
if (disk_->isFile(from))
|
if (disk_->isFile(from))
|
||||||
@ -943,21 +953,26 @@ void IMergeTreeDataPart::makeCloneInDetached(const String & prefix, const Storag
|
|||||||
volume->getDisk()->removeIfExists(destination_path + "/" + DELETE_ON_DESTROY_MARKER_FILE_NAME);
|
volume->getDisk()->removeIfExists(destination_path + "/" + DELETE_ON_DESTROY_MARKER_FILE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IMergeTreeDataPart::makeCloneOnDiskDetached(const ReservationPtr & reservation) const
|
void IMergeTreeDataPart::makeCloneOnDisk(const DiskPtr & disk, const String & directory_name) const
|
||||||
{
|
{
|
||||||
assertOnDisk();
|
assertOnDisk();
|
||||||
auto reserved_disk = reservation->getDisk();
|
|
||||||
if (reserved_disk->getName() == volume->getDisk()->getName())
|
if (disk->getName() == volume->getDisk()->getName())
|
||||||
throw Exception("Can not clone data part " + name + " to same disk " + volume->getDisk()->getName(), ErrorCodes::LOGICAL_ERROR);
|
throw Exception("Can not clone data part " + name + " to same disk " + volume->getDisk()->getName(), ErrorCodes::LOGICAL_ERROR);
|
||||||
|
if (directory_name.empty())
|
||||||
|
throw Exception("Can not clone data part " + name + " to empty directory.", ErrorCodes::LOGICAL_ERROR);
|
||||||
|
|
||||||
String path_to_clone = storage.relative_data_path + "detached/";
|
String path_to_clone = storage.relative_data_path + directory_name + '/';
|
||||||
|
|
||||||
if (reserved_disk->exists(path_to_clone + relative_path))
|
if (disk->exists(path_to_clone + relative_path))
|
||||||
throw Exception("Path " + fullPath(reserved_disk, path_to_clone + relative_path) + " already exists. Can not clone ", ErrorCodes::DIRECTORY_ALREADY_EXISTS);
|
{
|
||||||
reserved_disk->createDirectory(path_to_clone);
|
LOG_WARNING(storage.log, "Path " + fullPath(disk, path_to_clone + relative_path) + " already exists. Will remove it and clone again.");
|
||||||
|
disk->removeRecursive(path_to_clone + relative_path + '/');
|
||||||
|
}
|
||||||
|
disk->createDirectories(path_to_clone);
|
||||||
|
|
||||||
volume->getDisk()->copy(getFullRelativePath(), reserved_disk, path_to_clone);
|
volume->getDisk()->copy(getFullRelativePath(), disk, path_to_clone);
|
||||||
volume->getDisk()->removeIfExists(path_to_clone + "/" + DELETE_ON_DESTROY_MARKER_FILE_NAME);
|
volume->getDisk()->removeIfExists(path_to_clone + '/' + DELETE_ON_DESTROY_MARKER_FILE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IMergeTreeDataPart::checkConsistencyBase() const
|
void IMergeTreeDataPart::checkConsistencyBase() const
|
||||||
|
@ -316,15 +316,19 @@ public:
|
|||||||
/// Makes clone of a part in detached/ directory via hard links
|
/// Makes clone of a part in detached/ directory via hard links
|
||||||
virtual void makeCloneInDetached(const String & prefix, const StorageMetadataPtr & metadata_snapshot) const;
|
virtual void makeCloneInDetached(const String & prefix, const StorageMetadataPtr & metadata_snapshot) const;
|
||||||
|
|
||||||
/// Makes full clone of part in detached/ on another disk
|
/// Makes full clone of part in specified subdirectory (relative to storage data directory, e.g. "detached") on another disk
|
||||||
void makeCloneOnDiskDetached(const ReservationPtr & reservation) const;
|
void makeCloneOnDisk(const DiskPtr & disk, const String & directory_name) const;
|
||||||
|
|
||||||
/// Checks that .bin and .mrk files exist.
|
/// Checks that .bin and .mrk files exist.
|
||||||
///
|
///
|
||||||
/// NOTE: Doesn't take column renames into account, if some column renames
|
/// NOTE: Doesn't take column renames into account, if some column renames
|
||||||
/// take place, you must take original name of column for this part from
|
/// take place, you must take original name of column for this part from
|
||||||
/// storage and pass it to this method.
|
/// storage and pass it to this method.
|
||||||
virtual bool hasColumnFiles(const String & /* column */, const IDataType & /* type */) const{ return false; }
|
virtual bool hasColumnFiles(const String & /* column */, const IDataType & /* type */) const { return false; }
|
||||||
|
|
||||||
|
/// Returns true if this part shall participate in merges according to
|
||||||
|
/// settings of given storage policy.
|
||||||
|
bool shallParticipateInMerges(const StoragePolicyPtr & storage_policy) const;
|
||||||
|
|
||||||
/// Calculate the total size of the entire directory with all the files
|
/// Calculate the total size of the entire directory with all the files
|
||||||
static UInt64 calculateTotalSizeOnDisk(const DiskPtr & disk_, const String & from);
|
static UInt64 calculateTotalSizeOnDisk(const DiskPtr & disk_, const String & from);
|
||||||
|
@ -48,6 +48,8 @@ public:
|
|||||||
|
|
||||||
/// Part compression codec definition.
|
/// Part compression codec definition.
|
||||||
ASTPtr compression_codec_desc;
|
ASTPtr compression_codec_desc;
|
||||||
|
|
||||||
|
bool shall_participate_in_merges = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Parts are belong to partitions. Only parts within same partition could be merged.
|
/// Parts are belong to partitions. Only parts within same partition could be merged.
|
||||||
|
@ -776,7 +776,7 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks)
|
|||||||
if (!MergeTreePartInfo::tryParsePartName(part_name, &part_info, format_version))
|
if (!MergeTreePartInfo::tryParsePartName(part_name, &part_info, format_version))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + part_name, part_disk_ptr);
|
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + part_name, part_disk_ptr, 0);
|
||||||
auto part = createPart(part_name, part_info, single_disk_volume, part_name);
|
auto part = createPart(part_name, part_info, single_disk_volume, part_name);
|
||||||
bool broken = false;
|
bool broken = false;
|
||||||
|
|
||||||
@ -2996,7 +2996,7 @@ MergeTreeData::MutableDataPartsVector MergeTreeData::tryLoadPartsToAttach(const
|
|||||||
for (const auto & part_names : renamed_parts.old_and_new_names)
|
for (const auto & part_names : renamed_parts.old_and_new_names)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(log, "Checking part {}", part_names.second);
|
LOG_DEBUG(log, "Checking part {}", part_names.second);
|
||||||
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + part_names.first, name_to_disk[part_names.first]);
|
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + part_names.first, name_to_disk[part_names.first], 0);
|
||||||
MutableDataPartPtr part = createPart(part_names.first, single_disk_volume, source_dir + part_names.second);
|
MutableDataPartPtr part = createPart(part_names.first, single_disk_volume, source_dir + part_names.second);
|
||||||
loadPartAndFixMetadataImpl(part);
|
loadPartAndFixMetadataImpl(part);
|
||||||
loaded_parts.push_back(part);
|
loaded_parts.push_back(part);
|
||||||
@ -3409,7 +3409,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeData::cloneAndLoadDataPartOnSameDisk(
|
|||||||
localBackup(disk, src_part_path, dst_part_path);
|
localBackup(disk, src_part_path, dst_part_path);
|
||||||
disk->removeIfExists(dst_part_path + "/" + IMergeTreeDataPart::DELETE_ON_DESTROY_MARKER_FILE_NAME);
|
disk->removeIfExists(dst_part_path + "/" + IMergeTreeDataPart::DELETE_ON_DESTROY_MARKER_FILE_NAME);
|
||||||
|
|
||||||
auto single_disk_volume = std::make_shared<SingleDiskVolume>(disk->getName(), disk);
|
auto single_disk_volume = std::make_shared<SingleDiskVolume>(disk->getName(), disk, 0);
|
||||||
auto dst_data_part = createPart(dst_part_name, dst_part_info, single_disk_volume, tmp_dst_part_name);
|
auto dst_data_part = createPart(dst_part_name, dst_part_info, single_disk_volume, tmp_dst_part_name);
|
||||||
|
|
||||||
dst_data_part->is_temp = true;
|
dst_data_part->is_temp = true;
|
||||||
|
@ -226,6 +226,8 @@ bool MergeTreeDataMergerMutator::selectPartsToMerge(
|
|||||||
|
|
||||||
IMergeSelector::PartsRanges parts_ranges;
|
IMergeSelector::PartsRanges parts_ranges;
|
||||||
|
|
||||||
|
StoragePolicyPtr storage_policy = data.getStoragePolicy();
|
||||||
|
|
||||||
const String * prev_partition_id = nullptr;
|
const String * prev_partition_id = nullptr;
|
||||||
/// Previous part only in boundaries of partition frame
|
/// Previous part only in boundaries of partition frame
|
||||||
const MergeTreeData::DataPartPtr * prev_part = nullptr;
|
const MergeTreeData::DataPartPtr * prev_part = nullptr;
|
||||||
@ -275,6 +277,7 @@ bool MergeTreeDataMergerMutator::selectPartsToMerge(
|
|||||||
part_info.data = ∂
|
part_info.data = ∂
|
||||||
part_info.ttl_infos = &part->ttl_infos;
|
part_info.ttl_infos = &part->ttl_infos;
|
||||||
part_info.compression_codec_desc = part->default_codec->getFullCodecDesc();
|
part_info.compression_codec_desc = part->default_codec->getFullCodecDesc();
|
||||||
|
part_info.shall_participate_in_merges = part->shallParticipateInMerges(storage_policy);
|
||||||
|
|
||||||
parts_ranges.back().emplace_back(part_info);
|
parts_ranges.back().emplace_back(part_info);
|
||||||
|
|
||||||
@ -667,7 +670,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor
|
|||||||
merging_columns,
|
merging_columns,
|
||||||
merging_column_names);
|
merging_column_names);
|
||||||
|
|
||||||
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + future_part.name, disk);
|
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + future_part.name, disk, 0);
|
||||||
MergeTreeData::MutableDataPartPtr new_data_part = data.createPart(
|
MergeTreeData::MutableDataPartPtr new_data_part = data.createPart(
|
||||||
future_part.name,
|
future_part.name,
|
||||||
future_part.type,
|
future_part.type,
|
||||||
@ -1127,7 +1130,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mutatePartToTempor
|
|||||||
in->setProgressCallback(MergeProgressCallback(merge_entry, watch_prev_elapsed, stage_progress));
|
in->setProgressCallback(MergeProgressCallback(merge_entry, watch_prev_elapsed, stage_progress));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + future_part.name, space_reservation->getDisk());
|
auto single_disk_volume = std::make_shared<SingleDiskVolume>("volume_" + future_part.name, space_reservation->getDisk(), 0);
|
||||||
auto new_data_part = data.createPart(
|
auto new_data_part = data.createPart(
|
||||||
future_part.name, future_part.type, future_part.part_info, single_disk_volume, "tmp_mut_" + future_part.name);
|
future_part.name, future_part.type, future_part.part_info, single_disk_volume, "tmp_mut_" + future_part.name);
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user