diff --git a/.editorconfig b/.editorconfig index 570373a51d4..f453321daa1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,13 @@ insert_final_newline = true # Matches multiple files with brace expansion notation # Set default charset -[*.{c,cpp,cxx,h,hpp,hxx,py}] +[*.{c,cpp,cxx,h,hpp,hxx,py,cmake}] +charset = utf-8 +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true + +[CMakeLists.txt] charset = utf-8 indent_style = tab indent_size = 4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 863b7f04b16..6741730f835 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,108 +1,123 @@ project (ClickHouse) -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required (VERSION 2.6) -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # require at least gcc 5 - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5) - message(FATAL_ERROR "GCC version must be at least 5! For example, if GCC 5 is available under gcc-5, g++-5 names, do the following: export CC=gcc-5 CXX=g++-5; rm -rf CMakeCache.txt CMakeFiles; and re run cmake or ./release.") - endif() +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + # Require at least gcc 5 + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5 AND NOT CMAKE_VERSION VERSION_LESS 2.8.9) + message (FATAL_ERROR "GCC version must be at least 5! For example, if GCC 5 is available under gcc-5, g++-5 names, do the following: export CC=gcc-5 CXX=g++-5; rm -rf CMakeCache.txt CMakeFiles; and re run cmake or ./release.") + endif () elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - # require at least clang 3.8 - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.8) - message(FATAL_ERROR "Clang version must be at least 3.8!") - endif() -else() - message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang 3.8+ and GCC 5+.") -endif() + # Require at least clang 3.8 + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.8) + message (FATAL_ERROR "Clang version must be at least 3.8!") + endif () +else () + message (WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang 3.8+ and GCC 5+.") +endif () if (APPLE) - set(APPLE_EXTRA_CXX_FLAG "-Dexp10=__exp10") # Also needed for libc++ -endif() + set (APPLE_EXTRA_CXX_FLAG "-Dexp10=__exp10") # Also needed for libc++ +endif () -# отключаем варнинг о том, что в каждой директории должен быть CMakeLists.txt -cmake_policy(SET CMP0014 OLD) +cmake_policy (SET CMP0014 OLD) # Ignore warning about CMakeLists.txt in each directory +cmake_policy (SET CMP0012 NEW) # Don't dereference TRUE and FALSE -cmake_policy(SET CMP0012 NEW) # Don't dereference TRUE and FALSE - -IF(NOT CMAKE_BUILD_TYPE) - message(STATUS "CMAKE_BUILD_TYPE is not set, set to default = RELWITHDEBINFO") - SET(CMAKE_BUILD_TYPE "RELWITHDEBINFO") -ENDIF() -MESSAGE( STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE} ) +if (NOT CMAKE_BUILD_TYPE) + message (STATUS "CMAKE_BUILD_TYPE is not set, set to default = RELWITHDEBINFO") + set (CMAKE_BUILD_TYPE "RELWITHDEBINFO") +endif () +message (STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE} ) # ASan - build type with address sanitizer # UBSan - build type with undefined behaviour sanitizer # TSan is not supported due to false positive errors in libstdc++ and necessity to rebuild libstdc++ with TSan -set(CMAKE_CONFIGURATION_TYPES "RelWithDebInfo;Debug;Release;MinSizeRel;ASan;UBSan" CACHE STRING "" FORCE) +set (CMAKE_CONFIGURATION_TYPES "RelWithDebInfo;Debug;Release;MinSizeRel;ASan;UBSan" CACHE STRING "" FORCE) -IF (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*)") - SET(AARCH64 1) -ENDIF() +if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*)") + set (AARCH64 1) +endif () -IF (NOT AARCH64) - SET(MACHINE_FLAGS "-msse4 -mpopcnt") -ENDIF() +if (NOT AARCH64) + set (MACHINE_FLAGS "-msse4 -mpopcnt") +endif () -SET(COMMON_WARNING_FLAGS "-Wall -Werror") -SET(CXX_WARNING_FLAGS "-Wnon-virtual-dtor -Wold-style-cast") +set (COMMON_WARNING_FLAGS "-Wall -Werror") +set (CXX_WARNING_FLAGS "-Wnon-virtual-dtor -Wold-style-cast") +set (ENABLE_CXX11_ABI FALSE CACHE BOOL "Enables C++11 ABI") +set (TEST_COVERAGE FALSE CACHE BOOL "Enables flags for test coverage") +set (ENABLE_TESTS TRUE CACHE BOOL "Enables tests") + +set (USE_STATIC_LIBRARIES TRUE CACHE BOOL "Set to FALSE to use shared libraries") +if (NOT $ENV{USE_STATIC_LIBRARIES}) + set (USE_STATIC_LIBRARIES FALSE) +endif () set (GLIBC_COMPATIBILITY FALSE CACHE BOOL "Set to TRUE to enable compatibility with older glibc libraries") - if ($ENV{GLIBC_COMPATIBILITY}) set (GLIBC_COMPATIBILITY TRUE) -endif() +endif () + +set (ENABLE_MONGODB TRUE CACHE BOOL "Set to TRUE to enable MongoDB support as source for external dictionaries") +if (NOT $ENV{ENABLE_MONGODB}) + set (ENABLE_MONGODB FALSE) +endif () + +set (ENABLE_LIBTCMALLOC TRUE CACHE BOOL "Set to TRUE to enable libtcmalloc.") +if (NOT $ENV{ENABLE_LIBTCMALLOC}) + set (ENABLE_LIBTCMALLOC FALSE) +endif () + +set (DEBUG_LIBTCMALLOC FALSE CACHE BOOL "Set to TRUE to use debug version of libtcmalloc.") +if ($ENV{DEBUG_LIBTCMALLOC}) + set (ENABLE_LIBTCMALLOC TRUE) +endif () if (GLIBC_COMPATIBILITY) - SET(GLIBC_COMPATIBILITY_COMPILE_FLAGS "-include ${ClickHouse_SOURCE_DIR}/libs/libcommon/include/common/glibc_compatibility.h") - SET(GLIBC_COMPATIBILITY_LINK_FLAGS "-Wl,--wrap=memcpy") -endif() + set (GLIBC_COMPATIBILITY_COMPILE_FLAGS "-include ${ClickHouse_SOURCE_DIR}/libs/libcommon/include/common/glibc_compatibility.h") + set (GLIBC_COMPATIBILITY_LINK_FLAGS "-Wl,--wrap=memcpy") +endif () -if (DISABLE_CXX11_ABI) - SET(CXX11_ABI "-D_GLIBCXX_USE_CXX11_ABI=0") -endif() +if (NOT ENABLE_CXX11_ABI) + set (CXX11_ABI "-D_GLIBCXX_USE_CXX11_ABI=0") +endif () -SET(CMAKE_BUILD_COLOR_MAKEFILE ON) -SET(CMAKE_CXX_FLAGS "-std=gnu++1y ${APPLE_EXTRA_CXX_FLAG} -fno-omit-frame-pointer ${COMMON_WARNING_FLAGS} ${CXX_WARNING_FLAGS} ${MACHINE_FLAGS} ${GLIBC_COMPATIBILITY_COMPILE_FLAGS} ${CXX11_ABI}") -SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") -SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g") -SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -ggdb3 -fno-inline") +set (CMAKE_BUILD_COLOR_MAKEFILE ON) +set (CMAKE_CXX_FLAGS "-std=gnu++1y ${APPLE_EXTRA_CXX_FLAG} -fno-omit-frame-pointer ${COMMON_WARNING_FLAGS} ${CXX_WARNING_FLAGS} ${MACHINE_FLAGS} ${GLIBC_COMPATIBILITY_COMPILE_FLAGS} ${CXX11_ABI}") +set (CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") +set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g") +set (CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -ggdb3 -fno-inline") -SET(CMAKE_C_FLAGS "-fno-omit-frame-pointer ${COMMON_WARNING_FLAGS} ${MACHINE_FLAGS} ${GLIBC_COMPATIBILITY_COMPILE_FLAGS} ${CXX11_ABI}") -SET(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG") -SET(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g") -SET(CMAKE_C_FLAGS_DEBUG "-O0 -g3 -ggdb3 -fno-inline") +set (CMAKE_C_FLAGS "-fno-omit-frame-pointer ${COMMON_WARNING_FLAGS} ${MACHINE_FLAGS} ${GLIBC_COMPATIBILITY_COMPILE_FLAGS} ${CXX11_ABI}") +set (CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG") +set (CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g") +set (CMAKE_C_FLAGS_DEBUG "-O0 -g3 -ggdb3 -fno-inline") if (NOT APPLE) - SET(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ ${GLIBC_COMPATIBILITY_LINK_FLAGS} ${CXX11_ABI}") -endif() + set (CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ ${GLIBC_COMPATIBILITY_LINK_FLAGS} ${CXX11_ABI}") +endif () # -fuse-ld=gold - fix linkage for gcc-5.4, gcc-6.1 -# see more in http://stackoverflow.com/questions/37603238/fsanitize-not-using-gold-linker-in-gcc-6-1 -SET(CMAKE_CXX_FLAGS_ASAN "-O3 -g -fsanitize=address -fno-omit-frame-pointer -fuse-ld=gold ${CXX11_ABI}") -SET(CMAKE_CXX_FLAGS_UBSAN "-O3 -g -fsanitize=undefined -fno-omit-frame-pointer ${CXX11_ABI}") -SET(CMAKE_C_FLAGS_ASAN "-O3 -g -fsanitize=address -fno-omit-frame-pointer -fuse-ld=gold ${CXX11_ABI}") -SET(CMAKE_C_FLAGS_UBSAN "-O3 -g -fsanitize=undefined -fno-omit-frame-pointer ${CXX11_ABI}") +# See more in http://stackoverflow.com/questions/37603238/fsanitize-not-using-gold-linker-in-gcc-6-1 +set (CMAKE_CXX_FLAGS_ASAN "-O3 -g -fsanitize=address -fno-omit-frame-pointer -fuse-ld=gold ${CXX11_ABI}") +set (CMAKE_CXX_FLAGS_UBSAN "-O3 -g -fsanitize=undefined -fno-omit-frame-pointer ${CXX11_ABI}") +set (CMAKE_C_FLAGS_ASAN "-O3 -g -fsanitize=address -fno-omit-frame-pointer -fuse-ld=gold ${CXX11_ABI}") +set (CMAKE_C_FLAGS_UBSAN "-O3 -g -fsanitize=undefined -fno-omit-frame-pointer ${CXX11_ABI}") -# Флаги для test coverage -IF (TEST_COVERAGE) - SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -fprofile-arcs -ftest-coverage -fPIC -DIS_DEBUG ${CXX11_ABI}") -ENDIF(TEST_COVERAGE) +# Flags for test coverage +if (TEST_COVERAGE) + set (CMAKE_CXX_FLAGS_DEBUG "-O0 -g -fprofile-arcs -ftest-coverage -fPIC -DIS_DEBUG ${CXX11_ABI}") +endif (TEST_COVERAGE) -# Собирать тесты? -IF (NOT DEFINED TESTS) - MESSAGE(STATUS "Tests are enabled") - SET(TESTS YES) -ENDIF() +# Run tests with "make check" +if (ENABLE_TESTS) + message (STATUS "Tests are enabled") + include (cmake/add_check.cmake) +endif (ENABLE_TESTS) -# тесты запускать с помощью "make check" -IF(TESTS) - INCLUDE(add.test.cmake) -ENDIF(TESTS) - -# Префикс для установки -SET(CMAKE_INSTALL_PREFIX /usr) +# Installation prefix +set (CMAKE_INSTALL_PREFIX /usr) include_directories (${ClickHouse_SOURCE_DIR}/contrib/libcityhash/include/) include_directories (${ClickHouse_SOURCE_DIR}/contrib/liblz4/include/) @@ -140,61 +155,30 @@ include_directories (${ClickHouse_SOURCE_DIR}/libs/libzkutil/include/) include_directories (${ClickHouse_SOURCE_DIR}/dbms/include) -include_directories (/usr/local/include/glib-2.0/) -include_directories (/usr/local/lib/glib-2.0/include/) -include_directories (/usr/include/glib-2.0/) -include_directories (/usr/lib64/glib-2.0/include/) - -IF (AARCH64) - include_directories (/usr/lib/aarch64-linux-gnu/glib-2.0/include/) -ELSE() - include_directories (/usr/lib/x86_64-linux-gnu/glib-2.0/include/) -ENDIF() - include_directories (/usr/local/include/) link_directories (/usr/local/lib) -# External libraries - -# 1. openssl -include_directories ("/usr/local/opt/openssl/include") -set (OPENSSL_HINTS "/usr/local/opt/openssl/lib") -find_library (LIBSSL libssl.a HINTS ${OPENSSL_HINTS}) -find_library (LIBCRYPTO libcrypto.a HINTS ${OPENSSL_HINTS}) -set (OPENSSL_LIBS ${LIBSSL} ${LIBCRYPTO}) - -# 2. icu4c -include_directories ("/usr/local/opt/icu4c/include") -set (ICU_HINTS "/usr/local/opt/icu4c/lib") -find_library (ICUI18N libicui18n.a HINTS ${ICU_HINTS}) -find_library (ICUUC libicuuc.a HINTS ${ICU_HINTS}) -find_library (ICUDATA libicudata.a HINTS ${ICU_HINTS}) -set (ICU_LIBS ${ICUI18N} ${ICUUC} ${ICUDATA}) - -# 3. boost -set (BOOST_HINTS "/usr/local/opt/boost/lib") -find_library (BOOST_PROGRAM_OPTIONS_LIB libboost_program_options.a HINTS ${BOOST_HINTS}) -find_library (BOOST_SYSTEM_LIB libboost_system.a HINTS ${BOOST_HINTS}) -find_library (BOOST_FILESYSTEM_LIB libboost_filesystem.a HINTS ${BOOST_HINTS}) -find_library (BOOST_REGEX_LIB libboost_regex.a HINTS ${BOOST_HINTS}) -find_library (BOOST_THREAD_LIB libboost_thread.a HINTS ${BOOST_HINTS}) - -# 4. ltdl -set (LTDL_HINTS "/usr/local/opt/libtool/lib") -find_library (LTDL_LIB libltdl.a HINTS ${LTDL_HINTS}) +include (cmake/find_openssl.cmake) +include (cmake/find_icu4c.cmake) +include (cmake/find_boost.cmake) +include (cmake/find_libtool.cmake) +include (cmake/find_libmysqlclient.cmake) +include (cmake/find_rt.cmake) # Directory for Yandex specific files -SET(CLICKHOUSE_PRIVATE_DIR ${ClickHouse_SOURCE_DIR}/private/) +set (CLICKHOUSE_PRIVATE_DIR ${ClickHouse_SOURCE_DIR}/private/) add_subdirectory (contrib) add_subdirectory (libs) add_subdirectory (utils) add_subdirectory (dbms) -IF (EXISTS ${CLICKHOUSE_PRIVATE_DIR}) +if (EXISTS ${CLICKHOUSE_PRIVATE_DIR}) add_subdirectory (private) -ENDIF() +endif () -message(STATUS "C_FLAGS: =${CMAKE_C_FLAGS}") -message(STATUS "CXX_FLAGS:=${CMAKE_CXX_FLAGS}") +message (STATUS "C_FLAGS: =${CMAKE_C_FLAGS}") +message (STATUS "CXX_FLAGS:=${CMAKE_CXX_FLAGS}") + +include (cmake/print_include_directories.cmake) diff --git a/MacOS.md b/MacOS.md index 128a3c0e440..23457877567 100644 --- a/MacOS.md +++ b/MacOS.md @@ -1,6 +1,6 @@ -==How to increase maxfiles on MacOS X== +## How to increase maxfiles on Mac OS X -To increase maxfiles on MacOS create the following file: +To increase maxfiles on MacOS, create the following file: (Note: you'll need to use sudo) @@ -29,11 +29,11 @@ To increase maxfiles on MacOS create the following file: ``` -execute the following command: +Execute the following command: ``` sudo chown root:wheel /Library/LaunchDaemons/limit.maxfiles.plist ``` Reboot. -To check if it's working, you can use 'ulimit -n' command +To check if it's working, you can use `ulimit -n` command. diff --git a/add.test.cmake b/add.test.cmake deleted file mode 100644 index 0585f0951d4..00000000000 --- a/add.test.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# добавляем вывод программы при ошибке теста -enable_testing() -if (CMAKE_CONFIGURATION_TYPES) - add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} - --force-new-ctest-process --output-on-failure - --build-config "$") -else() - add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} - --force-new-ctest-process --output-on-failure) -endif() - -macro (add_check target) - add_test(NAME test_${target} COMMAND ${target} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - add_dependencies(check ${target}) -endmacro (add_check) \ No newline at end of file diff --git a/cmake/add_check.cmake b/cmake/add_check.cmake new file mode 100644 index 00000000000..24889fb9473 --- /dev/null +++ b/cmake/add_check.cmake @@ -0,0 +1,15 @@ +# Adding test output on failure +enable_testing () +if (CMAKE_CONFIGURATION_TYPES) + add_custom_target (check COMMAND ${CMAKE_CTEST_COMMAND} + --force-new-ctest-process --output-on-failure + --build-config "$") +else () + add_custom_target (check COMMAND ${CMAKE_CTEST_COMMAND} + --force-new-ctest-process --output-on-failure) +endif () + +macro (add_check target) + add_test (NAME test_${target} COMMAND ${target} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + add_dependencies (check ${target}) +endmacro (add_check) diff --git a/cmake/find_boost.cmake b/cmake/find_boost.cmake new file mode 100644 index 00000000000..2b0b60827f0 --- /dev/null +++ b/cmake/find_boost.cmake @@ -0,0 +1,20 @@ +set (Boost_USE_STATIC_LIBS ${USE_STATIC_LIBRARIES}) +set (BOOST_ROOT "/usr/local") +find_package (Boost 1.57 COMPONENTS program_options system filesystem regex thread) +if (NOT Boost_FOUND) + # Try to find manually. + set (BOOST_HINTS "") + if (USE_STATIC_LIBRARIES) + find_library (Boost_PROGRAM_OPTIONS_LIBRARY libboost_program_options.a HINTS ${BOOST_HINTS}) + find_library (Boost_SYSTEM_LIBRARY libboost_system.a HINTS ${BOOST_HINTS}) + find_library (Boost_FILESYSTEM_LIBRARY libboost_filesystem.a HINTS ${BOOST_HINTS}) + find_library (Boost_REGEX_LIBRARY libboost_regex.a HINTS ${BOOST_HINTS}) + find_library (Boost_THREAD_LIBRARY libboost_thread.a HINTS ${BOOST_HINTS}) + else () + find_library (Boost_PROGRAM_OPTIONS_LIBRARY boost_program_options HINTS ${BOOST_HINTS}) + find_library (Boost_SYSTEM_LIBRARY boost_system HINTS ${BOOST_HINTS}) + find_library (Boost_FILESYSTEM_LIBRARY boost_filesystem HINTS ${BOOST_HINTS}) + find_library (Boost_REGEX_LIBRARY boost_regex HINTS ${BOOST_HINTS}) + find_library (Boost_THREAD_LIBRARY boost_thread HINTS ${BOOST_HINTS}) + endif () +endif () diff --git a/cmake/find_glib.cmake b/cmake/find_glib.cmake new file mode 100644 index 00000000000..3e7dd005e7b --- /dev/null +++ b/cmake/find_glib.cmake @@ -0,0 +1,39 @@ +set (GLIB_HINTS "/usr/local/opt/glib/lib") +set (GLIB_INCLUDE_HINTS + "/usr/local/include/glib-2.0/" + "/usr/local/lib/glib-2.0/include/" + "/usr/include/glib-2.0/" + "/usr/lib64/glib-2.0/include/") +if (AARCH64) + set (GLIB_INCLUDE_HINTS ${GLIB_INCLUDE_HINTS} "/usr/lib/aarch64-linux-gnu/glib-2.0/include/") +else () + set (GLIB_INCLUDE_HINTS ${GLIB_INCLUDE_HINTS} "/usr/lib/x86_64-linux-gnu/glib-2.0/include/") +endif () +if (USE_STATIC_LIBRARIES) + find_library (GLIB_LIB libglib-2.0.a HINTS ${GLIB_HINTS}) +else () + find_library (GLIB_LIB glib-2.0 HINTS ${GLIB_HINTS}) +endif () +find_path (GLIB_INCLUDE_DIR NAMES glib.h HINTS ${GLIB_INCLUDE_HINTS}) +find_path (GLIB_CONFIG_INCLUDE_DIR NAMES glibconfig.h HINTS ${GLIB_INCLUDE_HINTS}) +include_directories (${GLIB_INCLUDE_DIR}) +include_directories (${GLIB_CONFIG_INCLUDE_DIR}) +if (APPLE) + set (INTL_HINTS "/usr/local/opt/gettext/lib") + if (USE_STATIC_LIBRARIES) + find_library (INTL_LIB libintl.a HINTS ${INTL_HINTS}) + else () + find_library (INTL_LIB intl HINTS ${INTL_HINTS}) + endif () + set (ICONV_HINTS "/usr/local/opt/libiconv/lib") + if (USE_STATIC_LIBRARIES) + find_library (ICONV_LIB libiconv.a HINTS ${ICONV_HINTS}) + else () + find_library (ICONV_LIB iconv HINTS ${ICONV_HINTS}) + endif () + find_library (CORE_FOUNDATION_LIB CoreFoundation) + find_library (CARBON_LIB Carbon) + set (GLIB_LIBS ${GLIB_LIB} ${INTL_LIB} ${ICONV_LIB} ${CORE_FOUNDATION_LIB} ${CARBON_LIB}) +else (APPLE) + set (GLIB_LIBS ${GLIB_LIB}) +endif (APPLE) diff --git a/cmake/find_icu4c.cmake b/cmake/find_icu4c.cmake new file mode 100644 index 00000000000..ec8287fdf7d --- /dev/null +++ b/cmake/find_icu4c.cmake @@ -0,0 +1,12 @@ +include_directories ("/usr/local/opt/icu4c/include") +set (ICU_HINTS "/usr/local/opt/icu4c/lib") +if (USE_STATIC_LIBRARIES) + find_library (ICUI18N libicui18n.a HINTS ${ICU_HINTS}) + find_library (ICUUC libicuuc.a HINTS ${ICU_HINTS}) + find_library (ICUDATA libicudata.a HINTS ${ICU_HINTS}) +else () + find_library (ICUI18N icui18n HINTS ${ICU_HINTS}) + find_library (ICUUC icuuc HINTS ${ICU_HINTS}) + find_library (ICUDATA icudata HINTS ${ICU_HINTS}) +endif () +set (ICU_LIBS ${ICUI18N} ${ICUUC} ${ICUDATA}) diff --git a/cmake/find_libmysqlclient.cmake b/cmake/find_libmysqlclient.cmake new file mode 100644 index 00000000000..10c18a14890 --- /dev/null +++ b/cmake/find_libmysqlclient.cmake @@ -0,0 +1,9 @@ +set (MYSQL_HINTS "/usr/local/opt/mysql/lib") +set (MYSQL_INCLUDE_HINTS "/usr/local/opt/mysql/include") +if (USE_STATIC_LIBRARIES) + find_library (STATIC_MYSQLCLIENT_LIB libmysqlclient.a HINTS ${MYSQL_HINTS}) +else () + find_library (MYSQLCLIENT_LIB mysqlclient HINTS ${MYSQL_HINTS}) +endif () +find_path (MYSQL_INCLUDE_DIR NAMES mysql.h PATH_SUFFIXES mysql HINTS ${MYSQL_INCLUDE_HINTS}) +include_directories (${MYSQL_INCLUDE_DIR}) diff --git a/cmake/find_libtool.cmake b/cmake/find_libtool.cmake new file mode 100644 index 00000000000..01696c1b64c --- /dev/null +++ b/cmake/find_libtool.cmake @@ -0,0 +1,6 @@ +set (LTDL_HINTS "/usr/local/opt/libtool/lib") +if (USE_STATIC_LIBRARIES) + find_library (LTDL_LIB libltdl.a HINTS ${LTDL_HINTS}) +else () + find_library (LTDL_LIB ltdl HINTS ${LTDL_HINTS}) +endif () diff --git a/cmake/find_openssl.cmake b/cmake/find_openssl.cmake new file mode 100644 index 00000000000..4a09a59f709 --- /dev/null +++ b/cmake/find_openssl.cmake @@ -0,0 +1,21 @@ +set (OPENSSL_USE_STATIC_LIBS ${USE_STATIC_LIBRARIES}) +if (APPLE) + set (OPENSSL_ROOT_DIR "/usr/local/opt/openssl") +endif () +find_package (OpenSSL) +if (OPENSSL_FOUND) + include_directories (${OPENSSL_INCLUDE_DIR}) +endif () +if (NOT OPENSSL_FOUND) + # Try to find manually. + include_directories ("/usr/local/opt/openssl/include") + set (OPENSSL_HINTS "/usr/local/opt/openssl/lib") + if (USE_STATIC_LIBRARIES) + find_library (OPENSSL_SSL_LIBRARY libssl.a HINTS ${OPENSSL_HINTS}) + find_library (OPENSSL_CRYPTO_LIBRARY libcrypto.a HINTS ${OPENSSL_HINTS}) + else () + find_library (OPENSSL_SSL_LIBRARY ssl HINTS ${OPENSSL_HINTS}) + find_library (OPENSSL_CRYPTO_LIBRARY crypto HINTS ${OPENSSL_HINTS}) + endif () + set (OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}) +endif () diff --git a/cmake/find_rt.cmake b/cmake/find_rt.cmake new file mode 100644 index 00000000000..58c6888fff5 --- /dev/null +++ b/cmake/find_rt.cmake @@ -0,0 +1,16 @@ +if (APPLE) + set (RT_LIBRARIES "apple_rt") +else () + if (USE_STATIC_LIBRARIES) + set (RT_LIBRARIES "librt.a") + else () + set (RT_LIBRARIES "rt") + endif () +endif () + +function (target_link_rt_by_force TARGET) + if (NOT APPLE) + set (FLAGS "-Wl,-no-as-needed -lrt -Wl,-as-needed") + set_property (TARGET ${TARGET} APPEND PROPERTY LINK_FLAGS "${FLAGS}") + endif () +endfunction () diff --git a/cmake/print_include_directories.cmake b/cmake/print_include_directories.cmake new file mode 100644 index 00000000000..82a2306fb91 --- /dev/null +++ b/cmake/print_include_directories.cmake @@ -0,0 +1,6 @@ +get_property (dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) +file (WRITE ${CMAKE_CURRENT_BINARY_DIR}/include_directories.txt "") +foreach (dir ${dirs}) + string (REPLACE "${ClickHouse_SOURCE_DIR}" "." dir "${dir}") + file (APPEND ${CMAKE_CURRENT_BINARY_DIR}/include_directories.txt "-I ${dir} ") +endforeach () diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 81d79f06735..313bd1f296e 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -1,4 +1,4 @@ -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-old-style-cast") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-old-style-cast") add_subdirectory (libcityhash) add_subdirectory (liblz4) @@ -10,10 +10,10 @@ add_subdirectory (libpoco) add_subdirectory (libre2) add_subdirectory (libzookeeper) -if (NOT DEFINED $ENV{DISABLE_LIBTCMALLOC}) +if (ENABLE_LIBTCMALLOC) add_subdirectory (libtcmalloc) -endif() +endif () -IF (NOT AARCH64) +if (NOT AARCH64) add_subdirectory (libcpuid) -ENDIF() +endif () diff --git a/contrib/libmetrohash/CMakeLists.txt b/contrib/libmetrohash/CMakeLists.txt index 6ce50e2de42..e69eb6fac39 100644 --- a/contrib/libmetrohash/CMakeLists.txt +++ b/contrib/libmetrohash/CMakeLists.txt @@ -1,8 +1,8 @@ include_directories (${CMAKE_CURRENT_BINARY_DIR}) -IF (NOT AARCH64) # Не используется. Портировать не сложно. - SET(SOURCES_ONLY_ON_X86_64 src/metrohash128crc.cpp) -ENDIF() +if (NOT AARCH64) # Not used. Pretty easy to port. + set (SOURCES_ONLY_ON_X86_64 src/metrohash128crc.cpp) +endif () add_library(metrohash src/metrohash.h diff --git a/contrib/libpoco/CMakeLists.txt b/contrib/libpoco/CMakeLists.txt index 4808bc4cee0..52922bba08f 100644 --- a/contrib/libpoco/CMakeLists.txt +++ b/contrib/libpoco/CMakeLists.txt @@ -5,8 +5,8 @@ cmake_minimum_required(VERSION 2.8.0) # POCO_UNBUNDLED # POCO_NO_LOCALE # -# ENABLE_{COMPONENT} -# ENABLE_TESTS +# POCO_ENABLE_{COMPONENT} +# POCO_ENABLE_TESTS project(Poco) @@ -68,30 +68,30 @@ set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Set debug library postfix" FORCE) include(PocoMacros) # Allow enabling and disabling components -option(ENABLE_XML "Enable the XML" ON) -option(ENABLE_JSON "Enable the JSON" ON) -option(ENABLE_MONGODB "Enable MongoDB" ON) -option(ENABLE_PDF "Enable PDF" OFF) -option(ENABLE_UTIL "Enable Util" ON) -option(ENABLE_NET "Enable Net" ON) -option(ENABLE_NETSSL "Enable NetSSL" ON) -option(ENABLE_NETSSL_WIN "Enable NetSSL Windows" OFF) -option(ENABLE_CRYPTO "Enable Crypto" ON) -option(ENABLE_DATA "Enable Data" ON) -option(ENABLE_DATA_SQLITE "Enable Data SQlite" OFF) -option(ENABLE_DATA_MYSQL "Enable Data MySQL" OFF) -option(ENABLE_DATA_ODBC "Enable Data ODBC" ON) -option(ENABLE_SEVENZIP "Enable SevenZip" OFF) -option(ENABLE_ZIP "Enable Zip" ON) -option(ENABLE_APACHECONNECTOR "Enable ApacheConnector" OFF) -option(ENABLE_CPPPARSER "Enable C++ parser" OFF) -option(ENABLE_POCODOC "Enable Poco Documentation Generator" OFF) -option(ENABLE_PAGECOMPILER "Enable PageCompiler" ON) -option(ENABLE_PAGECOMPILER_FILE2PAGE "Enable File2Page" ON) +option(POCO_ENABLE_XML "Enable the XML" ON) +option(POCO_ENABLE_JSON "Enable the JSON" ON) +option(POCO_ENABLE_MONGODB "Enable MongoDB" ON) +option(POCO_ENABLE_PDF "Enable PDF" OFF) +option(POCO_ENABLE_UTIL "Enable Util" ON) +option(POCO_ENABLE_NET "Enable Net" ON) +option(POCO_ENABLE_NETSSL "Enable NetSSL" ON) +option(POCO_ENABLE_NETSSL_WIN "Enable NetSSL Windows" OFF) +option(POCO_ENABLE_CRYPTO "Enable Crypto" ON) +option(POCO_ENABLE_DATA "Enable Data" ON) +option(POCO_ENABLE_DATA_SQLITE "Enable Data SQlite" OFF) +option(POCO_ENABLE_DATA_MYSQL "Enable Data MySQL" OFF) +option(POCO_ENABLE_DATA_ODBC "Enable Data ODBC" ON) +option(POCO_ENABLE_SEVENZIP "Enable SevenZip" OFF) +option(POCO_ENABLE_ZIP "Enable Zip" ON) +option(POCO_ENABLE_APACHECONNECTOR "Enable ApacheConnector" OFF) +option(POCO_ENABLE_CPPPARSER "Enable C++ parser" OFF) +option(POCO_ENABLE_POCODOC "Enable Poco Documentation Generator" OFF) +option(POCO_ENABLE_PAGECOMPILER "Enable PageCompiler" ON) +option(POCO_ENABLE_PAGECOMPILER_FILE2PAGE "Enable File2Page" ON) option(FORCE_OPENSSL "Force usage of OpenSSL even under windows" OFF) -option(ENABLE_TESTS +option(POCO_ENABLE_TESTS "Set to OFF|ON (default is OFF) to control build of POCO tests & samples" OFF) option(POCO_STATIC @@ -101,28 +101,28 @@ option(POCO_UNBUNDLED "Set to OFF|ON (default is OFF) to control linking dependencies as external" OFF) # Uncomment from next two lines to force statitc or dynamic library, default is autodetection -if(POCO_STATIC) - add_definitions( -DPOCO_STATIC -DPOCO_NO_AUTOMATIC_LIBS) - set( LIB_MODE STATIC ) - message(STATUS "Building static libraries") -else(POCO_STATIC) - set( LIB_MODE SHARED ) - message(STATUS "Building dynamic libraries") -endif(POCO_STATIC) +if (POCO_STATIC) + add_definitions( -DPOCO_STATIC -DPOCO_NO_AUTOMATIC_LIBS) + set( LIB_MODE STATIC ) + message(STATUS "Building static libraries") +else (POCO_STATIC) + set( LIB_MODE SHARED ) + message(STATUS "Building dynamic libraries") +endif (POCO_STATIC) -if (ENABLE_TESTS) - include(CTest) - enable_testing() - message(STATUS "Building with unittests & samples") +if (POCO_ENABLE_TESTS) + include(CTest) + enable_testing() + message(STATUS "Building with unittests & samples") else () - message(STATUS "Building without tests & samples") + message(STATUS "Building without tests & samples") endif () if (POCO_UNBUNDLED) - add_definitions( -DPOCO_UNBUNDLED) - message(STATUS "Build with using external sqlite, libz, pcre, expat ...") + add_definitions( -DPOCO_UNBUNDLED) + message(STATUS "Build with using external sqlite, libz, pcre, expat ...") else () - message(STATUS "Build with using internal copy of sqlite, libz, pcre, expat, ...") + message(STATUS "Build with using internal copy of sqlite, libz, pcre, expat, ...") endif () include(CheckTypeSize) @@ -130,34 +130,34 @@ find_package(Cygwin) # OS Detection if(WIN32) - add_definitions( -DPOCO_OS_FAMILY_WINDOWS -DUNICODE -D_UNICODE) - #set(SYSLIBS iphlpapi gdi32 odbc32) + add_definitions( -DPOCO_OS_FAMILY_WINDOWS -DUNICODE -D_UNICODE) + #set(SYSLIBS iphlpapi gdi32 odbc32) endif(WIN32) if (UNIX AND NOT ANDROID ) - add_definitions( -DPOCO_OS_FAMILY_UNIX ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-private-field -Wno-unused-local-typedef -Wno-for-loop-analysis -Wno-unknown-pragmas -Wno-unused-variable") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unknown-pragmas -Wno-unused-variable") - # Standard 'must be' defines - if (APPLE) - add_definitions( -DPOCO_HAVE_IPv6 -DPOCO_NO_STAT64) - set(SYSLIBS dl) - else (APPLE) - add_definitions(-D_XOPEN_SOURCE=500 -D_REENTRANT -D_THREAD_SAFE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -DPOCO_HAVE_FD_EPOLL -DPOCO_HAVE_IPv6) - set(SYSLIBS pthread dl rt) - endif (APPLE) + add_definitions( -DPOCO_OS_FAMILY_UNIX ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-private-field -Wno-unused-local-typedef -Wno-for-loop-analysis -Wno-unknown-pragmas -Wno-unused-variable") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unknown-pragmas -Wno-unused-variable") + # Standard 'must be' defines + if (APPLE) + add_definitions( -DPOCO_HAVE_IPv6 -DPOCO_NO_STAT64) + set(SYSLIBS dl) + else (APPLE) + add_definitions(-D_XOPEN_SOURCE=500 -D_REENTRANT -D_THREAD_SAFE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -DPOCO_HAVE_FD_EPOLL -DPOCO_HAVE_IPv6) + set(SYSLIBS pthread dl rt) + endif (APPLE) endif(UNIX AND NOT ANDROID ) if (CMAKE_SYSTEM MATCHES "SunOS") - add_definitions( -DPOCO_OS_FAMILY_UNIX ) - # Standard 'must be' defines - add_definitions( -D_XOPEN_SOURCE=500 -D_REENTRANT -D_THREAD_SAFE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 ) - set(SYSLIBS pthread socket xnet nsl resolv rt dl) + add_definitions( -DPOCO_OS_FAMILY_UNIX ) + # Standard 'must be' defines + add_definitions( -D_XOPEN_SOURCE=500 -D_REENTRANT -D_THREAD_SAFE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 ) + set(SYSLIBS pthread socket xnet nsl resolv rt dl) endif(CMAKE_SYSTEM MATCHES "SunOS") if (CMAKE_COMPILER_IS_MINGW) - add_definitions(-DWC_NO_BEST_FIT_CHARS=0x400 -DPOCO_WIN32_UTF8) - add_definitions(-D_WIN32 -DMINGW32 -DWINVER=0x500 -DODBCVER=0x0300 -DPOCO_THREAD_STACK_SIZE) + add_definitions(-DWC_NO_BEST_FIT_CHARS=0x400 -DPOCO_WIN32_UTF8) + add_definitions(-D_WIN32 -DMINGW32 -DWINVER=0x500 -DODBCVER=0x0300 -DPOCO_THREAD_STACK_SIZE) endif (CMAKE_COMPILER_IS_MINGW) if (CYGWIN) @@ -166,84 +166,84 @@ endif (CYGWIN) # SunPro C++ if (${CMAKE_CXX_COMPILER_ID} MATCHES "SunPro") - add_definitions( -D_BSD_SOURCE -library=stlport4) + add_definitions( -D_BSD_SOURCE -library=stlport4) endif (${CMAKE_CXX_COMPILER_ID} MATCHES "SunPro") # iOS if (IOS) - add_definitions( -DPOCO_HAVE_IPv6 -DPOCO_NO_FPENVIRONMENT -DPOCO_NO_STAT64 -DPOCO_NO_SHAREDLIBS -DPOCO_NO_NET_IFTYPES ) -endif(IOS) + add_definitions( -DPOCO_HAVE_IPv6 -DPOCO_NO_FPENVIRONMENT -DPOCO_NO_STAT64 -DPOCO_NO_SHAREDLIBS -DPOCO_NO_NET_IFTYPES ) +endif (IOS) #Android if (ANDROID) - add_definitions( -DPOCO_ANDROID -DPOCO_NO_FPENVIRONMENT -DPOCO_NO_WSTRING -DPOCO_NO_SHAREDMEMORY ) -endif(ANDROID) + add_definitions( -DPOCO_ANDROID -DPOCO_NO_FPENVIRONMENT -DPOCO_NO_WSTRING -DPOCO_NO_SHAREDMEMORY ) +endif (ANDROID) # Collect the built libraries and include dirs, the will be used to create the PocoConfig.cmake file -set(Poco_COMPONENTS "") +set (Poco_COMPONENTS "") -if (ENABLE_TESTS) - add_subdirectory(CppUnit) +if (POCO_ENABLE_TESTS) + add_subdirectory (CppUnit) endif () -add_subdirectory(Foundation) -if(ENABLE_XML) -add_subdirectory(XML) -list(APPEND Poco_COMPONENTS "XML") -endif() -if(ENABLE_JSON) -add_subdirectory(JSON) -list(APPEND Poco_COMPONENTS "JSON") -endif() -if(ENABLE_MONGODB) -add_subdirectory(MongoDB) -list(APPEND Poco_COMPONENTS "MongoDB") -endif() -if(ENABLE_PDF) -add_subdirectory(PDF) -list(APPEND Poco_COMPONENTS "PDF") -endif() -if(ENABLE_UTIL) -add_subdirectory(Util) -list(APPEND Poco_COMPONENTS "Util") -endif() -if(ENABLE_NET) -add_subdirectory(Net) -list(APPEND Poco_COMPONENTS "Net") +add_subdirectory (Foundation) +if (POCO_ENABLE_XML) + add_subdirectory (XML) + list (APPEND Poco_COMPONENTS "XML") +endif () +if (POCO_ENABLE_JSON) + add_subdirectory (JSON) + list (APPEND Poco_COMPONENTS "JSON") +endif () +if (POCO_ENABLE_MONGODB) + add_subdirectory (MongoDB) + list (APPEND Poco_COMPONENTS "MongoDB") +endif () +if (POCO_ENABLE_PDF) + add_subdirectory (PDF) + list (APPEND Poco_COMPONENTS "PDF") endif() +if (POCO_ENABLE_UTIL) + add_subdirectory (Util) + list (APPEND Poco_COMPONENTS "Util") +endif () +if (POCO_ENABLE_NET) + add_subdirectory (Net) + list (APPEND Poco_COMPONENTS "Net") +endif () #NetSSL -if(WIN32 AND ENABLE_NETSSL_WIN) +if(WIN32 AND POCO_ENABLE_NETSSL_WIN) add_subdirectory(NetSSL_Win) list(APPEND Poco_COMPONENTS "NetSSL_Win") -endif(WIN32 AND ENABLE_NETSSL_WIN) +endif(WIN32 AND POCO_ENABLE_NETSSL_WIN) find_package(OpenSSL) if(OPENSSL_FOUND) include_directories("${OPENSSL_INCLUDE_DIR}") - if(ENABLE_NETSSL) + if(POCO_ENABLE_NETSSL) add_subdirectory(NetSSL_OpenSSL) list(APPEND Poco_COMPONENTS "NetSSL_OpenSSL") endif() - if(ENABLE_CRYPTO) + if(POCO_ENABLE_CRYPTO) add_subdirectory(Crypto) list(APPEND Poco_COMPONENTS "Crypto") endif() endif(OPENSSL_FOUND) -if(ENABLE_DATA) +if(POCO_ENABLE_DATA) add_subdirectory(Data) list(APPEND Poco_COMPONENTS "Data") endif() -if(ENABLE_SEVENZIP) +if(POCO_ENABLE_SEVENZIP) add_subdirectory(SevenZip) list(APPEND Poco_COMPONENTS "SevenZip") endif() -if(ENABLE_ZIP) +if(POCO_ENABLE_ZIP) add_subdirectory(Zip) list(APPEND Poco_COMPONENTS "Zip") endif() @@ -252,28 +252,28 @@ find_package(APR) find_package(Apache2) if(APRUTIL_FOUND AND APACHE_FOUND) include_directories( "${APACHE_INCLUDE_DIR}" "${APRUTIL_INCLUDE_DIR}" ) - if(ENABLE_APACHECONNECTOR) + if(POCO_ENABLE_APACHECONNECTOR) add_subdirectory(ApacheConnector) list(APPEND Poco_COMPONENTS "ApacheConnector") endif() endif(APRUTIL_FOUND AND APACHE_FOUND) -if(ENABLE_CPPPARSER) +if(POCO_ENABLE_CPPPARSER) add_subdirectory(CppParser) list(APPEND Poco_COMPONENTS "CppParser") endif() -if(ENABLE_POCODOC) +if(POCO_ENABLE_POCODOC) add_subdirectory(PocoDoc) list(APPEND Poco_COMPONENTS "PocoDoc") endif() -if(ENABLE_PAGECOMPILER) +if(POCO_ENABLE_PAGECOMPILER) add_subdirectory(PageCompiler) list(APPEND Poco_COMPONENTS "PageCompiler") endif() -if(ENABLE_PAGECOMPILER_FILE2PAGE) +if(POCO_ENABLE_PAGECOMPILER_FILE2PAGE) add_subdirectory(PageCompiler/File2Page) list(APPEND Poco_COMPONENTS "File2Page") endif() diff --git a/contrib/libpoco/Crypto/CMakeLists.txt b/contrib/libpoco/Crypto/CMakeLists.txt index f69f9e9ce30..84b914893ea 100644 --- a/contrib/libpoco/Crypto/CMakeLists.txt +++ b/contrib/libpoco/Crypto/CMakeLists.txt @@ -21,7 +21,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoFoundation ${OPENSSL_LIBRARIES} ) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/Data/CMakeLists.txt b/contrib/libpoco/Data/CMakeLists.txt index a997f58d4c4..e8dbfcb18c4 100644 --- a/contrib/libpoco/Data/CMakeLists.txt +++ b/contrib/libpoco/Data/CMakeLists.txt @@ -28,7 +28,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoFoundation) -if(ENABLE_DATA_MYSQL) +if(POCO_ENABLE_DATA_MYSQL) find_package(MySQL) if(MYSQL_FOUND) include_directories("${MYSQL_INCLUDE_DIR}") @@ -37,9 +37,9 @@ if(ENABLE_DATA_MYSQL) else() message(STATUS "MySQL Support Disabled - no MySQL library") endif(MYSQL_FOUND) -endif(ENABLE_DATA_MYSQL) +endif(POCO_ENABLE_DATA_MYSQL) -if(ENABLE_DATA_ODBC) +if(POCO_ENABLE_DATA_ODBC) find_package(ODBC) if(WIN32 AND NOT WINCE) set(ODBC_LIBRARIES "odbc32" "odbccp32") @@ -54,9 +54,9 @@ if(ENABLE_DATA_ODBC) message(STATUS "ODBC Support Disabled - no ODBC runtime") endif() endif(WIN32 AND NOT WINCE) -endif(ENABLE_DATA_ODBC) +endif(POCO_ENABLE_DATA_ODBC) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/Data/MySQL/CMakeLists.txt b/contrib/libpoco/Data/MySQL/CMakeLists.txt index 899eb3e5623..8b7f84db097 100644 --- a/contrib/libpoco/Data/MySQL/CMakeLists.txt +++ b/contrib/libpoco/Data/MySQL/CMakeLists.txt @@ -21,6 +21,6 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoFoundation PocoData ${MYSQL_LIB}) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/Data/ODBC/CMakeLists.txt b/contrib/libpoco/Data/ODBC/CMakeLists.txt index 92effcd09a7..83943722ab2 100644 --- a/contrib/libpoco/Data/ODBC/CMakeLists.txt +++ b/contrib/libpoco/Data/ODBC/CMakeLists.txt @@ -21,6 +21,6 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoFoundation PocoData ${ODBC_LIBRARIES}) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/Foundation/CMakeLists.txt b/contrib/libpoco/Foundation/CMakeLists.txt index c6f83570111..2bf2d979197 100644 --- a/contrib/libpoco/Foundation/CMakeLists.txt +++ b/contrib/libpoco/Foundation/CMakeLists.txt @@ -114,7 +114,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" ${SYSLIBS}) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory( samples ) add_subdirectory( testsuite ) endif () diff --git a/contrib/libpoco/JSON/CMakeLists.txt b/contrib/libpoco/JSON/CMakeLists.txt index a70ad18925e..d2b0c8b36f5 100644 --- a/contrib/libpoco/JSON/CMakeLists.txt +++ b/contrib/libpoco/JSON/CMakeLists.txt @@ -19,7 +19,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoFoundation) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/MongoDB/CMakeLists.txt b/contrib/libpoco/MongoDB/CMakeLists.txt index 9c1a01e677a..2593c4c9a6f 100644 --- a/contrib/libpoco/MongoDB/CMakeLists.txt +++ b/contrib/libpoco/MongoDB/CMakeLists.txt @@ -19,7 +19,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoNet PocoFoundation) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/Net/CMakeLists.txt b/contrib/libpoco/Net/CMakeLists.txt index de9e61873a9..2b791069894 100644 --- a/contrib/libpoco/Net/CMakeLists.txt +++ b/contrib/libpoco/Net/CMakeLists.txt @@ -30,7 +30,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoFoundation ${SYSLIBS}) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/NetSSL_OpenSSL/CMakeLists.txt b/contrib/libpoco/NetSSL_OpenSSL/CMakeLists.txt index 8ad82db691f..3dee666152b 100644 --- a/contrib/libpoco/NetSSL_OpenSSL/CMakeLists.txt +++ b/contrib/libpoco/NetSSL_OpenSSL/CMakeLists.txt @@ -19,7 +19,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoCrypto PocoNet PocoUtil PocoFoundation ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY} ) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/Util/CMakeLists.txt b/contrib/libpoco/Util/CMakeLists.txt index 6742f989720..6c3b6fc5c50 100644 --- a/contrib/libpoco/Util/CMakeLists.txt +++ b/contrib/libpoco/Util/CMakeLists.txt @@ -24,18 +24,18 @@ set_target_properties( "${LIBNAME}" ) target_link_libraries( "${LIBNAME}" PocoFoundation) -if (ENABLE_XML) +if (POCO_ENABLE_XML) target_link_libraries( "${LIBNAME}" PocoXML) else () add_definitions( -DPOCO_UTIL_NO_XMLCONFIGURATION ) endif() -if (ENABLE_JSON) +if (POCO_ENABLE_JSON) target_link_libraries( "${LIBNAME}" PocoJSON) else () add_definitions( -DPOCO_UTIL_NO_JSONCONFIGURATION ) endif() -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/XML/CMakeLists.txt b/contrib/libpoco/XML/CMakeLists.txt index 1bf380089cc..7acadb10004 100644 --- a/contrib/libpoco/XML/CMakeLists.txt +++ b/contrib/libpoco/XML/CMakeLists.txt @@ -42,7 +42,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" ${SYSLIBS} PocoFoundation) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libpoco/Zip/CMakeLists.txt b/contrib/libpoco/Zip/CMakeLists.txt index 9ce7c5ca115..bd372839f66 100644 --- a/contrib/libpoco/Zip/CMakeLists.txt +++ b/contrib/libpoco/Zip/CMakeLists.txt @@ -19,7 +19,7 @@ set_target_properties( "${LIBNAME}" target_link_libraries( "${LIBNAME}" PocoUtil PocoXML PocoFoundation) -if (ENABLE_TESTS) +if (POCO_ENABLE_TESTS) add_subdirectory(samples) add_subdirectory(testsuite) endif () diff --git a/contrib/libre2/CMakeLists.txt b/contrib/libre2/CMakeLists.txt index e3a3a0d1d39..93bbfee81b4 100644 --- a/contrib/libre2/CMakeLists.txt +++ b/contrib/libre2/CMakeLists.txt @@ -1,59 +1,56 @@ -SET(re2_headers -./re2/tostring.cc -./re2/dfa.cc -./re2/prefilter.cc -./re2/compile.cc -./re2/regexp.cc -./re2/onepass.cc -./re2/prefilter_tree.cc -./re2/set.cc -./re2/filtered_re2.cc -./re2/perl_groups.cc -./re2/parse.cc -./re2/nfa.cc -./re2/bitstate.cc -./re2/simplify.cc -./re2/unicode_groups.cc -./re2/mimics_pcre.cc -./re2/re2.cc -./re2/prog.cc -./re2/unicode_casefold.cc -./util/test.cc -./util/strutil.cc -./util/stringpiece.cc -./util/hash.cc -./util/arena.cc -./util/benchmark.cc -./util/valgrind.cc -./util/pcre.cc -./util/stringprintf.cc -./util/rune.cc -./util/random.cc -./util/thread.cc +set (re2_headers + ./re2/tostring.cc + ./re2/dfa.cc + ./re2/prefilter.cc + ./re2/compile.cc + ./re2/regexp.cc + ./re2/onepass.cc + ./re2/prefilter_tree.cc + ./re2/set.cc + ./re2/filtered_re2.cc + ./re2/perl_groups.cc + ./re2/parse.cc + ./re2/nfa.cc + ./re2/bitstate.cc + ./re2/simplify.cc + ./re2/unicode_groups.cc + ./re2/mimics_pcre.cc + ./re2/re2.cc + ./re2/prog.cc + ./re2/unicode_casefold.cc + ./util/test.cc + ./util/strutil.cc + ./util/stringpiece.cc + ./util/hash.cc + ./util/arena.cc + ./util/benchmark.cc + ./util/valgrind.cc + ./util/pcre.cc + ./util/stringprintf.cc + ./util/rune.cc + ./util/random.cc + ./util/thread.cc ) -# Смысл в том, чтобы собрать две версии библиотеки - потокобезопасную (re2) и непотокобезопасную (re2_st). -# Библиотека re2, при выполнении регекспа, изменяет некоторое состояние - создаёт временные DFA. -# Для того, чтобы один объект-регексп можно было использовать одновременно из разных потоков, она использует RWLock. -# При этом, даже если использовать в разных потоках разные объекты re2 (созданные из одинакового регекспа), -# то, не смотря на отсутствие блокировок, RWLock "вхолостую" всё-равно очень существенно тормозит. -# Решение: собрать непотокобезопасную версию библиотеки и использовать разные объекты re2 в разных потоках. - -add_definitions( -DNDEBUG ) +# Building re2 which is thread-safe and re2_st which is not. +# re2 changes its state during matching of regular expression, e.g. creates temporary DFA. +# It uses RWLock to process the same regular expression object from different threads. +# In order to avoid redundant locks in some cases, we use not thread-safe version of the library (re2_st). +add_definitions (-DNDEBUG) add_library (re2 ${re2_headers}) add_library (re2_st ${re2_headers}) -set_target_properties(re2_st PROPERTIES COMPILE_DEFINITIONS "NO_THREADS;re2=re2_st") +set_target_properties (re2_st PROPERTIES COMPILE_DEFINITIONS "NO_THREADS;re2=re2_st") message ("Creating headers for re2_st library.") file (MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/re2_st) foreach (FILENAME filtered_re2.h re2.h set.h stringpiece.h variadic_function.h) file (READ ${CMAKE_CURRENT_SOURCE_DIR}/re2/${FILENAME} CONTENT) - string(REGEX REPLACE "using re2::RE2;" "" CONTENT "${CONTENT}") - string(REGEX REPLACE "namespace re2" "namespace re2_st" CONTENT "${CONTENT}") - string(REGEX REPLACE "re2::" "re2_st::" CONTENT "${CONTENT}") - string(REGEX REPLACE "\"re2/" "\"re2_st/" CONTENT "${CONTENT}") - string(REGEX REPLACE "(.\\*?_H)" "\\1_ST" CONTENT "${CONTENT}") + string (REGEX REPLACE "using re2::RE2;" "" CONTENT "${CONTENT}") + string (REGEX REPLACE "namespace re2" "namespace re2_st" CONTENT "${CONTENT}") + string (REGEX REPLACE "re2::" "re2_st::" CONTENT "${CONTENT}") + string (REGEX REPLACE "\"re2/" "\"re2_st/" CONTENT "${CONTENT}") + string (REGEX REPLACE "(.\\*?_H)" "\\1_ST" CONTENT "${CONTENT}") file (WRITE ${CMAKE_CURRENT_BINARY_DIR}/re2_st/${FILENAME} "${CONTENT}") endforeach () diff --git a/contrib/libtcmalloc/CMakeLists.txt b/contrib/libtcmalloc/CMakeLists.txt index 1228727dcec..800024aefea 100644 --- a/contrib/libtcmalloc/CMakeLists.txt +++ b/contrib/libtcmalloc/CMakeLists.txt @@ -10,6 +10,8 @@ add_definitions( include_directories (include src) +message(STATUS "Building: tcmalloc_minimal_internal") + add_library (tcmalloc_minimal_internal ./src/malloc_hook.cc ./src/base/spinlock_internal.cc diff --git a/copy_headers.sh b/copy_headers.sh index e687232fefc..327bb1c0bba 100755 --- a/copy_headers.sh +++ b/copy_headers.sh @@ -23,7 +23,7 @@ PATH="/usr/local/bin:/usr/local/sbin:/usr/bin:$PATH" # Опция -mcx16 для того, чтобы выбиралось больше заголовочных файлов (с запасом). for src_file in $(clang -M -xc++ -std=gnu++1y -Wall -Werror -msse4 -mcx16 -mpopcnt -O3 -g -fPIC \ - $(cat "$SOURCE_PATH/CMakeLists.txt" | grep include_directories | grep -v ClickHouse_BINARY_DIR | sed -e "s!\${ClickHouse_SOURCE_DIR}!$SOURCE_PATH!; s!include_directories (!-I !; s!)!!;" | tr '\n' ' ') \ + $(cat "$SOURCE_PATH/build/include_directories.txt") \ "$SOURCE_PATH/dbms/include/DB/Interpreters/SpecializedAggregator.h" | tr -d '\\' | grep -v '.o:' | diff --git a/dbms/CMakeLists.txt b/dbms/CMakeLists.txt index 32924d6e11e..d66b6ebfbf6 100644 --- a/dbms/CMakeLists.txt +++ b/dbms/CMakeLists.txt @@ -1,49 +1,32 @@ -include_directories(include) -include_directories(/usr/include/mysql) +include_directories (include) +include_directories (/usr/include/mysql) add_subdirectory (src) -option(ENABLE_MONGODB "Set to TRUE to enable MongoDB support as source for external dictionaries" True) -set (DISABLE_MONGODB FALSE CACHE BOOL "Set to TRUE to disable MongoDB support as source for external dictionaries") - if (ENABLE_MONGODB) - set (DISABLE_MONGODB FALSE) -else() - set (DISABLE_MONGODB TRUE) -endif() + set (LINK_MONGOCLIENT libmongoclient.a ${OPENSSL_LIBRARIES} ${Boost_THREAD_LIBRARY}) + add_definitions(-D ENABLE_MONGODB) +endif () -if ($ENV{DISABLE_MONGODB}) - set (DISABLE_MONGODB TRUE) -endif() - -if (DISABLE_MONGODB) - add_definitions(-D DISABLE_MONGODB) -else() - set (LINK_MONGOCLIENT libmongoclient.a ${OPENSSL_LIBS} ${BOOST_THREAD_LIB}) -endif() - -if ($ENV{DISABLE_LIBTCMALLOC}) +if (NOT ENABLE_LIBTCMALLOC) add_definitions(-D NO_TCMALLOC) -endif() +endif () if (APPLE) - set (AIO_CPP_FILES "") - set (AIO_H_FILES "") - set (APPLE_ICONV_LIB iconv) - + set (AIO_CPP_FILES "") + set (AIO_H_FILES "") + set (APPLE_ICONV_LIB iconv) else() - set (AIO_H_FILES include/DB/Common/AIO.h - include/DB/IO/WriteBufferAIO.h - include/DB/IO/ReadBufferAIO.h - ) - set (AIO_CPP_FILES - src/IO/ReadBufferAIO.cpp - src/IO/WriteBufferAIO.cpp - ) - set (APPLE_ICONV_LIB "") + set (AIO_H_FILES include/DB/Common/AIO.h + include/DB/IO/WriteBufferAIO.h + include/DB/IO/ReadBufferAIO.h) + set (AIO_CPP_FILES + src/IO/ReadBufferAIO.cpp + src/IO/WriteBufferAIO.cpp) + set (APPLE_ICONV_LIB "") endif() -add_library(string_utils +add_library (string_utils include/DB/Common/StringUtils.h src/Common/StringUtils.cpp) @@ -298,6 +281,7 @@ add_library (dbms include/DB/DataStreams/SquashingTransform.h include/DB/DataStreams/SquashingBlockInputStream.h include/DB/DataStreams/SquashingBlockOutputStream.h + include/DB/DataStreams/ColumnGathererStream.h include/DB/DataTypes/IDataType.h include/DB/DataTypes/IDataTypeDummy.h include/DB/DataTypes/DataTypeSet.h @@ -813,6 +797,7 @@ add_library (dbms src/DataStreams/SquashingTransform.cpp src/DataStreams/SquashingBlockInputStream.cpp src/DataStreams/SquashingBlockOutputStream.cpp + src/DataStreams/ColumnGathererStream.cpp src/DataTypes/DataTypeString.cpp src/DataTypes/DataTypeFixedString.cpp @@ -983,45 +968,44 @@ add_library (dbms ) if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - # Не генерируем отладочную информацию для файлов с большим количеством инстанцирований шаблонов - # - для более быстрой линковки и меньшего размера бинарника. - SET_SOURCE_FILES_PROPERTIES( - src/Functions/FunctionsArithmetic.cpp - src/Functions/FunctionsArray.cpp - src/Functions/FunctionsCoding.cpp - src/Functions/FunctionsComparison.cpp - src/Functions/FunctionsConditional.cpp - src/Functions/FunctionsConversion.cpp - src/Functions/FunctionsDateTime.cpp - src/Functions/FunctionsDictionaries.cpp - src/Functions/FunctionsFormatting.cpp - src/Functions/FunctionsHashing.cpp - src/Functions/FunctionsHigherOrder.cpp - src/Functions/FunctionsLogical.cpp - src/Functions/FunctionsRandom.cpp - src/Functions/FunctionsReinterpret.cpp - src/Functions/FunctionsRound.cpp - src/Functions/FunctionsString.cpp - src/Functions/FunctionsStringArray.cpp - src/Functions/FunctionsStringSearch.cpp - src/Functions/FunctionsURL.cpp - src/Functions/FunctionsVisitParam.cpp - src/Functions/FunctionsMath.cpp - src/Functions/FunctionsGeo.cpp - src/Functions/FunctionsMiscellaneous.cpp - src/Functions/FunctionsTransform.cpp - src/Dictionaries/FlatDictionary.cpp - src/Dictionaries/HashedDictionary.cpp - src/Dictionaries/CacheDictionary.cpp - src/Dictionaries/RangeHashedDictionary.cpp - src/Dictionaries/ComplexKeyHashedDictionary.cpp - src/Dictionaries/ComplexKeyCacheDictionary.cpp - PROPERTIES COMPILE_FLAGS -g0) -endif() + # Won't generate debug info for files with heavy template instantiation to achieve faster linking and lower size. + set_source_files_properties( + src/Functions/FunctionsArithmetic.cpp + src/Functions/FunctionsArray.cpp + src/Functions/FunctionsCoding.cpp + src/Functions/FunctionsComparison.cpp + src/Functions/FunctionsConditional.cpp + src/Functions/FunctionsConversion.cpp + src/Functions/FunctionsDateTime.cpp + src/Functions/FunctionsDictionaries.cpp + src/Functions/FunctionsFormatting.cpp + src/Functions/FunctionsHashing.cpp + src/Functions/FunctionsHigherOrder.cpp + src/Functions/FunctionsLogical.cpp + src/Functions/FunctionsRandom.cpp + src/Functions/FunctionsReinterpret.cpp + src/Functions/FunctionsRound.cpp + src/Functions/FunctionsString.cpp + src/Functions/FunctionsStringArray.cpp + src/Functions/FunctionsStringSearch.cpp + src/Functions/FunctionsURL.cpp + src/Functions/FunctionsVisitParam.cpp + src/Functions/FunctionsMath.cpp + src/Functions/FunctionsGeo.cpp + src/Functions/FunctionsMiscellaneous.cpp + src/Functions/FunctionsTransform.cpp + src/Dictionaries/FlatDictionary.cpp + src/Dictionaries/HashedDictionary.cpp + src/Dictionaries/CacheDictionary.cpp + src/Dictionaries/RangeHashedDictionary.cpp + src/Dictionaries/ComplexKeyHashedDictionary.cpp + src/Dictionaries/ComplexKeyCacheDictionary.cpp + PROPERTIES COMPILE_FLAGS -g0) +endif () -IF (NOT AARCH64) - SET(LINK_LIBRARIES_ONLY_ON_X86_64 cpuid) -ENDIF() +if (NOT AARCH64) + set (LINK_LIBRARIES_ONLY_ON_X86_64 cpuid) +endif () target_link_libraries(dbms common @@ -1033,10 +1017,10 @@ target_link_libraries(dbms double-conversion ${LINK_LIBRARIES_ONLY_ON_X86_64} re2 re2_st - libcrypto.a - ${BOOST_SYSTEM_LIB} + ${OPENSSL_CRYPTO_LIBRARY} + ${Boost_SYSTEM_LIBRARY} ${LINK_MONGOCLIENT} - ${BOOST_REGEX_LIB} + ${Boost_REGEX_LIBRARY} PocoData PocoDataODBC ${APPLE_ICONV_LIB} diff --git a/dbms/include/DB/Core/SortDescription.h b/dbms/include/DB/Core/SortDescription.h index d4fbe234e1e..daeaf8db2de 100644 --- a/dbms/include/DB/Core/SortDescription.h +++ b/dbms/include/DB/Core/SortDescription.h @@ -35,9 +35,9 @@ struct SortColumnDescription using SortDescription = std::vector; -/** Курсор, позволяющий сравнивать соответствующие строки в разных блоках. - * Курсор двигается по одному блоку. - * Для использования в priority queue. +/** Cursor allows to compare rows in different blocks (and parts). + * Cursor moves inside single block. + * It is used in priority queue. */ struct SortCursorImpl { @@ -48,14 +48,17 @@ struct SortCursorImpl size_t pos = 0; size_t rows = 0; - /** Порядок (что сравнивается), если сравниваемые столбцы равны. - * Даёт возможность предпочитать строки из нужного курсора. + /** Determines order if comparing columns are equal. + * Order is determined by number of cursor. + * + * Cursor number (always?) equals to number of merging part. + * Therefore this field can be used to determine part number of current row (see ColumnGathererStream). */ size_t order; using NeedCollationFlags = std::vector; - /** Нужно ли использовать Collator для сортировки столбца */ + /** Should we use Collator to sort a column? */ NeedCollationFlags need_collation; /** Есть ли хотя бы один столбец с Collator. */ diff --git a/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h b/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h index 3dba4cb00c3..da9c6bacfbf 100644 --- a/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h +++ b/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h @@ -24,8 +24,8 @@ class CollapsingSortedBlockInputStream : public MergingSortedBlockInputStream { public: CollapsingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, - const String & sign_column_, size_t max_block_size_) - : MergingSortedBlockInputStream(inputs_, description_, max_block_size_), + const String & sign_column_, size_t max_block_size_, MergedRowSources * out_row_sources_ = nullptr) + : MergingSortedBlockInputStream(inputs_, description_, max_block_size_, 0, out_row_sources_), sign_column(sign_column_) { } @@ -62,7 +62,7 @@ private: /// Прочитали до конца. bool finished = false; - RowRef current_key; /// Текущий первичный ключ. + RowRef current_key; /// Текущий первичный ключ. RowRef next_key; /// Первичный ключ следующей строки. RowRef first_negative; /// Первая отрицательная строка для текущего первичного ключа. @@ -77,6 +77,12 @@ private: size_t blocks_written = 0; + /// Fields specific for VERTICAL merge algorithm + size_t current_pos = 0; /// Global row number of current key + size_t first_negative_pos = 0; /// Global row number of first_negative + size_t last_positive_pos = 0; /// Global row number of last_positive + size_t last_negative_pos = 0; /// Global row number of last_negative + /** Делаем поддержку двух разных курсоров - с Collation и без. * Шаблоны используем вместо полиморфных SortCursor'ов и вызовов виртуальных функций. */ diff --git a/dbms/include/DB/DataStreams/ColumnGathererStream.h b/dbms/include/DB/DataStreams/ColumnGathererStream.h new file mode 100644 index 00000000000..7d19319518b --- /dev/null +++ b/dbms/include/DB/DataStreams/ColumnGathererStream.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include + + +namespace DB +{ + + +/// Tiny struct, stores number of a Part from which current row was fetched, and insertion flag. +struct RowSourcePart +{ + RowSourcePart() = default; + + RowSourcePart(size_t source_num, bool flag = false) + { + static_assert(sizeof(*this) == 1, "Size of RowSourcePart is too big due to compiler settings"); + setSourceNum(source_num); + setSkipFlag(flag); + } + + /// is equal to getSourceNum() if flag is false + size_t getData() const { return data; } + + size_t getSourceNum()const { return data & MASK_NUMBER; } + + /// In CollapsingMergeTree case flag means "skip this rows" + bool getSkipFlag() const { return (data & MASK_FLAG) != 0; } + + void setSourceNum(size_t source_num) + { + data = (data & MASK_FLAG) | (static_cast(source_num) & MASK_NUMBER); + } + + void setSkipFlag(bool flag) + { + data = flag ? data | MASK_FLAG : data & ~MASK_FLAG; + } + + static constexpr size_t MAX_PARTS = 0x7F; + static constexpr UInt8 MASK_NUMBER = 0x7F; + static constexpr UInt8 MASK_FLAG = 0x80; + +private: + UInt8 data; +}; + +using MergedRowSources = PODArray; + + +/** Gather single stream from multiple streams according to streams mask. + * Stream mask maps row number to index of source stream. + * Streams should conatin exactly one column. + */ +class ColumnGathererStream : public IProfilingBlockInputStream +{ +public: + ColumnGathererStream(const BlockInputStreams & source_streams, const String & column_name_, + const MergedRowSources & pos_to_source_idx_, size_t block_size_ = DEFAULT_BLOCK_SIZE); + + String getName() const override { return "ColumnGatherer"; } + + String getID() const override; + + Block readImpl() override; + +private: + + String name; + ColumnWithTypeAndName column; + const MergedRowSources & pos_to_source_idx; + + /// Cache required fileds + struct Source + { + const IColumn * column; + size_t pos; + size_t size; + Block block; + + Source(Block && block_, const String & name) : block(std::move(block_)) + { + update(name); + } + + void update(const String & name) + { + column = block.getByName(name).column.get(); + size = block.rows(); + pos = 0; + } + }; + + std::vector sources; + + size_t pos_global = 0; + size_t block_size; + + Logger * log = &Logger::get("ColumnGathererStream"); +}; + +} diff --git a/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h b/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h index 49ad3185720..223c71edf99 100644 --- a/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h +++ b/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h @@ -9,6 +9,7 @@ #include #include +#include namespace DB @@ -56,10 +57,12 @@ inline void intrusive_ptr_release(detail::SharedBlock * ptr) class MergingSortedBlockInputStream : public IProfilingBlockInputStream { public: - /// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке. - MergingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_, size_t limit_ = 0) + /// limit - if isn't 0, then we can produce only first limit rows in sorted order. + /// out_row_sources - if isn't nullptr, then at the end of execution it should contain part numbers of each readed row (and needed flag) + MergingSortedBlockInputStream(BlockInputStreams & inputs_, const SortDescription & description_, + size_t max_block_size_, size_t limit_ = 0, MergedRowSources * out_row_sources_ = nullptr) : description(description_), max_block_size(max_block_size_), limit(limit_), - source_blocks(inputs_.size()), cursors(inputs_.size()) + source_blocks(inputs_.size()), cursors(inputs_.size()), out_row_sources(out_row_sources_) { children.insert(children.end(), inputs_.begin(), inputs_.end()); } @@ -158,6 +161,10 @@ protected: using QueueWithCollation = std::priority_queue; QueueWithCollation queue_with_collation; + /// Used in Vertical merge algorithm to gather non-PK columns (on next step) + /// If it is not nullptr then it should be populated during execution + MergedRowSources * out_row_sources = nullptr; + /// Эти методы используются в Collapsing/Summing/Aggregating... SortedBlockInputStream-ах. diff --git a/dbms/include/DB/Dictionaries/DictionarySourceFactory.h b/dbms/include/DB/Dictionaries/DictionarySourceFactory.h index 8ea62b14e50..9839f6b18e1 100644 --- a/dbms/include/DB/Dictionaries/DictionarySourceFactory.h +++ b/dbms/include/DB/Dictionaries/DictionarySourceFactory.h @@ -8,7 +8,7 @@ #include #include -#ifndef DISABLE_MONGODB +#ifdef ENABLE_MONGODB #include #endif @@ -118,7 +118,7 @@ public: } else if ("mongodb" == source_type) { - #ifndef DISABLE_MONGODB + #ifdef ENABLE_MONGODB return std::make_unique(dict_struct, config, config_prefix + ".mongodb", sample_block); #else throw Exception{ diff --git a/dbms/include/DB/Functions/FunctionsArray.h b/dbms/include/DB/Functions/FunctionsArray.h index 2fd4aaa443f..bfcfd40ddef 100644 --- a/dbms/include/DB/Functions/FunctionsArray.h +++ b/dbms/include/DB/Functions/FunctionsArray.h @@ -81,47 +81,14 @@ private: const Context & context; /// Получить имя функции. - String getName() const override - { - return is_case_mode ? "CASE" : name; - } + String getName() const override; template - bool tryAddField(DataTypePtr type_res, const Field & f, Array & arr) const - { - if (typeid_cast(type_res.get())) - { - arr.push_back(apply_visitor(FieldVisitorConvertToNumber(), f)); - return true; - } - return false; - } + bool tryAddField(DataTypePtr type_res, const Field & f, Array & arr) const; - bool addField(DataTypePtr type_res, const Field & f, Array & arr) const - { - /// Иначе необходимо - if ( tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) - || tryAddField(type_res, f, arr) ) - return true; - else - { - if (is_case_mode) - throw Exception{"Illegal type encountered while processing the CASE construction.", - ErrorCodes::LOGICAL_ERROR}; - else - throw Exception{"Illegal result type " + type_res->getName() + " of function " + getName(), - ErrorCodes::LOGICAL_ERROR}; - } - } + bool addField(DataTypePtr type_res, const Field & f, Array & arr) const; + // TODO remove it because of unusing static const DataTypePtr & getScalarType(const DataTypePtr & type) { const auto array = typeid_cast(type.get()); @@ -132,225 +99,16 @@ private: return getScalarType(array->getNestedType()); } - DataTypeTraits::EnrichedDataTypePtr getLeastCommonType(const DataTypes & arguments) const - { - DataTypeTraits::EnrichedDataTypePtr result_type; - - try - { - result_type = Conditional::getArrayType(arguments); - } - catch (const Conditional::CondException & ex) - { - /// Translate a context-free error into a contextual error. - if (is_case_mode) - { - if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_ILLEGAL_COLUMN_TYPE) - throw Exception{"Illegal type of column " + ex.getMsg1() + - " in CASE construction", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; - else if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_UPSCALING_ERROR) - throw Exception{"THEN/ELSE clause parameters in CASE construction are not upscalable to a " - "common type without loss of precision: " + ex.getMsg1(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; - else - throw Exception{"An unexpected error has occurred in CASE expression", - ErrorCodes::LOGICAL_ERROR}; - } - else - { - if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_ILLEGAL_COLUMN_TYPE) - throw Exception{"Illegal type of column " + ex.getMsg1() + - " in array", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; - else if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_UPSCALING_ERROR) - throw Exception("Arguments of function " + getName() + " are not upscalable " - "to a common type without loss of precision.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - else - throw Exception{"An unexpected error has occurred in function " + getName(), - ErrorCodes::LOGICAL_ERROR}; - } - } - - return result_type; - } + DataTypeTraits::EnrichedDataTypePtr getLeastCommonType(const DataTypes & arguments) const; public: - void setCaseMode() - { - is_case_mode = true; - } + /// Выполнить функцию над блоком. + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; /// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.empty()) - { - if (is_case_mode) - throw Exception{"Either WHEN clauses or THEN clauses are missing " - "in the CASE construction.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH}; - else - throw Exception{"Function array requires at least one argument.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH}; - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; - DataTypePtr result_type = arguments[0]; - - if (result_type->behavesAsNumber()) - { - /// Если тип числовой, пробуем выделить наименьший общий тип - auto enriched_result_type = getLeastCommonType(arguments); - return std::make_shared(enriched_result_type); - } - else - { - /// Иначе все аргументы должны быть одинаковыми - for (size_t i = 1, size = arguments.size(); i < size; ++i) - { - if (arguments[i]->getName() != arguments[0]->getName()) - { - if (is_case_mode) - throw Exception{"Found type discrepancy in either WHEN " - "clauses or THEN clauses of the CASE construction", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; - else - throw Exception{"Arguments for function array must have same type or behave as number.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; - } - } - - return std::make_shared(result_type); - } - } - - /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - size_t num_elements = arguments.size(); - bool is_const = true; - - for (const auto arg_num : arguments) - { - if (!block.getByPosition(arg_num).column->isConst()) - { - is_const = false; - break; - } - } - - const auto first_arg = block.getByPosition(arguments[0]); - DataTypePtr result_type = first_arg.type; - DataTypeTraits::EnrichedDataTypePtr enriched_result_type; - if (result_type->behavesAsNumber()) - { - /// If type is numeric, calculate least common type. - DataTypes types; - types.reserve(num_elements); - - for (const auto & argument : arguments) - types.push_back(block.getByPosition(argument).type); - - enriched_result_type = getLeastCommonType(types); - result_type = enriched_result_type.first; - } - - if (is_const) - { - Array arr; - for (const auto arg_num : arguments) - if (block.getByPosition(arg_num).type->getName() == result_type->getName()) - /// Если элемент такого же типа как результат, просто добавляем его в ответ - arr.push_back((*block.getByPosition(arg_num).column)[0]); - else - /// Иначе необходимо привести его к типу результата - addField(result_type, (*block.getByPosition(arg_num).column)[0], arr); - - block.getByPosition(result).column = std::make_shared( - first_arg.column->size(), arr, std::make_shared(result_type)); - } - else - { - size_t block_size = block.rowsInFirstColumn(); - - /** If part of columns have not same type as common type of all elements of array, - * then convert them to common type. - * If part of columns are constants, - * then convert them to full columns. - */ - - Columns columns_holder(num_elements); - const IColumn * columns[num_elements]; - - for (size_t i = 0; i < num_elements; ++i) - { - const auto & arg = block.getByPosition(arguments[i]); - - String result_type_name = result_type->getName(); - ColumnPtr preprocessed_column = arg.column; - - if (arg.type->getName() != result_type_name) - { - Block temporary_block - { - { - arg.column, - arg.type, - arg.name - }, - { - std::make_shared(block_size, result_type_name), - std::make_shared(), - "" - }, - { - nullptr, - result_type, - "" - } - }; - - FunctionCast func_cast(context); - - { - DataTypePtr unused_return_type; - ColumnsWithTypeAndName arguments{ temporary_block.unsafeGetByPosition(0), temporary_block.unsafeGetByPosition(1) }; - std::vector unused_prerequisites; - - /// Prepares function to execution. TODO It is not obvious. - func_cast.getReturnTypeAndPrerequisites(arguments, unused_return_type, unused_prerequisites); - } - - func_cast.execute(temporary_block, {0, 1}, 2); - preprocessed_column = temporary_block.unsafeGetByPosition(2).column; - } - - if (auto materialized_column = preprocessed_column->convertToFullColumnIfConst()) - preprocessed_column = materialized_column; - - columns_holder[i] = std::move(preprocessed_column); - columns[i] = columns_holder[i].get(); - } - - /** Create and fill the result array. - */ - - auto out = std::make_shared(result_type->createColumn()); - IColumn & out_data = out->getData(); - IColumn::Offsets_t & out_offsets = out->getOffsets(); - - out_data.reserve(block_size * num_elements); - out_offsets.resize(block_size); - - IColumn::Offset_t current_offset = 0; - for (size_t i = 0; i < block_size; ++i) - { - for (size_t j = 0; j < num_elements; ++j) - out_data.insertFrom(*columns[j], i); - - current_offset += num_elements; - out_offsets[i] = current_offset; - } - - block.getByPosition(result).column = out; - } - } + void setCaseMode(); private: bool is_case_mode = false; @@ -416,6 +174,7 @@ struct ArrayElementNumImpl } }; + struct ArrayElementStringImpl { /** Implementation for constant index. @@ -520,6 +279,7 @@ struct ArrayElementStringImpl } }; + /// Generic implementation for other nested types. struct ArrayElementGenericImpl { @@ -588,368 +348,42 @@ public: private: template - bool executeNumberConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) - { - const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - - if (!col_array) - return false; - - const ColumnVector * col_nested = typeid_cast *>(&col_array->getData()); - - if (!col_nested) - return false; - - auto col_res = std::make_shared>(); - block.getByPosition(result).column = col_res; - - if (index.getType() == Field::Types::UInt64) - ArrayElementNumImpl::template vectorConst( - col_nested->getData(), col_array->getOffsets(), safeGet(index) - 1, col_res->getData()); - else if (index.getType() == Field::Types::Int64) - ArrayElementNumImpl::template vectorConst( - col_nested->getData(), col_array->getOffsets(), -safeGet(index) - 1, col_res->getData()); - else - throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); - - return true; - } + bool executeNumberConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index); template - bool executeNumber(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) - { - const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + bool executeNumber(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices); - if (!col_array) - return false; - - const ColumnVector * col_nested = typeid_cast *>(&col_array->getData()); - - if (!col_nested) - return false; - - auto col_res = std::make_shared>(); - block.getByPosition(result).column = col_res; - - ArrayElementNumImpl::template vector( - col_nested->getData(), col_array->getOffsets(), indices, col_res->getData()); - - return true; - } - - bool executeStringConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) - { - const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - - if (!col_array) - return false; - - const ColumnString * col_nested = typeid_cast(&col_array->getData()); - - if (!col_nested) - return false; - - std::shared_ptr col_res = std::make_shared(); - block.getByPosition(result).column = col_res; - - if (index.getType() == Field::Types::UInt64) - ArrayElementStringImpl::vectorConst( - col_nested->getChars(), - col_array->getOffsets(), - col_nested->getOffsets(), - safeGet(index) - 1, - col_res->getChars(), - col_res->getOffsets()); - else if (index.getType() == Field::Types::Int64) - ArrayElementStringImpl::vectorConst( - col_nested->getChars(), - col_array->getOffsets(), - col_nested->getOffsets(), - -safeGet(index) - 1, - col_res->getChars(), - col_res->getOffsets()); - else - throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); - - return true; - } + bool executeStringConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index); template - bool executeString(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) - { - const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + bool executeString(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices); - if (!col_array) - return false; - - const ColumnString * col_nested = typeid_cast(&col_array->getData()); - - if (!col_nested) - return false; - - std::shared_ptr col_res = std::make_shared(); - block.getByPosition(result).column = col_res; - - ArrayElementStringImpl::vector( - col_nested->getChars(), - col_array->getOffsets(), - col_nested->getOffsets(), - indices, - col_res->getChars(), - col_res->getOffsets()); - - return true; - } - - bool executeGenericConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) - { - const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - - if (!col_array) - return false; - - const auto & col_nested = col_array->getData(); - auto col_res = col_nested.cloneEmpty(); - block.getByPosition(result).column = col_res; - - if (index.getType() == Field::Types::UInt64) - ArrayElementGenericImpl::vectorConst( - col_nested, col_array->getOffsets(), safeGet(index) - 1, *col_res); - else if (index.getType() == Field::Types::Int64) - ArrayElementGenericImpl::vectorConst( - col_nested, col_array->getOffsets(), -safeGet(index) - 1, *col_res); - else - throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); - - return true; - } + bool executeGenericConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index); template - bool executeGeneric(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) - { - const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + bool executeGeneric(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices); - if (!col_array) - return false; - - const auto & col_nested = col_array->getData(); - auto col_res = col_nested.cloneEmpty(); - block.getByPosition(result).column = col_res; - - ArrayElementGenericImpl::vector( - col_nested, col_array->getOffsets(), indices, *col_res); - - return true; - } - - bool executeConstConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) - { - const ColumnConstArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - - if (!col_array) - return false; - - const DB::Array & array = col_array->getData(); - size_t array_size = array.size(); - size_t real_index = 0; - - if (index.getType() == Field::Types::UInt64) - real_index = safeGet(index) - 1; - else if (index.getType() == Field::Types::Int64) - real_index = array_size + safeGet(index); - else - throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); - - Field value = col_array->getData().at(real_index); - - block.getByPosition(result).column = block.getByPosition(result).type->createConstColumn( - block.rowsInFirstColumn(), - value); - - return true; - } + bool executeConstConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index); template - bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) - { - const ColumnConstArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - - if (!col_array) - return false; - - const DB::Array & array = col_array->getData(); - size_t array_size = array.size(); - - block.getByPosition(result).column = block.getByPosition(result).type->createColumn(); - - for (size_t i = 0; i < col_array->size(); ++i) - { - IndexType index = indices[i]; - if (index > 0 && static_cast(index) <= array_size) - block.getByPosition(result).column->insert(array[index - 1]); - else if (index < 0 && static_cast(-index) <= array_size) - block.getByPosition(result).column->insert(array[array_size + index]); - else - block.getByPosition(result).column->insertDefault(); - } - - return true; - } + bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices); template - bool executeArgument(Block & block, const ColumnNumbers & arguments, size_t result) - { - auto index = typeid_cast *>(block.getByPosition(arguments[1]).column.get()); - - if (!index) - return false; - - const auto & index_data = index->getData(); - - if (!( executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeNumber (block, arguments, result, index_data) - || executeConst (block, arguments, result, index_data) - || executeString (block, arguments, result, index_data) - || executeGeneric (block, arguments, result, index_data))) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of first argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN); - - return true; - } + bool executeArgument(Block & block, const ColumnNumbers & arguments, size_t result); /** Для массива кортежей функция вычисляется покомпонентно - для каждого элемента кортежа. */ - bool executeTuple(Block & block, const ColumnNumbers & arguments, size_t result) - { - ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + bool executeTuple(Block & block, const ColumnNumbers & arguments, size_t result); - if (!col_array) - return false; - - ColumnTuple * col_nested = typeid_cast(&col_array->getData()); - - if (!col_nested) - return false; - - Block & tuple_block = col_nested->getData(); - size_t tuple_size = tuple_block.columns(); - - /** Будем вычислять функцию для кортежа внутренностей массива. - * Для этого создадим временный блок. - * Он будет состоять из следующих столбцов: - * - индекс массива, который нужно взять; - * - массив из первых элементов кортежей; - * - результат взятия элементов по индексу для массива из первых элементов кортежей; - * - массив из вторых элементов кортежей; - * - результат взятия элементов по индексу для массива из вторых элементов кортежей; - * ... - */ - Block block_of_temporary_results; - block_of_temporary_results.insert(block.getByPosition(arguments[1])); - - /// результаты взятия элементов по индексу для массивов из каждых элементов кортежей; - Block result_tuple_block; - - for (size_t i = 0; i < tuple_size; ++i) - { - ColumnWithTypeAndName array_of_tuple_section; - array_of_tuple_section.column = std::make_shared( - tuple_block.getByPosition(i).column, col_array->getOffsetsColumn()); - array_of_tuple_section.type = std::make_shared( - tuple_block.getByPosition(i).type); - block_of_temporary_results.insert(array_of_tuple_section); - - ColumnWithTypeAndName array_elements_of_tuple_section; - block_of_temporary_results.insert(array_elements_of_tuple_section); - - execute(block_of_temporary_results, ColumnNumbers{i * 2 + 1, 0}, i * 2 + 2); - - result_tuple_block.insert(block_of_temporary_results.getByPosition(i * 2 + 2)); - } - - auto col_res = std::make_shared(result_tuple_block); - block.getByPosition(result).column = col_res; - - return true; - } public: /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; /// Получить типы результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() != 2) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 2.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - const DataTypeArray * array_type = typeid_cast(arguments[0].get()); - if (!array_type) - throw Exception("First argument for function " + getName() + " must be array.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - if (!arguments[1]->isNumeric() - || (!startsWith(arguments[1]->getName(), "UInt") && !startsWith(arguments[1]->getName(), "Int"))) - throw Exception("Second argument for function " + getName() + " must have UInt or Int type.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - return array_type->getNestedType(); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - if (executeTuple(block, arguments, result)) - { - } - else if (!block.getByPosition(arguments[1]).column->isConst()) - { - if (!( executeArgument (block, arguments, result) - || executeArgument (block, arguments, result) - || executeArgument (block, arguments, result) - || executeArgument (block, arguments, result) - || executeArgument (block, arguments, result) - || executeArgument (block, arguments, result) - || executeArgument (block, arguments, result) - || executeArgument (block, arguments, result))) - throw Exception("Second argument for function " + getName() + " must must have UInt or Int type.", - ErrorCodes::ILLEGAL_COLUMN); - } - else - { - Field index = (*block.getByPosition(arguments[1]).column)[0]; - - if (index == UInt64(0)) - throw Exception("Array indices is 1-based", ErrorCodes::ZERO_ARRAY_OR_TUPLE_INDEX); - - if (!( executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeNumberConst (block, arguments, result, index) - || executeConstConst (block, arguments, result, index) - || executeStringConst (block, arguments, result, index) - || executeGenericConst (block, arguments, result, index))) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - } - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; }; @@ -1360,6 +794,7 @@ public: } }; + class FunctionArrayEnumerate : public IFunction { public: @@ -1367,70 +802,13 @@ public: static FunctionPtr create(const Context & context) { return std::make_shared(); } /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; /// Получить типы результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() != 1) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 1.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - const DataTypeArray * array_type = typeid_cast(arguments[0].get()); - if (!array_type) - throw Exception("First argument for function " + getName() + " must be array.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - return std::make_shared(std::make_shared()); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - if (const ColumnArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get())) - { - const ColumnArray::Offsets_t & offsets = array->getOffsets(); - - auto res_nested = std::make_shared(); - auto res_array = std::make_shared(res_nested, array->getOffsetsColumn()); - block.getByPosition(result).column = res_array; - - ColumnUInt32::Container_t & res_values = res_nested->getData(); - res_values.resize(array->getData().size()); - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - { - res_values[j] = j - prev_off + 1; - } - prev_off = off; - } - } - else if (const ColumnConstArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get())) - { - const Array & values = array->getData(); - - Array res_values(values.size()); - for (size_t i = 0; i < values.size(); ++i) - { - res_values[i] = i + 1; - } - - auto res_array = std::make_shared(array->size(), res_values, std::make_shared(std::make_shared())); - block.getByPosition(result).column = res_array; - } - else - { - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - } - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; }; @@ -1443,227 +821,34 @@ public: static FunctionPtr create(const Context & context) { return std::make_shared(); } /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; /// Получить типы результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() == 0) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be at least 1.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - for (size_t i = 0; i < arguments.size(); ++i) - { - const DataTypeArray * array_type = typeid_cast(arguments[i].get()); - if (!array_type) - throw Exception("All arguments for function " + getName() + " must be arrays; argument " + toString(i + 1) + " isn't.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - } - - return std::make_shared(); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - if (arguments.size() == 1 && executeConst(block, arguments, result)) - return; - - Columns array_columns(arguments.size()); - const ColumnArray::Offsets_t * offsets = nullptr; - ConstColumnPlainPtrs data_columns(arguments.size()); - - for (size_t i = 0; i < arguments.size(); ++i) - { - ColumnPtr array_ptr = block.getByPosition(arguments[i]).column; - const ColumnArray * array = typeid_cast(array_ptr.get()); - if (!array) - { - const ColumnConstArray * const_array = typeid_cast( - block.getByPosition(arguments[i]).column.get()); - if (!const_array) - throw Exception("Illegal column " + block.getByPosition(arguments[i]).column->getName() - + " of " + toString(i + 1) + "-th argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - array_ptr = const_array->convertToFullColumn(); - array = typeid_cast(array_ptr.get()); - } - array_columns[i] = array_ptr; - const ColumnArray::Offsets_t & offsets_i = array->getOffsets(); - if (!i) - offsets = &offsets_i; - else if (offsets_i != *offsets) - throw Exception("Lengths of all arrays passsed to " + getName() + " must be equal.", - ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH); - data_columns[i] = &array->getData(); - } - - const ColumnArray * first_array = typeid_cast(array_columns[0].get()); - auto res = std::make_shared(); - block.getByPosition(result).column = res; - - ColumnUInt32::Container_t & res_values = res->getData(); - res_values.resize(offsets->size()); - - if (arguments.size() == 1) - { - if (!( executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeString (first_array, res_values))) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - } - else - { - if (!execute128bit(*offsets, data_columns, res_values)) - executeHashed(*offsets, data_columns, res_values); - } - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; private: /// Изначально выделить кусок памяти для 512 элементов. static constexpr size_t INITIAL_SIZE_DEGREE = 9; template - bool executeNumber(const ColumnArray * array, ColumnUInt32::Container_t & res_values) - { - const ColumnVector * nested = typeid_cast *>(&array->getData()); - if (!nested) - return false; - const ColumnArray::Offsets_t & offsets = array->getOffsets(); - const typename ColumnVector::Container_t & values = nested->getData(); + bool executeNumber(const ColumnArray * array, ColumnUInt32::Container_t & res_values); - typedef ClearableHashSet, HashTableGrower, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(T)> > Set; + bool executeString(const ColumnArray * array, ColumnUInt32::Container_t & res_values); - Set set; - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - set.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - set.insert(values[j]); - - res_values[i] = set.size(); - prev_off = off; - } - return true; - } - - bool executeString(const ColumnArray * array, ColumnUInt32::Container_t & res_values) - { - const ColumnString * nested = typeid_cast(&array->getData()); - if (!nested) - return false; - const ColumnArray::Offsets_t & offsets = array->getOffsets(); - - typedef ClearableHashSet, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(StringRef)> > Set; - - Set set; - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - set.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - set.insert(nested->getDataAt(j)); - - res_values[i] = set.size(); - prev_off = off; - } - return true; - } - - bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result) - { - const ColumnConstArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - if (!array) - return false; - const Array & values = array->getData(); - - std::set set; - for (size_t i = 0; i < values.size(); ++i) - set.insert(values[i]); - - block.getByPosition(result).column = std::make_shared(array->size(), set.size()); - return true; - } + bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result); bool execute128bit( const ColumnArray::Offsets_t & offsets, const ConstColumnPlainPtrs & columns, - ColumnUInt32::Container_t & res_values) - { - size_t count = columns.size(); - size_t keys_bytes = 0; - Sizes key_sizes(count); - for (size_t j = 0; j < count; ++j) - { - if (!columns[j]->isFixed()) - return false; - key_sizes[j] = columns[j]->sizeOfField(); - keys_bytes += key_sizes[j]; - } - if (keys_bytes > 16) - return false; - - typedef ClearableHashSet, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > Set; - - Set set; - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - set.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - set.insert(packFixed(j, count, columns, key_sizes)); - - res_values[i] = set.size(); - prev_off = off; - } - - return true; - } + ColumnUInt32::Container_t & res_values); void executeHashed( const ColumnArray::Offsets_t & offsets, const ConstColumnPlainPtrs & columns, - ColumnUInt32::Container_t & res_values) - { - size_t count = columns.size(); - - typedef ClearableHashSet, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > Set; - - Set set; - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - set.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - set.insert(hash128(j, count, columns)); - - res_values[i] = set.size(); - prev_off = off; - } - } + ColumnUInt32::Container_t & res_values); }; @@ -1674,234 +859,34 @@ public: static FunctionPtr create(const Context & context) { return std::make_shared(); } /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; /// Получить типы результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() == 0) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be at least 1.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - for (size_t i = 0; i < arguments.size(); ++i) - { - const DataTypeArray * array_type = typeid_cast(arguments[i].get()); - if (!array_type) - throw Exception("All arguments for function " + getName() + " must be arrays; argument " + toString(i + 1) + " isn't.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - } - - return std::make_shared(std::make_shared()); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - if (arguments.size() == 1 && executeConst(block, arguments, result)) - return; - - Columns array_columns(arguments.size()); - const ColumnArray::Offsets_t * offsets = nullptr; - ConstColumnPlainPtrs data_columns(arguments.size()); - - for (size_t i = 0; i < arguments.size(); ++i) - { - ColumnPtr array_ptr = block.getByPosition(arguments[i]).column; - const ColumnArray * array = typeid_cast(array_ptr.get()); - if (!array) - { - const ColumnConstArray * const_array = typeid_cast( - block.getByPosition(arguments[i]).column.get()); - if (!const_array) - throw Exception("Illegal column " + block.getByPosition(arguments[i]).column->getName() - + " of " + toString(i + 1) + "-th argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - array_ptr = const_array->convertToFullColumn(); - array = typeid_cast(array_ptr.get()); - } - array_columns[i] = array_ptr; - const ColumnArray::Offsets_t & offsets_i = array->getOffsets(); - if (!i) - offsets = &offsets_i; - else if (offsets_i != *offsets) - throw Exception("Lengths of all arrays passsed to " + getName() + " must be equal.", - ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH); - data_columns[i] = &array->getData(); - } - - const ColumnArray * first_array = typeid_cast(array_columns[0].get()); - auto res_nested = std::make_shared(); - auto res_array = std::make_shared(res_nested, first_array->getOffsetsColumn()); - block.getByPosition(result).column = res_array; - - ColumnUInt32::Container_t & res_values = res_nested->getData(); - if (!offsets->empty()) - res_values.resize(offsets->back()); - - if (arguments.size() == 1) - { - if (!( executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeNumber (first_array, res_values) - || executeString (first_array, res_values))) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - } - else - { - if (!execute128bit(*offsets, data_columns, res_values)) - executeHashed(*offsets, data_columns, res_values); - } - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; private: /// Изначально выделить кусок памяти для 512 элементов. static constexpr size_t INITIAL_SIZE_DEGREE = 9; template - bool executeNumber(const ColumnArray * array, ColumnUInt32::Container_t & res_values) - { - const ColumnVector * nested = typeid_cast *>(&array->getData()); - if (!nested) - return false; - const ColumnArray::Offsets_t & offsets = array->getOffsets(); - const typename ColumnVector::Container_t & values = nested->getData(); + bool executeNumber(const ColumnArray * array, ColumnUInt32::Container_t & res_values); - typedef ClearableHashMap, HashTableGrower, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(T)> > ValuesToIndices; + bool executeString(const ColumnArray * array, ColumnUInt32::Container_t & res_values); - ValuesToIndices indices; - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - indices.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - { - res_values[j] = ++indices[values[j]]; - } - prev_off = off; - } - return true; - } - - bool executeString(const ColumnArray * array, ColumnUInt32::Container_t & res_values) - { - const ColumnString * nested = typeid_cast(&array->getData()); - if (!nested) - return false; - const ColumnArray::Offsets_t & offsets = array->getOffsets(); - - size_t prev_off = 0; - typedef ClearableHashMap, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(StringRef)> > ValuesToIndices; - - ValuesToIndices indices; - for (size_t i = 0; i < offsets.size(); ++i) - { - indices.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - { - res_values[j] = ++indices[nested->getDataAt(j)]; - } - prev_off = off; - } - return true; - } - - bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result) - { - const ColumnConstArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - if (!array) - return false; - const Array & values = array->getData(); - - Array res_values(values.size()); - std::map indices; - for (size_t i = 0; i < values.size(); ++i) - { - res_values[i] = static_cast(++indices[values[i]]); - } - - auto res_array = std::make_shared(array->size(), res_values, std::make_shared(std::make_shared())); - block.getByPosition(result).column = res_array; - - return true; - } + bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result); bool execute128bit( const ColumnArray::Offsets_t & offsets, const ConstColumnPlainPtrs & columns, - ColumnUInt32::Container_t & res_values) - { - size_t count = columns.size(); - size_t keys_bytes = 0; - Sizes key_sizes(count); - for (size_t j = 0; j < count; ++j) - { - if (!columns[j]->isFixed()) - return false; - key_sizes[j] = columns[j]->sizeOfField(); - keys_bytes += key_sizes[j]; - } - if (keys_bytes > 16) - return false; - - typedef ClearableHashMap, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > ValuesToIndices; - - ValuesToIndices indices; - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - indices.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - { - res_values[j] = ++indices[packFixed(j, count, columns, key_sizes)]; - } - prev_off = off; - } - - return true; - } + ColumnUInt32::Container_t & res_values); void executeHashed( const ColumnArray::Offsets_t & offsets, const ConstColumnPlainPtrs & columns, - ColumnUInt32::Container_t & res_values) - { - size_t count = columns.size(); - - typedef ClearableHashMap, - HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > ValuesToIndices; - - ValuesToIndices indices; - size_t prev_off = 0; - for (size_t i = 0; i < offsets.size(); ++i) - { - indices.clear(); - size_t off = offsets[i]; - for (size_t j = prev_off; j < off; ++j) - { - res_values[j] = ++indices[hash128(j, count, columns)]; - } - prev_off = off; - } - } + ColumnUInt32::Container_t & res_values); }; @@ -1956,135 +941,14 @@ public: static FunctionPtr create(const Context &) { return std::make_shared(); } private: - String getName() const override - { - return name; - } + String getName() const override; - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() != 1) - throw Exception{ - "Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 1.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH - }; - - const auto arg = arguments.front().get(); - - if (!typeid_cast(arg) && - !typeid_cast(arg) && - !typeid_cast(arg) & - !typeid_cast(arg)) - { - throw Exception{ - "Illegal type " + arg->getName() + " of argument of function " + getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT - }; - } - - return std::make_shared(arg->clone()); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; template - bool execute(Block & block, const IColumn * const arg, const size_t result) - { - if (const auto in = typeid_cast *>(arg)) - { - const auto & in_data = in->getData(); - const auto total_values = std::accumulate(std::begin(in_data), std::end(in_data), std::size_t{}, - [this] (const std::size_t lhs, const std::size_t rhs) { - const auto sum = lhs + rhs; - if (sum < lhs) - throw Exception{ - "A call to function " + getName() + " overflows, investigate the values of arguments you are passing", - ErrorCodes::ARGUMENT_OUT_OF_BOUND - }; + bool execute(Block & block, const IColumn * const arg, const size_t result); - return sum; - }); - - if (total_values > max_elements) - throw Exception{ - "A call to function " + getName() + " would produce " + std::to_string(total_values) + - " array elements, which is greater than the allowed maximum of " + std::to_string(max_elements), - ErrorCodes::ARGUMENT_OUT_OF_BOUND - }; - - const auto data_col = std::make_shared>(total_values); - const auto out = std::make_shared( - data_col, - std::make_shared(in->size())); - block.getByPosition(result).column = out; - - auto & out_data = data_col->getData(); - auto & out_offsets = out->getOffsets(); - - IColumn::Offset_t offset{}; - for (const auto i : ext::range(0, in->size())) - { - std::copy(ext::make_range_iterator(T{}), ext::make_range_iterator(in_data[i]), &out_data[offset]); - offset += in_data[i]; - out_offsets[i] = offset; - } - - return true; - } - else if (const auto in = typeid_cast *>(arg)) - { - const auto & in_data = in->getData(); - if ((in_data != 0) && (in->size() > (std::numeric_limits::max() / in_data))) - throw Exception{ - "A call to function " + getName() + " overflows, investigate the values of arguments you are passing", - ErrorCodes::ARGUMENT_OUT_OF_BOUND - }; - - const std::size_t total_values = in->size() * in_data; - if (total_values > max_elements) - throw Exception{ - "A call to function " + getName() + " would produce " + std::to_string(total_values) + - " array elements, which is greater than the allowed maximum of " + std::to_string(max_elements), - ErrorCodes::ARGUMENT_OUT_OF_BOUND - }; - - const auto data_col = std::make_shared>(total_values); - const auto out = std::make_shared( - data_col, - std::make_shared(in->size())); - block.getByPosition(result).column = out; - - auto & out_data = data_col->getData(); - auto & out_offsets = out->getOffsets(); - - IColumn::Offset_t offset{}; - for (const auto i : ext::range(0, in->size())) - { - std::copy(ext::make_range_iterator(T{}), ext::make_range_iterator(in_data), &out_data[offset]); - offset += in_data; - out_offsets[i] = offset; - } - - return true; - } - - return false; - } - - void execute(Block & block, const ColumnNumbers & arguments, const size_t result) override - { - const auto col = block.getByPosition(arguments[0]).column.get(); - - if (!execute(block, col, result) && - !execute(block, col, result) && - !execute(block, col, result) && - !execute(block, col, result)) - { - throw Exception{ - "Illegal column " + col->getName() + " of argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN - }; - } - } + void execute(Block & block, const ColumnNumbers & arguments, const size_t result) override; }; @@ -2095,246 +959,29 @@ public: static FunctionPtr create(const Context & context) { return std::make_shared(); } /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; /// Получить типы результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() != 1) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 1.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - const DataTypeArray * array_type = typeid_cast(arguments[0].get()); - if (!array_type) - throw Exception("Argument for function " + getName() + " must be array.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - return arguments[0]->clone(); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - if (executeConst(block, arguments, result)) - return; - - const ColumnArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - if (!array) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - - ColumnPtr res_ptr = array->cloneEmpty(); - block.getByPosition(result).column = res_ptr; - ColumnArray & res = static_cast(*res_ptr); - - const IColumn & src_data = array->getData(); - const ColumnArray::Offsets_t & src_offsets = array->getOffsets(); - IColumn & res_data = res.getData(); - ColumnArray::Offsets_t & res_offsets = res.getOffsets(); - - if (!( executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeNumber (src_data, src_offsets, res_data, res_offsets) - || executeString (src_data, src_offsets, res_data, res_offsets) - || executeFixedString (src_data, src_offsets, res_data, res_offsets))) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; private: - bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result) - { - if (const ColumnConstArray * const_array = typeid_cast(block.getByPosition(arguments[0]).column.get())) - { - if (const_array->getData().empty()) - { - auto nested_type = typeid_cast(*block.getByPosition(arguments[0]).type).getNestedType(); - - block.getByPosition(result).column = std::make_shared( - block.rowsInFirstColumn(), - Array{nested_type->getDefault()}, - nested_type->clone()); - } - else - block.getByPosition(result).column = block.getByPosition(arguments[0]).column; - - return true; - } - else - return false; - } + bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result); template bool executeNumber( const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, - IColumn & res_data_col, ColumnArray::Offsets_t & res_offsets) - { - if (const ColumnVector * src_data_concrete = typeid_cast *>(&src_data)) - { - const PaddedPODArray & src_data = src_data_concrete->getData(); - PaddedPODArray & res_data = typeid_cast &>(res_data_col).getData(); - size_t size = src_offsets.size(); - res_offsets.resize(size); - res_data.reserve(src_data.size()); - - ColumnArray::Offset_t src_prev_offset = 0; - ColumnArray::Offset_t res_prev_offset = 0; - - for (size_t i = 0; i < size; ++i) - { - if (src_offsets[i] != src_prev_offset) - { - size_t size_to_write = src_offsets[i] - src_prev_offset; - size_t prev_res_data_size = res_data.size(); - res_data.resize(prev_res_data_size + size_to_write); - memcpy(&res_data[prev_res_data_size], &src_data[src_prev_offset], size_to_write * sizeof(T)); - res_prev_offset += size_to_write; - res_offsets[i] = res_prev_offset; - } - else - { - res_data.push_back(T()); - ++res_prev_offset; - res_offsets[i] = res_prev_offset; - } - - src_prev_offset = src_offsets[i]; - } - - return true; - } - else - return false; - } + IColumn & res_data_col, ColumnArray::Offsets_t & res_offsets); bool executeFixedString( const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, - IColumn & res_data_col, ColumnArray::Offsets_t & res_offsets) - { - if (const ColumnFixedString * src_data_concrete = typeid_cast(&src_data)) - { - const size_t n = src_data_concrete->getN(); - const ColumnFixedString::Chars_t & src_data = src_data_concrete->getChars(); - ColumnFixedString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); - size_t size = src_offsets.size(); - res_offsets.resize(size); - res_data.reserve(src_data.size()); - - ColumnArray::Offset_t src_prev_offset = 0; - ColumnArray::Offset_t res_prev_offset = 0; - - for (size_t i = 0; i < size; ++i) - { - if (src_offsets[i] != src_prev_offset) - { - size_t size_to_write = src_offsets[i] - src_prev_offset; - size_t prev_res_data_size = res_data.size(); - res_data.resize(prev_res_data_size + size_to_write * n); - memcpy(&res_data[prev_res_data_size], &src_data[src_prev_offset], size_to_write * n); - res_prev_offset += size_to_write; - res_offsets[i] = res_prev_offset; - } - else - { - size_t prev_res_data_size = res_data.size(); - res_data.resize(prev_res_data_size + n); - memset(&res_data[prev_res_data_size], 0, n); - ++res_prev_offset; - res_offsets[i] = res_prev_offset; - } - - src_prev_offset = src_offsets[i]; - } - - return true; - } - else - return false; - } + IColumn & res_data_col, ColumnArray::Offsets_t & res_offsets); bool executeString( const IColumn & src_data, const ColumnArray::Offsets_t & src_array_offsets, - IColumn & res_data_col, ColumnArray::Offsets_t & res_array_offsets) - { - if (const ColumnString * src_data_concrete = typeid_cast(&src_data)) - { - const ColumnString::Offsets_t & src_string_offsets = src_data_concrete->getOffsets(); - ColumnString::Offsets_t & res_string_offsets = typeid_cast(res_data_col).getOffsets(); - - const ColumnString::Chars_t & src_data = src_data_concrete->getChars(); - ColumnString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); - - size_t size = src_array_offsets.size(); - res_array_offsets.resize(size); - res_string_offsets.reserve(src_string_offsets.size()); - res_data.reserve(src_data.size()); - - ColumnArray::Offset_t src_array_prev_offset = 0; - ColumnArray::Offset_t res_array_prev_offset = 0; - - ColumnString::Offset_t src_string_prev_offset = 0; - ColumnString::Offset_t res_string_prev_offset = 0; - - for (size_t i = 0; i < size; ++i) - { - if (src_array_offsets[i] != src_array_prev_offset) - { - size_t array_size = src_array_offsets[i] - src_array_prev_offset; - - size_t bytes_to_copy = 0; - size_t from_string_prev_offset_local = src_string_prev_offset; - for (size_t j = 0; j < array_size; ++j) - { - size_t string_size = src_string_offsets[src_array_prev_offset + j] - from_string_prev_offset_local; - - res_string_prev_offset += string_size; - res_string_offsets.push_back(res_string_prev_offset); - - from_string_prev_offset_local += string_size; - bytes_to_copy += string_size; - } - - size_t res_data_old_size = res_data.size(); - res_data.resize(res_data_old_size + bytes_to_copy); - memcpy(&res_data[res_data_old_size], &src_data[src_string_prev_offset], bytes_to_copy); - - res_array_prev_offset += array_size; - res_array_offsets[i] = res_array_prev_offset; - } - else - { - res_data.push_back(0); /// Пустая строка, включая ноль на конце. - - ++res_string_prev_offset; - res_string_offsets.push_back(res_string_prev_offset); - - ++res_array_prev_offset; - res_array_offsets[i] = res_array_prev_offset; - } - - src_array_prev_offset = src_array_offsets[i]; - - if (src_array_prev_offset) - src_string_prev_offset = src_string_offsets[src_array_prev_offset - 1]; - } - - return true; - } - else - return false; - } + IColumn & res_data_col, ColumnArray::Offsets_t & res_array_offsets); }; @@ -2345,215 +992,29 @@ public: static FunctionPtr create(const Context & context) { return std::make_shared(); } /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; /// Получить типы результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() != 1) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 1.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - const DataTypeArray * array_type = typeid_cast(arguments[0].get()); - if (!array_type) - throw Exception("Argument for function " + getName() + " must be array.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - return arguments[0]->clone(); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - if (executeConst(block, arguments, result)) - return; - - const ColumnArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); - if (!array) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - - ColumnPtr res_ptr = array->cloneEmpty(); - block.getByPosition(result).column = res_ptr; - ColumnArray & res = static_cast(*res_ptr); - - const IColumn & src_data = array->getData(); - const ColumnArray::Offsets_t & offsets = array->getOffsets(); - IColumn & res_data = res.getData(); - res.getOffsetsColumn() = array->getOffsetsColumn(); - - if (!( executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeNumber (src_data, offsets, res_data) - || executeString (src_data, offsets, res_data) - || executeFixedString (src_data, offsets, res_data))) - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of first argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; private: - bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result) - { - if (const ColumnConstArray * const_array = typeid_cast(block.getByPosition(arguments[0]).column.get())) - { - const Array & arr = const_array->getData(); - - size_t size = arr.size(); - Array res(size); - - for (size_t i = 0; i < size; ++i) - res[i] = arr[size - i - 1]; - - block.getByPosition(result).column = std::make_shared( - block.rowsInFirstColumn(), - res, - block.getByPosition(arguments[0]).type->clone()); - - return true; - } - else - return false; - } + bool executeConst(Block & block, const ColumnNumbers & arguments, size_t result); template bool executeNumber( const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, - IColumn & res_data_col) - { - if (const ColumnVector * src_data_concrete = typeid_cast *>(&src_data)) - { - const PaddedPODArray & src_data = src_data_concrete->getData(); - PaddedPODArray & res_data = typeid_cast &>(res_data_col).getData(); - size_t size = src_offsets.size(); - res_data.resize(src_data.size()); - - ColumnArray::Offset_t src_prev_offset = 0; - - for (size_t i = 0; i < size; ++i) - { - const T * src = &src_data[src_prev_offset]; - const T * src_end = &src_data[src_offsets[i]]; - - if (src == src_end) - continue; - - T * dst = &res_data[src_offsets[i] - 1]; - - while (src < src_end) - { - *dst = *src; - ++src; - --dst; - } - - src_prev_offset = src_offsets[i]; - } - - return true; - } - else - return false; - } + IColumn & res_data_col); bool executeFixedString( const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, - IColumn & res_data_col) - { - if (const ColumnFixedString * src_data_concrete = typeid_cast(&src_data)) - { - const size_t n = src_data_concrete->getN(); - const ColumnFixedString::Chars_t & src_data = src_data_concrete->getChars(); - ColumnFixedString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); - size_t size = src_offsets.size(); - res_data.resize(src_data.size()); - - ColumnArray::Offset_t src_prev_offset = 0; - - for (size_t i = 0; i < size; ++i) - { - const UInt8 * src = &src_data[src_prev_offset * n]; - const UInt8 * src_end = &src_data[src_offsets[i] * n]; - - if (src == src_end) - continue; - - UInt8 * dst = &res_data[src_offsets[i] * n - n]; - - while (src < src_end) - { - memcpySmallAllowReadWriteOverflow15(dst, src, n); - src += n; - dst -= n; - } - - src_prev_offset = src_offsets[i]; - } - - return true; - } - else - return false; - } + IColumn & res_data_col); bool executeString( const IColumn & src_data, const ColumnArray::Offsets_t & src_array_offsets, - IColumn & res_data_col) - { - if (const ColumnString * src_data_concrete = typeid_cast(&src_data)) - { - const ColumnString::Offsets_t & src_string_offsets = src_data_concrete->getOffsets(); - ColumnString::Offsets_t & res_string_offsets = typeid_cast(res_data_col).getOffsets(); - - const ColumnString::Chars_t & src_data = src_data_concrete->getChars(); - ColumnString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); - - size_t size = src_array_offsets.size(); - res_string_offsets.resize(src_string_offsets.size()); - res_data.resize(src_data.size()); - - ColumnArray::Offset_t src_array_prev_offset = 0; - ColumnString::Offset_t res_string_prev_offset = 0; - - for (size_t i = 0; i < size; ++i) - { - if (src_array_offsets[i] != src_array_prev_offset) - { - size_t array_size = src_array_offsets[i] - src_array_prev_offset; - - for (size_t j = 0; j < array_size; ++j) - { - size_t j_reversed = array_size - j - 1; - - auto src_pos = src_array_prev_offset + j_reversed == 0 ? 0 : src_string_offsets[src_array_prev_offset + j_reversed - 1]; - size_t string_size = src_string_offsets[src_array_prev_offset + j_reversed] - src_pos; - - memcpySmallAllowReadWriteOverflow15(&res_data[res_string_prev_offset], &src_data[src_pos], string_size); - - res_string_prev_offset += string_size; - res_string_offsets[src_array_prev_offset + j] = res_string_prev_offset; - } - } - - src_array_prev_offset = src_array_offsets[i]; - } - - return true; - } - else - return false; - } + IColumn & res_data_col); }; @@ -2567,167 +1028,14 @@ public: static FunctionPtr create(const Context & context) { return std::make_shared(); } /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; void getReturnTypeAndPrerequisites( const ColumnsWithTypeAndName & arguments, DataTypePtr & out_return_type, - std::vector & out_prerequisites) override - { - /// Первый аргумент - константная строка с именем агрегатной функции (возможно, с параметрами в скобках, например: "quantile(0.99)"). + std::vector & out_prerequisites) override; - if (arguments.size() < 2) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be at least 2.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - const ColumnConstString * aggregate_function_name_column = typeid_cast(arguments[0].column.get()); - if (!aggregate_function_name_column) - throw Exception("First argument for function " + getName() + " must be constant string: name of aggregate function.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - DataTypes argument_types(arguments.size() - 1); - for (size_t i = 1, size = arguments.size(); i < size; ++i) - { - const DataTypeArray * arg = typeid_cast(arguments[i].type.get()); - if (!arg) - throw Exception("Argument " + toString(i) + " for function " + getName() + " must be array.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - argument_types[i - 1] = arg->getNestedType()->clone(); - } - - if (!aggregate_function) - { - const String & aggregate_function_name_with_params = aggregate_function_name_column->getData(); - - if (aggregate_function_name_with_params.empty()) - throw Exception("First argument for function " + getName() + " (name of aggregate function) cannot be empty.", - ErrorCodes::BAD_ARGUMENTS); - - bool has_parameters = ')' == aggregate_function_name_with_params.back(); - - String aggregate_function_name = aggregate_function_name_with_params; - String parameters; - Array params_row; - - if (has_parameters) - { - size_t pos = aggregate_function_name_with_params.find('('); - if (pos == std::string::npos || pos + 2 >= aggregate_function_name_with_params.size()) - throw Exception("First argument for function " + getName() + " doesn't look like aggregate function name.", - ErrorCodes::BAD_ARGUMENTS); - - aggregate_function_name = aggregate_function_name_with_params.substr(0, pos); - parameters = aggregate_function_name_with_params.substr(pos + 1, aggregate_function_name_with_params.size() - pos - 2); - - if (aggregate_function_name.empty()) - throw Exception("First argument for function " + getName() + " doesn't look like aggregate function name.", - ErrorCodes::BAD_ARGUMENTS); - - ParserExpressionList params_parser(false); - ASTPtr args_ast = parseQuery(params_parser, - parameters.data(), parameters.data() + parameters.size(), - "parameters of aggregate function"); - - ASTExpressionList & args_list = typeid_cast(*args_ast); - - if (args_list.children.empty()) - throw Exception("Incorrect list of parameters to aggregate function " - + aggregate_function_name, ErrorCodes::BAD_ARGUMENTS); - - params_row.reserve(args_list.children.size()); - for (const auto & child : args_list.children) - { - const ASTLiteral * lit = typeid_cast(child.get()); - if (!lit) - throw Exception("Parameters to aggregate functions must be literals", - ErrorCodes::PARAMETERS_TO_AGGREGATE_FUNCTIONS_MUST_BE_LITERALS); - - params_row.push_back(lit->value); - } - } - - aggregate_function = AggregateFunctionFactory().get(aggregate_function_name, argument_types); - - /// Потому что владение состояниями агрегатных функций никуда не отдаётся. - if (aggregate_function->isState()) - throw Exception("Using aggregate function with -State modifier in function arrayReduce is not supported", ErrorCodes::BAD_ARGUMENTS); - - if (has_parameters) - aggregate_function->setParameters(params_row); - aggregate_function->setArguments(argument_types); - } - - out_return_type = aggregate_function->getReturnType(); - } - - - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - IAggregateFunction & agg_func = *aggregate_function.get(); - std::unique_ptr place_holder { new char[agg_func.sizeOfData()] }; - AggregateDataPtr place = place_holder.get(); - - size_t rows = block.rowsInFirstColumn(); - - /// Агрегатные функции не поддерживают константные столбцы. Поэтому, материализуем их. - std::vector materialized_columns; - - std::vector aggregate_arguments_vec(arguments.size() - 1); - - for (size_t i = 0, size = arguments.size() - 1; i < size; ++i) - { - const IColumn * col = block.unsafeGetByPosition(arguments[i + 1]).column.get(); - if (const ColumnArray * arr = typeid_cast(col)) - { - aggregate_arguments_vec[i] = arr->getDataPtr().get(); - } - else if (const ColumnConstArray * arr = typeid_cast(col)) - { - materialized_columns.emplace_back(arr->convertToFullColumn()); - aggregate_arguments_vec[i] = typeid_cast(*materialized_columns.back().get()).getDataPtr().get(); - } - else - throw Exception("Illegal column " + col->getName() + " as argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN); - - } - const IColumn ** aggregate_arguments = aggregate_arguments_vec.data(); - - const ColumnArray::Offsets_t & offsets = typeid_cast(!materialized_columns.empty() - ? *materialized_columns.front().get() - : *block.unsafeGetByPosition(arguments[1]).column.get()).getOffsets(); - - ColumnPtr result_holder = block.getByPosition(result).type->createColumn(); - block.getByPosition(result).column = result_holder; - IColumn & res_col = *result_holder.get(); - - ColumnArray::Offset_t current_offset = 0; - for (size_t i = 0; i < rows; ++i) - { - agg_func.create(place); - ColumnArray::Offset_t next_offset = offsets[i]; - - try - { - for (size_t j = current_offset; j < next_offset; ++j) - agg_func.add(place, aggregate_arguments, j, nullptr); - - agg_func.insertResultInto(place, res_col); - } - catch (...) - { - agg_func.destroy(place); - throw; - } - - agg_func.destroy(place); - current_offset = next_offset; - } - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; private: AggregateFunctionPtr aggregate_function; diff --git a/dbms/include/DB/Functions/FunctionsString.h b/dbms/include/DB/Functions/FunctionsString.h index 70564cc8e61..d4291297f16 100644 --- a/dbms/include/DB/Functions/FunctionsString.h +++ b/dbms/include/DB/Functions/FunctionsString.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -932,61 +931,13 @@ public: static FunctionPtr create(const Context & context) { return std::make_shared(); } /// Получить имя функции. - String getName() const override - { - return name; - } + String getName() const override; /// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() != 1) - throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 1.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - if (!typeid_cast(&*arguments[0]) && !typeid_cast(&*arguments[0]) - && !typeid_cast(&*arguments[0])) - throw Exception("Illegal type " + arguments[0]->getName() + " of argument of function " + getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - return arguments[0]->clone(); - } + DataTypePtr getReturnType(const DataTypes & arguments) const override; /// Выполнить функцию над блоком. - void execute(Block & block, const ColumnNumbers & arguments, size_t result) override - { - const ColumnPtr column = block.getByPosition(arguments[0]).column; - if (const ColumnString * col = typeid_cast(column.get())) - { - std::shared_ptr col_res = std::make_shared(); - block.getByPosition(result).column = col_res; - ReverseImpl::vector(col->getChars(), col->getOffsets(), - col_res->getChars(), col_res->getOffsets()); - } - else if (const ColumnFixedString * col = typeid_cast(column.get())) - { - auto col_res = std::make_shared(col->getN()); - block.getByPosition(result).column = col_res; - ReverseImpl::vector_fixed(col->getChars(), col->getN(), - col_res->getChars()); - } - else if (const ColumnConstString * col = typeid_cast(column.get())) - { - String res; - ReverseImpl::constant(col->getData(), res); - auto col_res = std::make_shared(col->size(), res); - block.getByPosition(result).column = col_res; - } - else if (typeid_cast(column.get()) || typeid_cast(column.get())) - { - FunctionArrayReverse().execute(block, arguments, result); - } - else - throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN); - } + void execute(Block & block, const ColumnNumbers & arguments, size_t result) override; }; @@ -1566,108 +1517,12 @@ public: static constexpr auto name = "appendTrailingCharIfAbsent"; static FunctionPtr create(const Context & context) { return std::make_shared(); } - String getName() const override - { - return name; - } + String getName() const override; private: - DataTypePtr getReturnType(const DataTypes & arguments) const override - { - if (arguments.size() != 2) - throw Exception{ - "Number of arguments for function " + getName() + " doesn't match: passed " - + toString(arguments.size()) + ", should be 2.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH - }; + DataTypePtr getReturnType(const DataTypes & arguments) const override; - if (!typeid_cast(arguments[0].get())) - throw Exception{ - "Illegal type " + arguments[0]->getName() + " of argument of function " + getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT - }; - - if (!typeid_cast(arguments[1].get())) - throw Exception{ - "Illegal type " + arguments[1]->getName() + " of argument of function " + getName(), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT - }; - - return std::make_shared(); - } - - void execute(Block & block, const ColumnNumbers & arguments, const size_t result) override - { - const auto & column = block.getByPosition(arguments[0]).column; - const auto & column_char = block.getByPosition(arguments[1]).column; - - if (!typeid_cast(column_char.get())) - throw Exception{ - "Second argument of function " + getName() + " must be a constant string", - ErrorCodes::ILLEGAL_COLUMN - }; - - const auto & trailing_char_str = static_cast(*column_char).getData(); - - if (trailing_char_str.size() != 1) - throw Exception{ - "Second argument of function " + getName() + " must be a one-character string", - ErrorCodes::BAD_ARGUMENTS - }; - - if (const auto col = typeid_cast(&*column)) - { - auto col_res = std::make_shared(); - block.getByPosition(result).column = col_res; - - const auto & src_data = col->getChars(); - const auto & src_offsets = col->getOffsets(); - - auto & dst_data = col_res->getChars(); - auto & dst_offsets = col_res->getOffsets(); - - const auto size = src_offsets.size(); - dst_data.resize(src_data.size() + size); - dst_offsets.resize(size); - - ColumnString::Offset_t src_offset{}; - ColumnString::Offset_t dst_offset{}; - - for (const auto i : ext::range(0, size)) - { - const auto src_length = src_offsets[i] - src_offset; - memcpySmallAllowReadWriteOverflow15(&dst_data[dst_offset], &src_data[src_offset], src_length); - src_offset = src_offsets[i]; - dst_offset += src_length; - - if (src_length > 1 && dst_data[dst_offset - 2] != trailing_char_str.front()) - { - dst_data[dst_offset - 1] = trailing_char_str.front(); - dst_data[dst_offset] = 0; - ++dst_offset; - } - - dst_offsets[i] = dst_offset; - } - - dst_data.resize_assume_reserved(dst_offset); - } - else if (const auto col = typeid_cast(&*column)) - { - const auto & in_data = col->getData(); - - block.getByPosition(result).column = std::make_shared( - col->size(), - in_data.size() == 0 ? in_data : - in_data.back() == trailing_char_str.front() ? in_data : in_data + trailing_char_str); - } - else - throw Exception{ - "Illegal column " + block.getByPosition(arguments[0]).column->getName() - + " of argument of function " + getName(), - ErrorCodes::ILLEGAL_COLUMN - }; - } + void execute(Block & block, const ColumnNumbers & arguments, const size_t result) override; }; diff --git a/dbms/include/DB/Functions/FunctionsStringSearch.h b/dbms/include/DB/Functions/FunctionsStringSearch.h index 29bb908647d..e2370aba065 100644 --- a/dbms/include/DB/Functions/FunctionsStringSearch.h +++ b/dbms/include/DB/Functions/FunctionsStringSearch.h @@ -1048,7 +1048,7 @@ struct ReplaceStringImpl /// Определим, к какому индексу оно относится. while (i < size && begin + n * (i + 1) <= match) { - res_offsets[i] = res_offset + ((begin + n * (i + 1)) - pos); + res_offsets[i] = res_offset + ((begin + n * (i + 1)) - pos) + 1; ++i; } res_offset += (match - pos); @@ -1061,7 +1061,7 @@ struct ReplaceStringImpl bool can_finish_current_string = false; /// Проверяем, что вхождение не переходит через границы строк. - if (match + needle.size() < begin + n * (i + 1)) + if (match + needle.size() - 1 < begin + n * (i + 1)) { res_data.resize(res_data.size() + replacement.size()); memcpy(&res_data[res_offset], replacement.data(), replacement.size()); @@ -1085,6 +1085,10 @@ struct ReplaceStringImpl pos = begin + n * (i + 1); } } + + if (i < size) { + res_offsets[i] = res_offset + ((begin + n * (i + 1)) - pos) + 1; + } } static void constant(const std::string & data, const std::string & needle, const std::string & replacement, diff --git a/dbms/include/DB/Storages/MergeTree/LevelMergeSelector.h b/dbms/include/DB/Storages/MergeTree/LevelMergeSelector.h index 7ac848f1fa7..369e6da6600 100644 --- a/dbms/include/DB/Storages/MergeTree/LevelMergeSelector.h +++ b/dbms/include/DB/Storages/MergeTree/LevelMergeSelector.h @@ -7,31 +7,14 @@ namespace DB { /** Select parts to merge based on its level. - * - * Select first range of parts at least min_parts_to_merge length with minimum level. - * - * If enough time has passed, lower min_parts_to_merge. - * And if no ranges of consecutive parts with same level, and much time has passed, - * allow to select parts of different level. - * This is done to allow further merging when table is not updated. + * Select first range of parts of parts_to_merge length with minimum level. */ class LevelMergeSelector : public IMergeSelector { public: struct Settings { - size_t min_parts_to_merge = 8; - size_t max_parts_to_merge = 100; - - /** min_parts_to_merge will be lowered by 1 after that time. - * It will be lowered by 2 after that time * 2^1, - * It will be lowered by 3 after that time * 2^2, - * and so on, exponentially. - */ - time_t lower_base_after = 300; - - /// If there are too much parts, merge any parts of same level, or if no such ranges, then totally ignore levels. - size_t fallback_after_num_parts = 50; + size_t parts_to_merge = 10; }; LevelMergeSelector(const Settings & settings) : settings(settings) {} diff --git a/dbms/include/DB/Storages/MergeTree/MergeList.h b/dbms/include/DB/Storages/MergeTree/MergeList.h index 251be557160..28b09100da5 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeList.h +++ b/dbms/include/DB/Storages/MergeTree/MergeList.h @@ -32,10 +32,21 @@ struct MergeInfo UInt64 total_size_bytes_compressed{}; UInt64 total_size_marks{}; std::atomic bytes_read_uncompressed{}; - std::atomic rows_read{}; std::atomic bytes_written_uncompressed{}; + + /// Updated only for Horizontal algorithm + std::atomic rows_read{}; std::atomic rows_written{}; + /// Updated only for Vertical algorithm + /// mutually exclusive with rows_read and rows_written, updated either rows_written either columns_written + std::atomic columns_written{}; + + /// Updated in both cases + /// Number of rows for which primary key columns have been written + std::atomic rows_with_key_columns_read{}; + std::atomic rows_with_key_columns_written{}; + MergeInfo(const std::string & database, const std::string & table, const std::string & result_part_name) : database{database}, table{table}, result_part_name{result_part_name} @@ -52,9 +63,12 @@ struct MergeInfo total_size_bytes_compressed(other.total_size_bytes_compressed), total_size_marks(other.total_size_marks), bytes_read_uncompressed(other.bytes_read_uncompressed.load(std::memory_order_relaxed)), - rows_read(other.rows_read.load(std::memory_order_relaxed)), bytes_written_uncompressed(other.bytes_written_uncompressed.load(std::memory_order_relaxed)), - rows_written(other.rows_written.load(std::memory_order_relaxed)) + rows_read(other.rows_read.load(std::memory_order_relaxed)), + rows_written(other.rows_written.load(std::memory_order_relaxed)), + columns_written(other.columns_written.load(std::memory_order_relaxed)), + rows_with_key_columns_read(other.rows_with_key_columns_read.load(std::memory_order_relaxed)), + rows_with_key_columns_written(other.rows_with_key_columns_written.load(std::memory_order_relaxed)) { } }; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h index e5bcbd70a82..f5a52a81e7d 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h @@ -24,7 +24,7 @@ public: const MarkRanges & mark_ranges_, bool use_uncompressed_cache_, ExpressionActionsPtr prewhere_actions_, String prewhere_column_, bool check_columns, size_t min_bytes_to_use_direct_io_, size_t max_read_buffer_size_, - bool save_marks_in_cache_); + bool save_marks_in_cache_, bool quiet = false); ~MergeTreeBlockInputStream() override; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index e1911c09875..ffc09973378 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -55,11 +55,11 @@ namespace ErrorCodes * Структура файлов: * / min-date _ max-date _ min-id _ max-id _ level / - директория с куском. * Внутри директории с куском: - * checksums.txt - список файлов с их размерами и контрольными суммами. - * columns.txt - список столбцов с их типами. + * checksums.txt - содержит список файлов с их размерами и контрольными суммами. + * columns.txt - содержит список столбцов с их типами. * primary.idx - индексный файл. - * Column.bin - данные столбца - * Column.mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк. + * [Column].bin - данные столбца + * [Column].mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк. * * Имеется несколько режимов работы, определяющих, что делать при мердже: * - Ordinary - ничего дополнительно не делать; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h index 066b2ffc579..c2257ade8d8 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h @@ -9,6 +9,7 @@ namespace DB { class MergeListEntry; +class MergeProgressCallback; struct ReshardingJob; @@ -125,6 +126,19 @@ public: bool isCancelled() const { return cancelled > 0; } +public: + + enum class MergeAlgorithm + { + Horizontal, /// per-row merge of all columns + Vertical /// per-row merge of PK columns, per-column gather for non-PK columns + }; + +private: + + MergeAlgorithm chooseMergeAlgorithm(const MergeTreeData & data, const MergeTreeData::DataPartsVector & parts, + size_t rows_upper_bound, MergedRowSources & rows_sources_to_alloc) const; + private: MergeTreeData & data; const BackgroundProcessingPool & pool; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataPart.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataPart.h index 32fd1ea4963..2e6780d8bda 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataPart.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataPart.h @@ -46,6 +46,8 @@ struct MergeTreeDataPartChecksums void addFile(const String & file_name, size_t file_size, uint128 file_hash); + void add(MergeTreeDataPartChecksums && rhs_checksums); + /// Проверяет, что множество столбцов и их контрольные суммы совпадают. Если нет - бросает исключение. /// Если have_uncompressed, для сжатых файлов сравнивает чексуммы разжатых данных. Иначе сравнивает только чексуммы файлов. void checkEqual(const MergeTreeDataPartChecksums & rhs, bool have_uncompressed) const; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h index d8a7ec7adc1..606700559ac 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h @@ -62,6 +62,7 @@ private: std::unique_ptr non_cached_buffer; std::string path_prefix; size_t max_mark_range; + bool is_empty = false; Stream( const String & path_prefix_, UncompressedCache * uncompressed_cache, @@ -69,9 +70,14 @@ private: const MarkRanges & all_mark_ranges, size_t aio_threshold, size_t max_read_buffer_size, const ReadBufferFromFileBase::ProfileCallback & profile_callback, clockid_t clock_type); + static std::unique_ptr createEmptyPtr(); + void loadMarks(MarkCache * cache, bool save_in_cache); void seekToMark(size_t index); + + private: + Stream() = default; }; using FileStreams = std::map>; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeSettings.h b/dbms/include/DB/Storages/MergeTree/MergeTreeSettings.h index db6b0ac8f7f..52256d40869 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeSettings.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeSettings.h @@ -16,13 +16,18 @@ struct MergeTreeSettings { /** Merge settings. */ - /// Maximum in total size of parts to merge, when there are maximum (minimum) free threads in background pool. + /// Maximum in total size of parts to merge, when there are maximum (minimum) free threads in background pool (or entries in replication queue). size_t max_bytes_to_merge_at_max_space_in_pool = 100ULL * 1024 * 1024 * 1024; size_t max_bytes_to_merge_at_min_space_in_pool = 1024 * 1024; /// How many tasks of merging parts are allowed simultaneously in ReplicatedMergeTree queue. size_t max_replicated_merges_in_queue = 16; + /// When there is less than specified number of free entries in pool (or replicated queue), + /// start to lower maximum size of merge to process (or to put in queue). + /// This is to allow small merges to process - not filling the pool with long running merges. + size_t number_of_free_entries_in_pool_to_lower_max_size_of_merge = 8; + /// How many seconds to keep obsolete parts. time_t old_parts_lifetime = 8 * 60; @@ -90,6 +95,9 @@ struct MergeTreeSettings /// Minimal absolute delay to close, stop serving requests and not return Ok during status check. size_t min_absolute_delay_to_close = 0; + /// Enable usage of Vertical merge algorithm. + size_t enable_vertical_merge_algorithm = 0; + void loadFromConfig(const String & config_elem, Poco::Util::AbstractConfiguration & config) { @@ -124,6 +132,7 @@ struct MergeTreeSettings SET_SIZE_T(min_relative_delay_to_yield_leadership); SET_SIZE_T(min_relative_delay_to_close); SET_SIZE_T(min_absolute_delay_to_close); + SET_SIZE_T(enable_vertical_merge_algorithm); #undef SET_SIZE_T #undef SET_DOUBLE diff --git a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h index 17b22048ad0..fbb68a711b6 100644 --- a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h @@ -11,6 +11,8 @@ #include #include +#include + namespace DB { @@ -33,6 +35,7 @@ public: { } + protected: using OffsetColumns = std::set; @@ -94,7 +97,8 @@ protected: using ColumnStreams = std::map>; - void addStream(const String & path, const String & name, const IDataType & type, size_t estimated_size = 0, size_t level = 0, String filename = "") + void addStream(const String & path, const String & name, const IDataType & type, size_t estimated_size = 0, + size_t level = 0, String filename = "", bool skip_offsets = false) { String escaped_column_name; if (filename.size()) @@ -105,23 +109,27 @@ protected: /// Для массивов используются отдельные потоки для размеров. if (const DataTypeArray * type_arr = typeid_cast(&type)) { - String size_name = DataTypeNested::extractNestedTableName(name) - + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); - String escaped_size_name = escapeForFileName(DataTypeNested::extractNestedTableName(name)) - + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); + if (!skip_offsets) + { + String size_name = DataTypeNested::extractNestedTableName(name) + + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); + String escaped_size_name = escapeForFileName(DataTypeNested::extractNestedTableName(name)) + + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); - column_streams[size_name] = std::make_unique( - escaped_size_name, - path + escaped_size_name + ".bin", - path + escaped_size_name + ".mrk", - max_compress_block_size, - compression_method, - estimated_size, - aio_threshold); + column_streams[size_name] = std::make_unique( + escaped_size_name, + path + escaped_size_name + ".bin", + path + escaped_size_name + ".mrk", + max_compress_block_size, + compression_method, + estimated_size, + aio_threshold); + } addStream(path, name, *type_arr->getNestedType(), estimated_size, level + 1); } else + { column_streams[name] = std::make_unique( escaped_column_name, path + escaped_column_name + ".bin", @@ -130,16 +138,19 @@ protected: compression_method, estimated_size, aio_threshold); + } } /// Записать данные одного столбца. - void writeData(const String & name, const IDataType & type, const IColumn & column, OffsetColumns & offset_columns, size_t level = 0) + void writeData(const String & name, const IDataType & type, const IColumn & column, OffsetColumns & offset_columns, + size_t level = 0, bool skip_offsets = false) { size_t size = column.size(); /// Для массивов требуется сначала сериализовать размеры, а потом значения. - if (const DataTypeArray * type_arr = typeid_cast(&type)) + const DataTypeArray * type_arr = typeid_cast(&type); + if (!skip_offsets && type_arr) { String size_name = DataTypeNested::extractNestedTableName(name) + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); @@ -306,11 +317,16 @@ public: throw Exception("Method writeSuffix is not supported by MergedBlockOutputStream", ErrorCodes::NOT_IMPLEMENTED); } - MergeTreeData::DataPart::Checksums writeSuffixAndGetChecksums() + MergeTreeData::DataPart::Checksums writeSuffixAndGetChecksums( + const NamesAndTypesList & total_column_list, + MergeTreeData::DataPart::Checksums * additional_column_checksums = nullptr) { /// Заканчиваем запись и достаем чексуммы. MergeTreeData::DataPart::Checksums checksums; + if (additional_column_checksums) + checksums = std::move(*additional_column_checksums); + if (storage.merging_params.mode != MergeTreeData::MergingParams::Unsorted) { index_stream->next(); @@ -319,10 +335,10 @@ public: index_stream = nullptr; } - for (ColumnStreams::iterator it = column_streams.begin(); it != column_streams.end(); ++it) + for (auto & column_stream : column_streams) { - it->second->finalize(); - it->second->addToChecksums(checksums); + column_stream.second->finalize(); + column_stream.second->addToChecksums(checksums); } column_streams.clear(); @@ -338,7 +354,7 @@ public: { /// Записываем файл с описанием столбцов. WriteBufferFromFile out(part_path + "columns.txt", 4096); - columns_list.writeText(out); + total_column_list.writeText(out); } { @@ -350,6 +366,11 @@ public: return checksums; } + MergeTreeData::DataPart::Checksums writeSuffixAndGetChecksums() + { + return writeSuffixAndGetChecksums(columns_list, nullptr); + } + MergeTreeData::DataPart::Index & getIndex() { return index_columns; @@ -488,12 +509,13 @@ private: class MergedColumnOnlyOutputStream : public IMergedBlockOutputStream { public: - MergedColumnOnlyOutputStream(MergeTreeData & storage_, String part_path_, bool sync_, CompressionMethod compression_method) + MergedColumnOnlyOutputStream(MergeTreeData & storage_, String part_path_, bool sync_, CompressionMethod compression_method, + bool skip_offsets_ = false) : IMergedBlockOutputStream( storage_, storage_.context.getSettings().min_compress_block_size, storage_.context.getSettings().max_compress_block_size, compression_method, storage_.context.getSettings().min_bytes_to_use_direct_io), - part_path(part_path_), sync(sync_) + part_path(part_path_), sync(sync_), skip_offsets(skip_offsets_) { } @@ -505,7 +527,7 @@ public: for (size_t i = 0; i < block.columns(); ++i) { addStream(part_path, block.getByPosition(i).name, - *block.getByPosition(i).type, 0, 0, block.getByPosition(i).name); + *block.getByPosition(i).type, 0, 0, block.getByPosition(i).name, skip_offsets); } initialized = true; } @@ -516,7 +538,7 @@ public: for (size_t i = 0; i < block.columns(); ++i) { const ColumnWithTypeAndName & column = block.getByPosition(i); - writeData(column.name, *column.type, *column.column, offset_columns); + writeData(column.name, *column.type, *column.column, offset_columns, 0, skip_offsets); } size_t written_for_last_mark = (storage.index_granularity - index_offset + rows) % storage.index_granularity; @@ -537,8 +559,7 @@ public: column_stream.second->finalize(); if (sync) column_stream.second->sync(); - std::string column = escapeForFileName(column_stream.first); - column_stream.second->addToChecksums(checksums, column); + column_stream.second->addToChecksums(checksums); } column_streams.clear(); @@ -552,6 +573,7 @@ private: bool initialized = false; bool sync; + bool skip_offsets; }; } diff --git a/dbms/src/Client/CMakeLists.txt b/dbms/src/Client/CMakeLists.txt index a74c2617373..ca50941c8e1 100644 --- a/dbms/src/Client/CMakeLists.txt +++ b/dbms/src/Client/CMakeLists.txt @@ -1,30 +1,45 @@ -find_library(READLINE_LIB - NAMES libreadline.a libreadline.so - HINTS "/usr/local/opt/readline/lib") +set (READLINE_HINTS "/usr/local/opt/readline/lib") +if (USE_STATIC_LIBRARIES) + find_library (READLINE_LIB NAMES libreadline.a HINTS ${READLINE_HINTS}) +else () + find_library (READLINE_LIB NAMES readline HINTS ${READLINE_HINTS}) +endif () +if (USE_STATIC_LIBRARIES) + find_library (EDIT_LIB NAMES libedit.a) +else () + find_library (EDIT_LIB NAMES edit) +endif () +if (USE_STATIC_LIBRARIES) + find_library (CURSES_LIB NAMES libcurses.a) +else () + find_library (CURSES_LIB NAMES curses) +endif () +if (USE_STATIC_LIBRARIES) + find_library (TERMCAP_LIB NAMES libtermcap.a termcap) +else () + find_library (TERMCAP_LIB NAMES termcap) +endif () -find_library(LIBEDIT_LIB - NAMES libedit.a libedit.so) +if (READLINE_LIB) + include_directories ("/usr/local/opt/readline/include") + add_definitions (-D USE_READLINE) + set (LINE_EDITING_LIBS ${READLINE_LIB} ${TERMCAP_LIB}) + message (STATUS "Using line editing libraries: ${LINE_EDITING_LIBS}") +elseif (EDIT_LIB) + add_definitions (-D USE_LIBEDIT) + set (LINE_EDITING_LIBS ${EDIT_LIB} ${CURSES_LIB} ${TERMCAP_LIB}) + message (STATUS "Using line editing libraries: ${LINE_EDITING_LIBS}") +else () + message (STATUS "Not using any library for line editing.") +endif () -if(READLINE_LIB) - include_directories("/usr/local/opt/readline/include") - add_definitions(-D USE_READLINE) - set(LINE_EDITING_LIBS ${READLINE_LIB} libtermcap.a) - message(STATUS "Using line editing libraries: ${LINE_EDITING_LIBS}") -elseif(LIBEDIT_LIB) - add_definitions(-D USE_LIBEDIT) - set(LINE_EDITING_LIBS ${LIBEDIT_LIB} libcurses.a libtermcap.a) - message(STATUS "Using line editing libraries: ${LINE_EDITING_LIBS}") -else() - message(STATUS "Not using any library for line editing.") -endif() +add_library (clickhouse-client Client.cpp) +target_link_libraries (clickhouse-client dbms ${LINE_EDITING_LIBS} ${Boost_PROGRAM_OPTIONS_LIBRARY}) +install (FILES config.xml DESTINATION /etc/clickhouse-client COMPONENT clickhouse-client) -add_library(clickhouse-client Client.cpp) -target_link_libraries (clickhouse-client dbms ${LINE_EDITING_LIBS} ${BOOST_PROGRAM_OPTIONS_LIB}) -INSTALL(FILES config.xml DESTINATION /etc/clickhouse-client COMPONENT clickhouse-client) +add_library (clickhouse-benchmark Benchmark.cpp) +target_link_libraries (clickhouse-benchmark dbms ${Boost_PROGRAM_OPTIONS_LIBRARY}) -add_library(clickhouse-benchmark Benchmark.cpp) -target_link_libraries (clickhouse-benchmark dbms ${BOOST_PROGRAM_OPTIONS_LIB}) - -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/Common/CMakeLists.txt b/dbms/src/Common/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/Common/CMakeLists.txt +++ b/dbms/src/Common/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/Common/tests/CMakeLists.txt b/dbms/src/Common/tests/CMakeLists.txt index 4a1279eadf6..9937fcc8268 100644 --- a/dbms/src/Common/tests/CMakeLists.txt +++ b/dbms/src/Common/tests/CMakeLists.txt @@ -32,7 +32,7 @@ add_executable (simple_cache simple_cache.cpp) target_link_libraries (simple_cache common) add_executable (compact_array compact_array.cpp) -target_link_libraries (compact_array dbms ${BOOST_FILESYSTEM_LIB}) +target_link_libraries (compact_array dbms ${Boost_FILESYSTEM_LIBRARY}) add_executable (radix_sort radix_sort.cpp) target_link_libraries (radix_sort dbms) diff --git a/dbms/src/Core/CMakeLists.txt b/dbms/src/Core/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/Core/CMakeLists.txt +++ b/dbms/src/Core/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/DataStreams/CMakeLists.txt b/dbms/src/DataStreams/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/DataStreams/CMakeLists.txt +++ b/dbms/src/DataStreams/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/DataStreams/CollapsingSortedBlockInputStream.cpp b/dbms/src/DataStreams/CollapsingSortedBlockInputStream.cpp index b3d80677fe3..1cf51ab0d94 100644 --- a/dbms/src/DataStreams/CollapsingSortedBlockInputStream.cpp +++ b/dbms/src/DataStreams/CollapsingSortedBlockInputStream.cpp @@ -55,6 +55,13 @@ void CollapsingSortedBlockInputStream::insertRows(ColumnPlainPtrs & merged_colum ++merged_rows; for (size_t i = 0; i < num_columns; ++i) merged_columns[i]->insertFrom(*last_negative.columns[i], last_negative.row_num); + + if (out_row_sources) + { + /// true flag value means "skip row" + out_row_sources->data()[last_positive_pos].setSkipFlag(false); + out_row_sources->data()[last_negative_pos].setSkipFlag(false); + } } return; } @@ -64,6 +71,9 @@ void CollapsingSortedBlockInputStream::insertRows(ColumnPlainPtrs & merged_colum ++merged_rows; for (size_t i = 0; i < num_columns; ++i) merged_columns[i]->insertFrom(*first_negative.columns[i], first_negative.row_num); + + if (out_row_sources) + out_row_sources->data()[first_negative_pos].setSkipFlag(false); } if (count_positive >= count_negative) @@ -71,6 +81,9 @@ void CollapsingSortedBlockInputStream::insertRows(ColumnPlainPtrs & merged_colum ++merged_rows; for (size_t i = 0; i < num_columns; ++i) merged_columns[i]->insertFrom(*last_positive.columns[i], last_positive.row_num); + + if (out_row_sources) + out_row_sources->data()[last_positive_pos].setSkipFlag(false); } if (!(count_positive == count_negative || count_positive + 1 == count_negative || count_positive == count_negative + 1)) @@ -123,7 +136,7 @@ void CollapsingSortedBlockInputStream::merge(ColumnPlainPtrs & merged_columns, s size_t merged_rows = 0; /// Вынимаем строки в нужном порядке и кладём в merged_block, пока строк не больше max_block_size - while (!queue.empty()) + for (; !queue.empty(); ++current_pos) { TSortCursor current = queue.top(); @@ -149,6 +162,10 @@ void CollapsingSortedBlockInputStream::merge(ColumnPlainPtrs & merged_columns, s queue.pop(); + /// Initially, skip all rows. On insert, unskip "corner" rows. + if (out_row_sources) + out_row_sources->emplace_back(current.impl->order, true); + if (key_differs) { /// Запишем данные для предыдущего первичного ключа. @@ -166,13 +183,21 @@ void CollapsingSortedBlockInputStream::merge(ColumnPlainPtrs & merged_columns, s last_is_positive = true; setRowRef(last_positive, current); + last_positive_pos = current_pos; } else if (sign == -1) { if (!count_negative) + { setRowRef(first_negative, current); + first_negative_pos = current_pos; + } + if (!blocks_written && !merged_rows) + { setRowRef(last_negative, current); + last_negative_pos = current_pos; + } ++count_negative; last_is_positive = false; diff --git a/dbms/src/DataStreams/ColumnGathererStream.cpp b/dbms/src/DataStreams/ColumnGathererStream.cpp new file mode 100644 index 00000000000..0c46baf207e --- /dev/null +++ b/dbms/src/DataStreams/ColumnGathererStream.cpp @@ -0,0 +1,111 @@ +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; + extern const int INCOMPATIBLE_COLUMNS; + extern const int INCORRECT_NUMBER_OF_COLUMNS; + extern const int EMPTY_DATA_PASSED; + extern const int RECEIVED_EMPTY_DATA; +} + +ColumnGathererStream::ColumnGathererStream(const BlockInputStreams & source_streams, const String & column_name_, + const MergedRowSources & pos_to_source_idx_, size_t block_size_) +: name(column_name_), pos_to_source_idx(pos_to_source_idx_), block_size(block_size_) +{ + if (source_streams.empty()) + throw Exception("There are no streams to gather", ErrorCodes::EMPTY_DATA_PASSED); + + children.assign(source_streams.begin(), source_streams.end()); + + sources.reserve(children.size()); + for (size_t i = 0; i < children.size(); i++) + { + sources.emplace_back(children[i]->read(), name); + + Block & block = sources.back().block; + + /// Sometimes MergeTreeReader injects additional column with partitioning key + if (block.columns() > 2 || !block.has(name)) + throw Exception("Block should have 1 or 2 columns and contain column with requested name", ErrorCodes::INCORRECT_NUMBER_OF_COLUMNS); + + if (i == 0) + { + column.name = name; + column.type = block.getByName(name).type->clone(); + column.column = column.type->createColumn(); + } + + if (block.getByName(name).column->getName() != column.column->getName()) + throw Exception("Column types don't match", ErrorCodes::INCOMPATIBLE_COLUMNS); + } +} + + +String ColumnGathererStream::getID() const +{ + std::stringstream res; + + res << getName() << "("; + for (size_t i = 0; i < children.size(); i++) + res << (i == 0 ? "" : ", " ) << children[i]->getID(); + res << ")"; + + return res.str(); +} + + +Block ColumnGathererStream::readImpl() +{ + if (children.size() == 1) + return children[0]->read(); + + if (pos_global >= pos_to_source_idx.size()) + return Block(); + + Block block_res{column.cloneEmpty()}; + IColumn & column_res = *block_res.unsafeGetByPosition(0).column; + + size_t pos_finish = std::min(pos_global + block_size, pos_to_source_idx.size()); + column_res.reserve(pos_finish - pos_global); + + for (size_t pos = pos_global; pos < pos_finish; ++pos) + { + auto source_id = pos_to_source_idx[pos].getSourceNum(); + bool skip = pos_to_source_idx[pos].getSkipFlag(); + Source & source = sources[source_id]; + + if (source.pos >= source.size) /// Fetch new block + { + try + { + source.block = children[source_id]->read(); + source.update(name); + } + catch (Exception & e) + { + e.addMessage("Cannot fetch required block. Stream " + children[source_id]->getID() + ", part " + toString(source_id)); + throw; + } + + if (0 == source.size) + { + throw Exception("Fetched block is empty. Stream " + children[source_id]->getID() + ", part " + toString(source_id), + ErrorCodes::RECEIVED_EMPTY_DATA); + } + } + + if (!skip) + column_res.insertFrom(*source.column, source.pos); //TODO: vectorize + ++source.pos; + } + + pos_global = pos_finish; + + return block_res; +} + +} diff --git a/dbms/src/DataStreams/MergingSortedBlockInputStream.cpp b/dbms/src/DataStreams/MergingSortedBlockInputStream.cpp index 9a8a641c7cd..4f14b5465c1 100644 --- a/dbms/src/DataStreams/MergingSortedBlockInputStream.cpp +++ b/dbms/src/DataStreams/MergingSortedBlockInputStream.cpp @@ -195,13 +195,10 @@ void MergingSortedBlockInputStream::merge(Block & merged_block, ColumnPlainPtrs return; } - size_t source_num = 0; - size_t size = cursors.size(); - for (; source_num < size; ++source_num) - if (&cursors[source_num] == current.impl) - break; + /// Actually, current.impl->order stores source number (i.e. cursors[current.impl->order] == current.impl) + size_t source_num = current.impl->order; - if (source_num == size) + if (source_num >= cursors.size()) throw Exception("Logical error in MergingSortedBlockInputStream", ErrorCodes::LOGICAL_ERROR); for (size_t i = 0; i < num_columns; ++i) @@ -224,6 +221,9 @@ void MergingSortedBlockInputStream::merge(Block & merged_block, ColumnPlainPtrs finished = true; } + if (out_row_sources) + out_row_sources->resize_fill(out_row_sources->size() + merged_rows, RowSourcePart(source_num)); + // std::cerr << "fetching next block\n"; total_merged_rows += merged_rows; @@ -236,6 +236,12 @@ void MergingSortedBlockInputStream::merge(Block & merged_block, ColumnPlainPtrs for (size_t i = 0; i < num_columns; ++i) merged_columns[i]->insertFrom(*current->all_columns[i], current->pos); + if (out_row_sources) + { + /// Actually, current.impl->order stores source number (i.e. cursors[current.impl->order] == current.impl) + out_row_sources->emplace_back(current.impl->order); + } + if (!current->isLast()) { // std::cerr << "moving to next row\n"; diff --git a/dbms/src/DataTypes/CMakeLists.txt b/dbms/src/DataTypes/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/DataTypes/CMakeLists.txt +++ b/dbms/src/DataTypes/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/Functions/CMakeLists.txt b/dbms/src/Functions/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/Functions/CMakeLists.txt +++ b/dbms/src/Functions/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/Functions/FunctionsArray.cpp b/dbms/src/Functions/FunctionsArray.cpp index 2313f60cd37..eefd92e401d 100644 --- a/dbms/src/Functions/FunctionsArray.cpp +++ b/dbms/src/Functions/FunctionsArray.cpp @@ -4,6 +4,1930 @@ namespace DB { +void FunctionArray::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + size_t num_elements = arguments.size(); + bool is_const = true; + + for (const auto arg_num : arguments) + { + if (!block.getByPosition(arg_num).column->isConst()) + { + is_const = false; + break; + } + } + + const auto first_arg = block.getByPosition(arguments[0]); + DataTypePtr result_type = first_arg.type; + DataTypeTraits::EnrichedDataTypePtr enriched_result_type; + if (result_type->behavesAsNumber()) + { + /// If type is numeric, calculate least common type. + DataTypes types; + types.reserve(num_elements); + + for (const auto & argument : arguments) + types.push_back(block.getByPosition(argument).type); + + enriched_result_type = getLeastCommonType(types); + result_type = enriched_result_type.first; + } + + if (is_const) + { + Array arr; + for (const auto arg_num : arguments) + if (block.getByPosition(arg_num).type->getName() == result_type->getName()) + /// Если элемент такого же типа как результат, просто добавляем его в ответ + arr.push_back((*block.getByPosition(arg_num).column)[0]); + else + /// Иначе необходимо привести его к типу результата + addField(result_type, (*block.getByPosition(arg_num).column)[0], arr); + + block.getByPosition(result).column = std::make_shared( + first_arg.column->size(), arr, std::make_shared(result_type)); + } + else + { + size_t block_size = block.rowsInFirstColumn(); + + /** If part of columns have not same type as common type of all elements of array, + * then convert them to common type. + * If part of columns are constants, + * then convert them to full columns. + */ + + Columns columns_holder(num_elements); + const IColumn * columns[num_elements]; + + for (size_t i = 0; i < num_elements; ++i) + { + const auto & arg = block.getByPosition(arguments[i]); + + String result_type_name = result_type->getName(); + ColumnPtr preprocessed_column = arg.column; + + if (arg.type->getName() != result_type_name) + { + Block temporary_block + { + { + arg.column, + arg.type, + arg.name + }, + { + std::make_shared(block_size, result_type_name), + std::make_shared(), + "" + }, + { + nullptr, + result_type, + "" + } + }; + + FunctionCast func_cast(context); + + { + DataTypePtr unused_return_type; + ColumnsWithTypeAndName arguments{ temporary_block.unsafeGetByPosition(0), temporary_block.unsafeGetByPosition(1) }; + std::vector unused_prerequisites; + + /// Prepares function to execution. TODO It is not obvious. + func_cast.getReturnTypeAndPrerequisites(arguments, unused_return_type, unused_prerequisites); + } + + func_cast.execute(temporary_block, {0, 1}, 2); + preprocessed_column = temporary_block.unsafeGetByPosition(2).column; + } + + if (auto materialized_column = preprocessed_column->convertToFullColumnIfConst()) + preprocessed_column = materialized_column; + + columns_holder[i] = std::move(preprocessed_column); + columns[i] = columns_holder[i].get(); + } + + /** Create and fill the result array. + */ + + auto out = std::make_shared(result_type->createColumn()); + IColumn & out_data = out->getData(); + IColumn::Offsets_t & out_offsets = out->getOffsets(); + + out_data.reserve(block_size * num_elements); + out_offsets.resize(block_size); + + IColumn::Offset_t current_offset = 0; + for (size_t i = 0; i < block_size; ++i) + { + for (size_t j = 0; j < num_elements; ++j) + out_data.insertFrom(*columns[j], i); + + current_offset += num_elements; + out_offsets[i] = current_offset; + } + + block.getByPosition(result).column = out; + } +} + + +DataTypePtr FunctionArray::getReturnType(const DataTypes & arguments) const +{ + if (arguments.empty()) + { + if (is_case_mode) + throw Exception{"Either WHEN clauses or THEN clauses are missing " + "in the CASE construction.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH}; + else + throw Exception{"Function array requires at least one argument.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH}; + } + + DataTypePtr result_type = arguments[0]; + + if (result_type->behavesAsNumber()) + { + /// Если тип числовой, пробуем выделить наименьший общий тип + auto enriched_result_type = getLeastCommonType(arguments); + return std::make_shared(enriched_result_type); + } + else + { + /// Иначе все аргументы должны быть одинаковыми + for (size_t i = 1, size = arguments.size(); i < size; ++i) + { + if (arguments[i]->getName() != arguments[0]->getName()) + { + if (is_case_mode) + throw Exception{"Found type discrepancy in either WHEN " + "clauses or THEN clauses of the CASE construction", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; + else + throw Exception{"Arguments for function array must have same type or behave as number.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; + } + } + + return std::make_shared(result_type); + } +} + + +void FunctionArray::setCaseMode() +{ + is_case_mode = true; +} + + +String FunctionArray::getName() const +{ + return is_case_mode ? "CASE" : name; +} + + +template +bool FunctionArray::tryAddField(DataTypePtr type_res, const Field & f, Array & arr) const +{ + if (typeid_cast(type_res.get())) + { + arr.push_back(apply_visitor(FieldVisitorConvertToNumber(), f)); + return true; + } + return false; +} + + +bool FunctionArray::addField(DataTypePtr type_res, const Field & f, Array & arr) const +{ + /// Иначе необходимо + if ( tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) + || tryAddField(type_res, f, arr) ) + return true; + else + { + if (is_case_mode) + throw Exception{"Illegal type encountered while processing the CASE construction.", + ErrorCodes::LOGICAL_ERROR}; + else + throw Exception{"Illegal result type " + type_res->getName() + " of function " + getName(), + ErrorCodes::LOGICAL_ERROR}; + } +} + + +DataTypeTraits::EnrichedDataTypePtr FunctionArray::getLeastCommonType(const DataTypes & arguments) const +{ + DataTypeTraits::EnrichedDataTypePtr result_type; + + try + { + result_type = Conditional::getArrayType(arguments); + } + catch (const Conditional::CondException & ex) + { + /// Translate a context-free error into a contextual error. + if (is_case_mode) + { + if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_ILLEGAL_COLUMN_TYPE) + throw Exception{"Illegal type of column " + ex.getMsg1() + + " in CASE construction", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; + else if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_UPSCALING_ERROR) + throw Exception{"THEN/ELSE clause parameters in CASE construction are not upscalable to a " + "common type without loss of precision: " + ex.getMsg1(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; + else + throw Exception{"An unexpected error has occurred in CASE expression", + ErrorCodes::LOGICAL_ERROR}; + } + else + { + if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_ILLEGAL_COLUMN_TYPE) + throw Exception{"Illegal type of column " + ex.getMsg1() + + " in array", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; + else if (ex.getCode() == Conditional::CondErrorCodes::TYPE_DEDUCER_UPSCALING_ERROR) + throw Exception("Arguments of function " + getName() + " are not upscalable " + "to a common type without loss of precision.", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + else + throw Exception{"An unexpected error has occurred in function " + getName(), + ErrorCodes::LOGICAL_ERROR}; + } + } + + return result_type; +} + + + +String FunctionArrayElement::getName() const +{ + return name; +} + + +DataTypePtr FunctionArrayElement::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() != 2) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 2.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + const DataTypeArray * array_type = typeid_cast(arguments[0].get()); + if (!array_type) + throw Exception("First argument for function " + getName() + " must be array.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + if (!arguments[1]->isNumeric() + || (!startsWith(arguments[1]->getName(), "UInt") && !startsWith(arguments[1]->getName(), "Int"))) + throw Exception("Second argument for function " + getName() + " must have UInt or Int type.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return array_type->getNestedType(); +} + + +void FunctionArrayElement::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (executeTuple(block, arguments, result)) + { + } + else if (!block.getByPosition(arguments[1]).column->isConst()) + { + if (!( executeArgument (block, arguments, result) + || executeArgument (block, arguments, result) + || executeArgument (block, arguments, result) + || executeArgument (block, arguments, result) + || executeArgument (block, arguments, result) + || executeArgument (block, arguments, result) + || executeArgument (block, arguments, result) + || executeArgument (block, arguments, result))) + throw Exception("Second argument for function " + getName() + " must must have UInt or Int type.", + ErrorCodes::ILLEGAL_COLUMN); + } + else + { + Field index = (*block.getByPosition(arguments[1]).column)[0]; + + if (index == UInt64(0)) + throw Exception("Array indices is 1-based", ErrorCodes::ZERO_ARRAY_OR_TUPLE_INDEX); + + if (!( executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeNumberConst (block, arguments, result, index) + || executeConstConst (block, arguments, result, index) + || executeStringConst (block, arguments, result, index) + || executeGenericConst (block, arguments, result, index))) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + } +} + + +template +bool FunctionArrayElement::executeNumberConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) +{ + const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const ColumnVector * col_nested = typeid_cast *>(&col_array->getData()); + + if (!col_nested) + return false; + + auto col_res = std::make_shared>(); + block.getByPosition(result).column = col_res; + + if (index.getType() == Field::Types::UInt64) + ArrayElementNumImpl::template vectorConst( + col_nested->getData(), col_array->getOffsets(), safeGet(index) - 1, col_res->getData()); + else if (index.getType() == Field::Types::Int64) + ArrayElementNumImpl::template vectorConst( + col_nested->getData(), col_array->getOffsets(), -safeGet(index) - 1, col_res->getData()); + else + throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); + + return true; +} + + +template +bool FunctionArrayElement::executeNumber(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) +{ + const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const ColumnVector * col_nested = typeid_cast *>(&col_array->getData()); + + if (!col_nested) + return false; + + auto col_res = std::make_shared>(); + block.getByPosition(result).column = col_res; + + ArrayElementNumImpl::template vector( + col_nested->getData(), col_array->getOffsets(), indices, col_res->getData()); + + return true; +} + + +bool FunctionArrayElement::executeStringConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) +{ + const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const ColumnString * col_nested = typeid_cast(&col_array->getData()); + + if (!col_nested) + return false; + + std::shared_ptr col_res = std::make_shared(); + block.getByPosition(result).column = col_res; + + if (index.getType() == Field::Types::UInt64) + ArrayElementStringImpl::vectorConst( + col_nested->getChars(), + col_array->getOffsets(), + col_nested->getOffsets(), + safeGet(index) - 1, + col_res->getChars(), + col_res->getOffsets()); + else if (index.getType() == Field::Types::Int64) + ArrayElementStringImpl::vectorConst( + col_nested->getChars(), + col_array->getOffsets(), + col_nested->getOffsets(), + -safeGet(index) - 1, + col_res->getChars(), + col_res->getOffsets()); + else + throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); + + return true; +} + + +template +bool FunctionArrayElement::executeString(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) +{ + const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const ColumnString * col_nested = typeid_cast(&col_array->getData()); + + if (!col_nested) + return false; + + std::shared_ptr col_res = std::make_shared(); + block.getByPosition(result).column = col_res; + + ArrayElementStringImpl::vector( + col_nested->getChars(), + col_array->getOffsets(), + col_nested->getOffsets(), + indices, + col_res->getChars(), + col_res->getOffsets()); + + return true; +} + + +bool FunctionArrayElement::executeGenericConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) +{ + const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const auto & col_nested = col_array->getData(); + auto col_res = col_nested.cloneEmpty(); + block.getByPosition(result).column = col_res; + + if (index.getType() == Field::Types::UInt64) + ArrayElementGenericImpl::vectorConst( + col_nested, col_array->getOffsets(), safeGet(index) - 1, *col_res); + else if (index.getType() == Field::Types::Int64) + ArrayElementGenericImpl::vectorConst( + col_nested, col_array->getOffsets(), -safeGet(index) - 1, *col_res); + else + throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); + + return true; +} + + +template +bool FunctionArrayElement::executeGeneric(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) +{ + const ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const auto & col_nested = col_array->getData(); + auto col_res = col_nested.cloneEmpty(); + block.getByPosition(result).column = col_res; + + ArrayElementGenericImpl::vector( + col_nested, col_array->getOffsets(), indices, *col_res); + + return true; +} + + +bool FunctionArrayElement::executeConstConst(Block & block, const ColumnNumbers & arguments, size_t result, const Field & index) +{ + const ColumnConstArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const DB::Array & array = col_array->getData(); + size_t array_size = array.size(); + size_t real_index = 0; + + if (index.getType() == Field::Types::UInt64) + real_index = safeGet(index) - 1; + else if (index.getType() == Field::Types::Int64) + real_index = array_size + safeGet(index); + else + throw Exception("Illegal type of array index", ErrorCodes::LOGICAL_ERROR); + + Field value = col_array->getData().at(real_index); + + block.getByPosition(result).column = block.getByPosition(result).type->createConstColumn( + block.rowsInFirstColumn(), + value); + + return true; +} + + +template +bool FunctionArrayElement::executeConst(Block & block, const ColumnNumbers & arguments, size_t result, const PaddedPODArray & indices) +{ + const ColumnConstArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + const DB::Array & array = col_array->getData(); + size_t array_size = array.size(); + + block.getByPosition(result).column = block.getByPosition(result).type->createColumn(); + + for (size_t i = 0; i < col_array->size(); ++i) + { + IndexType index = indices[i]; + if (index > 0 && static_cast(index) <= array_size) + block.getByPosition(result).column->insert(array[index - 1]); + else if (index < 0 && static_cast(-index) <= array_size) + block.getByPosition(result).column->insert(array[array_size + index]); + else + block.getByPosition(result).column->insertDefault(); + } + + return true; +} + + +template +bool FunctionArrayElement::executeArgument(Block & block, const ColumnNumbers & arguments, size_t result) +{ + auto index = typeid_cast *>(block.getByPosition(arguments[1]).column.get()); + + if (!index) + return false; + + const auto & index_data = index->getData(); + + if (!( executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeNumber (block, arguments, result, index_data) + || executeConst (block, arguments, result, index_data) + || executeString (block, arguments, result, index_data) + || executeGeneric (block, arguments, result, index_data))) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of first argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN); + + return true; +} + + +bool FunctionArrayElement::executeTuple(Block & block, const ColumnNumbers & arguments, size_t result) +{ + ColumnArray * col_array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + + if (!col_array) + return false; + + ColumnTuple * col_nested = typeid_cast(&col_array->getData()); + + if (!col_nested) + return false; + + Block & tuple_block = col_nested->getData(); + size_t tuple_size = tuple_block.columns(); + + /** Будем вычислять функцию для кортежа внутренностей массива. + * Для этого создадим временный блок. + * Он будет состоять из следующих столбцов: + * - индекс массива, который нужно взять; + * - массив из первых элементов кортежей; + * - результат взятия элементов по индексу для массива из первых элементов кортежей; + * - массив из вторых элементов кортежей; + * - результат взятия элементов по индексу для массива из вторых элементов кортежей; + * ... + */ + Block block_of_temporary_results; + block_of_temporary_results.insert(block.getByPosition(arguments[1])); + + /// результаты взятия элементов по индексу для массивов из каждых элементов кортежей; + Block result_tuple_block; + + for (size_t i = 0; i < tuple_size; ++i) + { + ColumnWithTypeAndName array_of_tuple_section; + array_of_tuple_section.column = std::make_shared( + tuple_block.getByPosition(i).column, col_array->getOffsetsColumn()); + array_of_tuple_section.type = std::make_shared( + tuple_block.getByPosition(i).type); + block_of_temporary_results.insert(array_of_tuple_section); + + ColumnWithTypeAndName array_elements_of_tuple_section; + block_of_temporary_results.insert(array_elements_of_tuple_section); + + execute(block_of_temporary_results, ColumnNumbers{i * 2 + 1, 0}, i * 2 + 2); + + result_tuple_block.insert(block_of_temporary_results.getByPosition(i * 2 + 2)); + } + + auto col_res = std::make_shared(result_tuple_block); + block.getByPosition(result).column = col_res; + + return true; +} + + + +String FunctionArrayEnumerate::getName() const +{ + return name; +} + + +DataTypePtr FunctionArrayEnumerate::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() != 1) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + const DataTypeArray * array_type = typeid_cast(arguments[0].get()); + if (!array_type) + throw Exception("First argument for function " + getName() + " must be an array but it has type " + + arguments[0]->getName() + ".", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return std::make_shared(std::make_shared()); +} + + +void FunctionArrayEnumerate::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (const ColumnArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get())) + { + const ColumnArray::Offsets_t & offsets = array->getOffsets(); + + auto res_nested = std::make_shared(); + auto res_array = std::make_shared(res_nested, array->getOffsetsColumn()); + block.getByPosition(result).column = res_array; + + ColumnUInt32::Container_t & res_values = res_nested->getData(); + res_values.resize(array->getData().size()); + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + { + res_values[j] = j - prev_off + 1; + } + prev_off = off; + } + } + else if (const ColumnConstArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get())) + { + const Array & values = array->getData(); + + Array res_values(values.size()); + for (size_t i = 0; i < values.size(); ++i) + { + res_values[i] = i + 1; + } + + auto res_array = std::make_shared(array->size(), res_values, std::make_shared(std::make_shared())); + block.getByPosition(result).column = res_array; + } + else + { + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + } +} + + + +String FunctionArrayUniq::getName() const +{ + return name; +} + + +DataTypePtr FunctionArrayUniq:: getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() == 0) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be at least 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + for (size_t i = 0; i < arguments.size(); ++i) + { + const DataTypeArray * array_type = typeid_cast(arguments[i].get()); + if (!array_type) + throw Exception("All arguments for function " + getName() + " must be arrays but argument " + + toString(i + 1) + " has type " + arguments[i]->getName() + ".", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + + return std::make_shared(); +} + + +void FunctionArrayUniq::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (arguments.size() == 1 && executeConst(block, arguments, result)) + return; + + Columns array_columns(arguments.size()); + const ColumnArray::Offsets_t * offsets = nullptr; + ConstColumnPlainPtrs data_columns(arguments.size()); + + for (size_t i = 0; i < arguments.size(); ++i) + { + ColumnPtr array_ptr = block.getByPosition(arguments[i]).column; + const ColumnArray * array = typeid_cast(array_ptr.get()); + if (!array) + { + const ColumnConstArray * const_array = typeid_cast( + block.getByPosition(arguments[i]).column.get()); + if (!const_array) + throw Exception("Illegal column " + block.getByPosition(arguments[i]).column->getName() + + " of " + toString(i + 1) + "-th argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + array_ptr = const_array->convertToFullColumn(); + array = typeid_cast(array_ptr.get()); + } + array_columns[i] = array_ptr; + const ColumnArray::Offsets_t & offsets_i = array->getOffsets(); + if (!i) + offsets = &offsets_i; + else if (offsets_i != *offsets) + throw Exception("Lengths of all arrays passsed to " + getName() + " must be equal.", + ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH); + data_columns[i] = &array->getData(); + } + + const ColumnArray * first_array = typeid_cast(array_columns[0].get()); + auto res = std::make_shared(); + block.getByPosition(result).column = res; + + ColumnUInt32::Container_t & res_values = res->getData(); + res_values.resize(offsets->size()); + + if (arguments.size() == 1) + { + if (!( executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeString (first_array, res_values))) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + } + else + { + if (!execute128bit(*offsets, data_columns, res_values)) + executeHashed(*offsets, data_columns, res_values); + } +} + + +template +bool FunctionArrayUniq::executeNumber(const ColumnArray * array, ColumnUInt32::Container_t & res_values) +{ + const ColumnVector * nested = typeid_cast *>(&array->getData()); + if (!nested) + return false; + const ColumnArray::Offsets_t & offsets = array->getOffsets(); + const typename ColumnVector::Container_t & values = nested->getData(); + + typedef ClearableHashSet, HashTableGrower, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(T)> > Set; + + Set set; + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + set.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + set.insert(values[j]); + + res_values[i] = set.size(); + prev_off = off; + } + return true; +} + + +bool FunctionArrayUniq::executeString(const ColumnArray * array, ColumnUInt32::Container_t & res_values) +{ + const ColumnString * nested = typeid_cast(&array->getData()); + if (!nested) + return false; + const ColumnArray::Offsets_t & offsets = array->getOffsets(); + + typedef ClearableHashSet, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(StringRef)> > Set; + + Set set; + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + set.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + set.insert(nested->getDataAt(j)); + + res_values[i] = set.size(); + prev_off = off; + } + return true; +} + + +bool FunctionArrayUniq::executeConst(Block & block, const ColumnNumbers & arguments, size_t result) +{ + const ColumnConstArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + if (!array) + return false; + const Array & values = array->getData(); + + std::set set; + for (size_t i = 0; i < values.size(); ++i) + set.insert(values[i]); + + block.getByPosition(result).column = std::make_shared(array->size(), set.size()); + return true; +} + + +bool FunctionArrayUniq::execute128bit( + const ColumnArray::Offsets_t & offsets, + const ConstColumnPlainPtrs & columns, + ColumnUInt32::Container_t & res_values) +{ + size_t count = columns.size(); + size_t keys_bytes = 0; + Sizes key_sizes(count); + for (size_t j = 0; j < count; ++j) + { + if (!columns[j]->isFixed()) + return false; + key_sizes[j] = columns[j]->sizeOfField(); + keys_bytes += key_sizes[j]; + } + if (keys_bytes > 16) + return false; + + typedef ClearableHashSet, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > Set; + + Set set; + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + set.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + set.insert(packFixed(j, count, columns, key_sizes)); + + res_values[i] = set.size(); + prev_off = off; + } + + return true; +} + + +void FunctionArrayUniq::executeHashed( + const ColumnArray::Offsets_t & offsets, + const ConstColumnPlainPtrs & columns, + ColumnUInt32::Container_t & res_values) +{ + size_t count = columns.size(); + + typedef ClearableHashSet, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > Set; + + Set set; + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + set.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + set.insert(hash128(j, count, columns)); + + res_values[i] = set.size(); + prev_off = off; + } +} + + + +String FunctionArrayEnumerateUniq::getName() const +{ + return name; +} + + +DataTypePtr FunctionArrayEnumerateUniq::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() == 0) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be at least 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + for (size_t i = 0; i < arguments.size(); ++i) + { + const DataTypeArray * array_type = typeid_cast(arguments[i].get()); + if (!array_type) + throw Exception("All arguments for function " + getName() + " must be arrays but argument " + + toString(i + 1) + " has type " + arguments[i]->getName() + ".", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + + return std::make_shared(std::make_shared()); +} + + +void FunctionArrayEnumerateUniq::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (arguments.size() == 1 && executeConst(block, arguments, result)) + return; + + Columns array_columns(arguments.size()); + const ColumnArray::Offsets_t * offsets = nullptr; + ConstColumnPlainPtrs data_columns(arguments.size()); + + for (size_t i = 0; i < arguments.size(); ++i) + { + ColumnPtr array_ptr = block.getByPosition(arguments[i]).column; + const ColumnArray * array = typeid_cast(array_ptr.get()); + if (!array) + { + const ColumnConstArray * const_array = typeid_cast( + block.getByPosition(arguments[i]).column.get()); + if (!const_array) + throw Exception("Illegal column " + block.getByPosition(arguments[i]).column->getName() + + " of " + toString(i + 1) + "-th argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + array_ptr = const_array->convertToFullColumn(); + array = typeid_cast(array_ptr.get()); + } + array_columns[i] = array_ptr; + const ColumnArray::Offsets_t & offsets_i = array->getOffsets(); + if (!i) + offsets = &offsets_i; + else if (offsets_i != *offsets) + throw Exception("Lengths of all arrays passsed to " + getName() + " must be equal.", + ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH); + data_columns[i] = &array->getData(); + } + + const ColumnArray * first_array = typeid_cast(array_columns[0].get()); + auto res_nested = std::make_shared(); + auto res_array = std::make_shared(res_nested, first_array->getOffsetsColumn()); + block.getByPosition(result).column = res_array; + + ColumnUInt32::Container_t & res_values = res_nested->getData(); + if (!offsets->empty()) + res_values.resize(offsets->back()); + + if (arguments.size() == 1) + { + if (!( executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeNumber (first_array, res_values) + || executeString (first_array, res_values))) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + } + else + { + if (!execute128bit(*offsets, data_columns, res_values)) + executeHashed(*offsets, data_columns, res_values); + } +} + + +template +bool FunctionArrayEnumerateUniq::executeNumber(const ColumnArray * array, ColumnUInt32::Container_t & res_values) +{ + const ColumnVector * nested = typeid_cast *>(&array->getData()); + if (!nested) + return false; + const ColumnArray::Offsets_t & offsets = array->getOffsets(); + const typename ColumnVector::Container_t & values = nested->getData(); + + typedef ClearableHashMap, HashTableGrower, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(T)> > ValuesToIndices; + + ValuesToIndices indices; + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + indices.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + { + res_values[j] = ++indices[values[j]]; + } + prev_off = off; + } + return true; +} + + +bool FunctionArrayEnumerateUniq::executeString(const ColumnArray * array, ColumnUInt32::Container_t & res_values) +{ + const ColumnString * nested = typeid_cast(&array->getData()); + if (!nested) + return false; + const ColumnArray::Offsets_t & offsets = array->getOffsets(); + + size_t prev_off = 0; + typedef ClearableHashMap, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(StringRef)> > ValuesToIndices; + + ValuesToIndices indices; + for (size_t i = 0; i < offsets.size(); ++i) + { + indices.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + { + res_values[j] = ++indices[nested->getDataAt(j)]; + } + prev_off = off; + } + return true; +} + + +bool FunctionArrayEnumerateUniq::executeConst(Block & block, const ColumnNumbers & arguments, size_t result) +{ + const ColumnConstArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + if (!array) + return false; + const Array & values = array->getData(); + + Array res_values(values.size()); + std::map indices; + for (size_t i = 0; i < values.size(); ++i) + { + res_values[i] = static_cast(++indices[values[i]]); + } + + auto res_array = std::make_shared(array->size(), res_values, std::make_shared(std::make_shared())); + block.getByPosition(result).column = res_array; + + return true; +} + + +bool FunctionArrayEnumerateUniq::execute128bit( + const ColumnArray::Offsets_t & offsets, + const ConstColumnPlainPtrs & columns, + ColumnUInt32::Container_t & res_values) +{ + size_t count = columns.size(); + size_t keys_bytes = 0; + Sizes key_sizes(count); + for (size_t j = 0; j < count; ++j) + { + if (!columns[j]->isFixed()) + return false; + key_sizes[j] = columns[j]->sizeOfField(); + keys_bytes += key_sizes[j]; + } + if (keys_bytes > 16) + return false; + + typedef ClearableHashMap, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > ValuesToIndices; + + ValuesToIndices indices; + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + indices.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + { + res_values[j] = ++indices[packFixed(j, count, columns, key_sizes)]; + } + prev_off = off; + } + + return true; +} + + +void FunctionArrayEnumerateUniq::executeHashed( + const ColumnArray::Offsets_t & offsets, + const ConstColumnPlainPtrs & columns, + ColumnUInt32::Container_t & res_values) +{ + size_t count = columns.size(); + + typedef ClearableHashMap, + HashTableAllocatorWithStackMemory<(1 << INITIAL_SIZE_DEGREE) * sizeof(UInt128)> > ValuesToIndices; + + ValuesToIndices indices; + size_t prev_off = 0; + for (size_t i = 0; i < offsets.size(); ++i) + { + indices.clear(); + size_t off = offsets[i]; + for (size_t j = prev_off; j < off; ++j) + { + res_values[j] = ++indices[hash128(j, count, columns)]; + } + prev_off = off; + } +} + + + +String FunctionRange::getName() const +{ + return name; +} + + +DataTypePtr FunctionRange::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() != 1) + throw Exception{ + "Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH + }; + + const auto arg = arguments.front().get(); + + if (!typeid_cast(arg) && + !typeid_cast(arg) && + !typeid_cast(arg) & + !typeid_cast(arg)) + { + throw Exception{ + "Illegal type " + arg->getName() + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT + }; + } + + return std::make_shared(arg->clone()); +} + + +template +bool FunctionRange::execute(Block & block, const IColumn * const arg, const size_t result) +{ + if (const auto in = typeid_cast *>(arg)) + { + const auto & in_data = in->getData(); + const auto total_values = std::accumulate(std::begin(in_data), std::end(in_data), std::size_t{}, + [this] (const std::size_t lhs, const std::size_t rhs) { + const auto sum = lhs + rhs; + if (sum < lhs) + throw Exception{ + "A call to function " + getName() + " overflows, investigate the values of arguments you are passing", + ErrorCodes::ARGUMENT_OUT_OF_BOUND + }; + + return sum; + }); + + if (total_values > max_elements) + throw Exception{ + "A call to function " + getName() + " would produce " + std::to_string(total_values) + + " array elements, which is greater than the allowed maximum of " + std::to_string(max_elements), + ErrorCodes::ARGUMENT_OUT_OF_BOUND + }; + + const auto data_col = std::make_shared>(total_values); + const auto out = std::make_shared( + data_col, + std::make_shared(in->size())); + block.getByPosition(result).column = out; + + auto & out_data = data_col->getData(); + auto & out_offsets = out->getOffsets(); + + IColumn::Offset_t offset{}; + for (const auto i : ext::range(0, in->size())) + { + std::copy(ext::make_range_iterator(T{}), ext::make_range_iterator(in_data[i]), &out_data[offset]); + offset += in_data[i]; + out_offsets[i] = offset; + } + + return true; + } + else if (const auto in = typeid_cast *>(arg)) + { + const auto & in_data = in->getData(); + if ((in_data != 0) && (in->size() > (std::numeric_limits::max() / in_data))) + throw Exception{ + "A call to function " + getName() + " overflows, investigate the values of arguments you are passing", + ErrorCodes::ARGUMENT_OUT_OF_BOUND + }; + + const std::size_t total_values = in->size() * in_data; + if (total_values > max_elements) + throw Exception{ + "A call to function " + getName() + " would produce " + std::to_string(total_values) + + " array elements, which is greater than the allowed maximum of " + std::to_string(max_elements), + ErrorCodes::ARGUMENT_OUT_OF_BOUND + }; + + const auto data_col = std::make_shared>(total_values); + const auto out = std::make_shared( + data_col, + std::make_shared(in->size())); + block.getByPosition(result).column = out; + + auto & out_data = data_col->getData(); + auto & out_offsets = out->getOffsets(); + + IColumn::Offset_t offset{}; + for (const auto i : ext::range(0, in->size())) + { + std::copy(ext::make_range_iterator(T{}), ext::make_range_iterator(in_data), &out_data[offset]); + offset += in_data; + out_offsets[i] = offset; + } + + return true; + } + + return false; +} + + +void FunctionRange::execute(Block & block, const ColumnNumbers & arguments, const size_t result) +{ + const auto col = block.getByPosition(arguments[0]).column.get(); + + if (!execute(block, col, result) && + !execute(block, col, result) && + !execute(block, col, result) && + !execute(block, col, result)) + { + throw Exception{ + "Illegal column " + col->getName() + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN + }; + } +} + + + +String FunctionEmptyArrayToSingle::getName() const +{ + return name; +} + + +DataTypePtr FunctionEmptyArrayToSingle::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() != 1) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + const DataTypeArray * array_type = typeid_cast(arguments[0].get()); + if (!array_type) + throw Exception("Argument for function " + getName() + " must be array.", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return arguments[0]->clone(); +} + + +void FunctionEmptyArrayToSingle::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (executeConst(block, arguments, result)) + return; + + const ColumnArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + if (!array) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + + ColumnPtr res_ptr = array->cloneEmpty(); + block.getByPosition(result).column = res_ptr; + ColumnArray & res = static_cast(*res_ptr); + + const IColumn & src_data = array->getData(); + const ColumnArray::Offsets_t & src_offsets = array->getOffsets(); + IColumn & res_data = res.getData(); + ColumnArray::Offsets_t & res_offsets = res.getOffsets(); + + if (!( executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeNumber (src_data, src_offsets, res_data, res_offsets) + || executeString (src_data, src_offsets, res_data, res_offsets) + || executeFixedString (src_data, src_offsets, res_data, res_offsets))) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); +} + + +bool FunctionEmptyArrayToSingle::executeConst(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (const ColumnConstArray * const_array = typeid_cast(block.getByPosition(arguments[0]).column.get())) + { + if (const_array->getData().empty()) + { + auto nested_type = typeid_cast(*block.getByPosition(arguments[0]).type).getNestedType(); + + block.getByPosition(result).column = std::make_shared( + block.rowsInFirstColumn(), + Array{nested_type->getDefault()}, + nested_type->clone()); + } + else + block.getByPosition(result).column = block.getByPosition(arguments[0]).column; + + return true; + } + else + return false; +} + + +template +bool FunctionEmptyArrayToSingle::executeNumber( + const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, + IColumn & res_data_col, ColumnArray::Offsets_t & res_offsets) +{ + if (const ColumnVector * src_data_concrete = typeid_cast *>(&src_data)) + { + const PaddedPODArray & src_data = src_data_concrete->getData(); + PaddedPODArray & res_data = typeid_cast &>(res_data_col).getData(); + size_t size = src_offsets.size(); + res_offsets.resize(size); + res_data.reserve(src_data.size()); + + ColumnArray::Offset_t src_prev_offset = 0; + ColumnArray::Offset_t res_prev_offset = 0; + + for (size_t i = 0; i < size; ++i) + { + if (src_offsets[i] != src_prev_offset) + { + size_t size_to_write = src_offsets[i] - src_prev_offset; + size_t prev_res_data_size = res_data.size(); + res_data.resize(prev_res_data_size + size_to_write); + memcpy(&res_data[prev_res_data_size], &src_data[src_prev_offset], size_to_write * sizeof(T)); + res_prev_offset += size_to_write; + res_offsets[i] = res_prev_offset; + } + else + { + res_data.push_back(T()); + ++res_prev_offset; + res_offsets[i] = res_prev_offset; + } + + src_prev_offset = src_offsets[i]; + } + + return true; + } + else + return false; +} + + +bool FunctionEmptyArrayToSingle::executeFixedString( + const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, + IColumn & res_data_col, ColumnArray::Offsets_t & res_offsets) +{ + if (const ColumnFixedString * src_data_concrete = typeid_cast(&src_data)) + { + const size_t n = src_data_concrete->getN(); + const ColumnFixedString::Chars_t & src_data = src_data_concrete->getChars(); + ColumnFixedString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); + size_t size = src_offsets.size(); + res_offsets.resize(size); + res_data.reserve(src_data.size()); + + ColumnArray::Offset_t src_prev_offset = 0; + ColumnArray::Offset_t res_prev_offset = 0; + + for (size_t i = 0; i < size; ++i) + { + if (src_offsets[i] != src_prev_offset) + { + size_t size_to_write = src_offsets[i] - src_prev_offset; + size_t prev_res_data_size = res_data.size(); + res_data.resize(prev_res_data_size + size_to_write * n); + memcpy(&res_data[prev_res_data_size], &src_data[src_prev_offset], size_to_write * n); + res_prev_offset += size_to_write; + res_offsets[i] = res_prev_offset; + } + else + { + size_t prev_res_data_size = res_data.size(); + res_data.resize(prev_res_data_size + n); + memset(&res_data[prev_res_data_size], 0, n); + ++res_prev_offset; + res_offsets[i] = res_prev_offset; + } + + src_prev_offset = src_offsets[i]; + } + + return true; + } + else + return false; +} + + +bool FunctionEmptyArrayToSingle::executeString( + const IColumn & src_data, const ColumnArray::Offsets_t & src_array_offsets, + IColumn & res_data_col, ColumnArray::Offsets_t & res_array_offsets) +{ + if (const ColumnString * src_data_concrete = typeid_cast(&src_data)) + { + const ColumnString::Offsets_t & src_string_offsets = src_data_concrete->getOffsets(); + ColumnString::Offsets_t & res_string_offsets = typeid_cast(res_data_col).getOffsets(); + + const ColumnString::Chars_t & src_data = src_data_concrete->getChars(); + ColumnString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); + + size_t size = src_array_offsets.size(); + res_array_offsets.resize(size); + res_string_offsets.reserve(src_string_offsets.size()); + res_data.reserve(src_data.size()); + + ColumnArray::Offset_t src_array_prev_offset = 0; + ColumnArray::Offset_t res_array_prev_offset = 0; + + ColumnString::Offset_t src_string_prev_offset = 0; + ColumnString::Offset_t res_string_prev_offset = 0; + + for (size_t i = 0; i < size; ++i) + { + if (src_array_offsets[i] != src_array_prev_offset) + { + size_t array_size = src_array_offsets[i] - src_array_prev_offset; + + size_t bytes_to_copy = 0; + size_t from_string_prev_offset_local = src_string_prev_offset; + for (size_t j = 0; j < array_size; ++j) + { + size_t string_size = src_string_offsets[src_array_prev_offset + j] - from_string_prev_offset_local; + + res_string_prev_offset += string_size; + res_string_offsets.push_back(res_string_prev_offset); + + from_string_prev_offset_local += string_size; + bytes_to_copy += string_size; + } + + size_t res_data_old_size = res_data.size(); + res_data.resize(res_data_old_size + bytes_to_copy); + memcpy(&res_data[res_data_old_size], &src_data[src_string_prev_offset], bytes_to_copy); + + res_array_prev_offset += array_size; + res_array_offsets[i] = res_array_prev_offset; + } + else + { + res_data.push_back(0); /// Пустая строка, включая ноль на конце. + + ++res_string_prev_offset; + res_string_offsets.push_back(res_string_prev_offset); + + ++res_array_prev_offset; + res_array_offsets[i] = res_array_prev_offset; + } + + src_array_prev_offset = src_array_offsets[i]; + + if (src_array_prev_offset) + src_string_prev_offset = src_string_offsets[src_array_prev_offset - 1]; + } + + return true; + } + else + return false; +} + + + +String FunctionArrayReverse::getName() const +{ + return name; +} + + +DataTypePtr FunctionArrayReverse::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() != 1) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + const DataTypeArray * array_type = typeid_cast(arguments[0].get()); + if (!array_type) + throw Exception("Argument for function " + getName() + " must be array.", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return arguments[0]->clone(); +} + + +void FunctionArrayReverse::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (executeConst(block, arguments, result)) + return; + + const ColumnArray * array = typeid_cast(block.getByPosition(arguments[0]).column.get()); + if (!array) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + + ColumnPtr res_ptr = array->cloneEmpty(); + block.getByPosition(result).column = res_ptr; + ColumnArray & res = static_cast(*res_ptr); + + const IColumn & src_data = array->getData(); + const ColumnArray::Offsets_t & offsets = array->getOffsets(); + IColumn & res_data = res.getData(); + res.getOffsetsColumn() = array->getOffsetsColumn(); + + if (!( executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeNumber (src_data, offsets, res_data) + || executeString (src_data, offsets, res_data) + || executeFixedString (src_data, offsets, res_data))) + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); +} + + +bool FunctionArrayReverse::executeConst(Block & block, const ColumnNumbers & arguments, size_t result) +{ + if (const ColumnConstArray * const_array = typeid_cast(block.getByPosition(arguments[0]).column.get())) + { + const Array & arr = const_array->getData(); + + size_t size = arr.size(); + Array res(size); + + for (size_t i = 0; i < size; ++i) + res[i] = arr[size - i - 1]; + + block.getByPosition(result).column = std::make_shared( + block.rowsInFirstColumn(), + res, + block.getByPosition(arguments[0]).type->clone()); + + return true; + } + else + return false; +} + + +template +bool FunctionArrayReverse::executeNumber( + const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, + IColumn & res_data_col) +{ + if (const ColumnVector * src_data_concrete = typeid_cast *>(&src_data)) + { + const PaddedPODArray & src_data = src_data_concrete->getData(); + PaddedPODArray & res_data = typeid_cast &>(res_data_col).getData(); + size_t size = src_offsets.size(); + res_data.resize(src_data.size()); + + ColumnArray::Offset_t src_prev_offset = 0; + + for (size_t i = 0; i < size; ++i) + { + const T * src = &src_data[src_prev_offset]; + const T * src_end = &src_data[src_offsets[i]]; + + if (src == src_end) + continue; + + T * dst = &res_data[src_offsets[i] - 1]; + + while (src < src_end) + { + *dst = *src; + ++src; + --dst; + } + + src_prev_offset = src_offsets[i]; + } + + return true; + } + else + return false; +} + + +bool FunctionArrayReverse::executeFixedString( + const IColumn & src_data, const ColumnArray::Offsets_t & src_offsets, + IColumn & res_data_col) +{ + if (const ColumnFixedString * src_data_concrete = typeid_cast(&src_data)) + { + const size_t n = src_data_concrete->getN(); + const ColumnFixedString::Chars_t & src_data = src_data_concrete->getChars(); + ColumnFixedString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); + size_t size = src_offsets.size(); + res_data.resize(src_data.size()); + + ColumnArray::Offset_t src_prev_offset = 0; + + for (size_t i = 0; i < size; ++i) + { + const UInt8 * src = &src_data[src_prev_offset * n]; + const UInt8 * src_end = &src_data[src_offsets[i] * n]; + + if (src == src_end) + continue; + + UInt8 * dst = &res_data[src_offsets[i] * n - n]; + + while (src < src_end) + { + memcpySmallAllowReadWriteOverflow15(dst, src, n); + src += n; + dst -= n; + } + + src_prev_offset = src_offsets[i]; + } + + return true; + } + else + return false; +} + + +bool FunctionArrayReverse::executeString( + const IColumn & src_data, const ColumnArray::Offsets_t & src_array_offsets, + IColumn & res_data_col) +{ + if (const ColumnString * src_data_concrete = typeid_cast(&src_data)) + { + const ColumnString::Offsets_t & src_string_offsets = src_data_concrete->getOffsets(); + ColumnString::Offsets_t & res_string_offsets = typeid_cast(res_data_col).getOffsets(); + + const ColumnString::Chars_t & src_data = src_data_concrete->getChars(); + ColumnString::Chars_t & res_data = typeid_cast(res_data_col).getChars(); + + size_t size = src_array_offsets.size(); + res_string_offsets.resize(src_string_offsets.size()); + res_data.resize(src_data.size()); + + ColumnArray::Offset_t src_array_prev_offset = 0; + ColumnString::Offset_t res_string_prev_offset = 0; + + for (size_t i = 0; i < size; ++i) + { + if (src_array_offsets[i] != src_array_prev_offset) + { + size_t array_size = src_array_offsets[i] - src_array_prev_offset; + + for (size_t j = 0; j < array_size; ++j) + { + size_t j_reversed = array_size - j - 1; + + auto src_pos = src_array_prev_offset + j_reversed == 0 ? 0 : src_string_offsets[src_array_prev_offset + j_reversed - 1]; + size_t string_size = src_string_offsets[src_array_prev_offset + j_reversed] - src_pos; + + memcpySmallAllowReadWriteOverflow15(&res_data[res_string_prev_offset], &src_data[src_pos], string_size); + + res_string_prev_offset += string_size; + res_string_offsets[src_array_prev_offset + j] = res_string_prev_offset; + } + } + + src_array_prev_offset = src_array_offsets[i]; + } + + return true; + } + else + return false; +} + + + +String FunctionArrayReduce::getName() const +{ + return name; +} + + +void FunctionArrayReduce::getReturnTypeAndPrerequisites( + const ColumnsWithTypeAndName & arguments, + DataTypePtr & out_return_type, + std::vector & out_prerequisites) +{ + /// Первый аргумент - константная строка с именем агрегатной функции (возможно, с параметрами в скобках, например: "quantile(0.99)"). + + if (arguments.size() < 2) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be at least 2.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + const ColumnConstString * aggregate_function_name_column = typeid_cast(arguments[0].column.get()); + if (!aggregate_function_name_column) + throw Exception("First argument for function " + getName() + " must be constant string: name of aggregate function.", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + DataTypes argument_types(arguments.size() - 1); + for (size_t i = 1, size = arguments.size(); i < size; ++i) + { + const DataTypeArray * arg = typeid_cast(arguments[i].type.get()); + if (!arg) + throw Exception("Argument " + toString(i) + " for function " + getName() + " must be an array but it has type " + + arguments[i].type->getName() + ".", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + argument_types[i - 1] = arg->getNestedType()->clone(); + } + + if (!aggregate_function) + { + const String & aggregate_function_name_with_params = aggregate_function_name_column->getData(); + + if (aggregate_function_name_with_params.empty()) + throw Exception("First argument for function " + getName() + " (name of aggregate function) cannot be empty.", + ErrorCodes::BAD_ARGUMENTS); + + bool has_parameters = ')' == aggregate_function_name_with_params.back(); + + String aggregate_function_name = aggregate_function_name_with_params; + String parameters; + Array params_row; + + if (has_parameters) + { + size_t pos = aggregate_function_name_with_params.find('('); + if (pos == std::string::npos || pos + 2 >= aggregate_function_name_with_params.size()) + throw Exception("First argument for function " + getName() + " doesn't look like aggregate function name.", + ErrorCodes::BAD_ARGUMENTS); + + aggregate_function_name = aggregate_function_name_with_params.substr(0, pos); + parameters = aggregate_function_name_with_params.substr(pos + 1, aggregate_function_name_with_params.size() - pos - 2); + + if (aggregate_function_name.empty()) + throw Exception("First argument for function " + getName() + " doesn't look like aggregate function name.", + ErrorCodes::BAD_ARGUMENTS); + + ParserExpressionList params_parser(false); + ASTPtr args_ast = parseQuery(params_parser, + parameters.data(), parameters.data() + parameters.size(), + "parameters of aggregate function"); + + ASTExpressionList & args_list = typeid_cast(*args_ast); + + if (args_list.children.empty()) + throw Exception("Incorrect list of parameters to aggregate function " + + aggregate_function_name, ErrorCodes::BAD_ARGUMENTS); + + params_row.reserve(args_list.children.size()); + for (const auto & child : args_list.children) + { + const ASTLiteral * lit = typeid_cast(child.get()); + if (!lit) + throw Exception("Parameters to aggregate functions must be literals", + ErrorCodes::PARAMETERS_TO_AGGREGATE_FUNCTIONS_MUST_BE_LITERALS); + + params_row.push_back(lit->value); + } + } + + aggregate_function = AggregateFunctionFactory().get(aggregate_function_name, argument_types); + + /// Потому что владение состояниями агрегатных функций никуда не отдаётся. + if (aggregate_function->isState()) + throw Exception("Using aggregate function with -State modifier in function arrayReduce is not supported", ErrorCodes::BAD_ARGUMENTS); + + if (has_parameters) + aggregate_function->setParameters(params_row); + aggregate_function->setArguments(argument_types); + } + + out_return_type = aggregate_function->getReturnType(); +} + + +void FunctionArrayReduce::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + IAggregateFunction & agg_func = *aggregate_function.get(); + std::unique_ptr place_holder { new char[agg_func.sizeOfData()] }; + AggregateDataPtr place = place_holder.get(); + + size_t rows = block.rowsInFirstColumn(); + + /// Агрегатные функции не поддерживают константные столбцы. Поэтому, материализуем их. + std::vector materialized_columns; + + std::vector aggregate_arguments_vec(arguments.size() - 1); + + for (size_t i = 0, size = arguments.size() - 1; i < size; ++i) + { + const IColumn * col = block.unsafeGetByPosition(arguments[i + 1]).column.get(); + if (const ColumnArray * arr = typeid_cast(col)) + { + aggregate_arguments_vec[i] = arr->getDataPtr().get(); + } + else if (const ColumnConstArray * arr = typeid_cast(col)) + { + materialized_columns.emplace_back(arr->convertToFullColumn()); + aggregate_arguments_vec[i] = typeid_cast(*materialized_columns.back().get()).getDataPtr().get(); + } + else + throw Exception("Illegal column " + col->getName() + " as argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN); + + } + const IColumn ** aggregate_arguments = aggregate_arguments_vec.data(); + + const ColumnArray::Offsets_t & offsets = typeid_cast(!materialized_columns.empty() + ? *materialized_columns.front().get() + : *block.unsafeGetByPosition(arguments[1]).column.get()).getOffsets(); + + ColumnPtr result_holder = block.getByPosition(result).type->createColumn(); + block.getByPosition(result).column = result_holder; + IColumn & res_col = *result_holder.get(); + + ColumnArray::Offset_t current_offset = 0; + for (size_t i = 0; i < rows; ++i) + { + agg_func.create(place); + ColumnArray::Offset_t next_offset = offsets[i]; + + try + { + for (size_t j = current_offset; j < next_offset; ++j) + agg_func.add(place, aggregate_arguments, j, nullptr); + + agg_func.insertResultInto(place, res_col); + } + catch (...) + { + agg_func.destroy(place); + throw; + } + + agg_func.destroy(place); + current_offset = next_offset; + } +} + + void registerFunctionsArray(FunctionFactory & factory) { factory.registerFunction(); diff --git a/dbms/src/Functions/FunctionsString.cpp b/dbms/src/Functions/FunctionsString.cpp index a017c77abc7..7fdd8ab255c 100644 --- a/dbms/src/Functions/FunctionsString.cpp +++ b/dbms/src/Functions/FunctionsString.cpp @@ -1,9 +1,173 @@ +#include #include #include namespace DB { +String FunctionReverse::getName() const +{ + return name; +} + + +DataTypePtr FunctionReverse::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() != 1) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + if (!typeid_cast(&*arguments[0]) && !typeid_cast(&*arguments[0]) + && !typeid_cast(&*arguments[0])) + throw Exception("Illegal type " + arguments[0]->getName() + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return arguments[0]->clone(); +} + + +void FunctionReverse::execute(Block & block, const ColumnNumbers & arguments, size_t result) +{ + const ColumnPtr column = block.getByPosition(arguments[0]).column; + if (const ColumnString * col = typeid_cast(column.get())) + { + std::shared_ptr col_res = std::make_shared(); + block.getByPosition(result).column = col_res; + ReverseImpl::vector(col->getChars(), col->getOffsets(), + col_res->getChars(), col_res->getOffsets()); + } + else if (const ColumnFixedString * col = typeid_cast(column.get())) + { + auto col_res = std::make_shared(col->getN()); + block.getByPosition(result).column = col_res; + ReverseImpl::vector_fixed(col->getChars(), col->getN(), + col_res->getChars()); + } + else if (const ColumnConstString * col = typeid_cast(column.get())) + { + String res; + ReverseImpl::constant(col->getData(), res); + auto col_res = std::make_shared(col->size(), res); + block.getByPosition(result).column = col_res; + } + else if (typeid_cast(column.get()) || typeid_cast(column.get())) + { + FunctionArrayReverse().execute(block, arguments, result); + } + else + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); +} + + + +String FunctionAppendTrailingCharIfAbsent::getName() const +{ + return name; +} + + +DataTypePtr FunctionAppendTrailingCharIfAbsent::getReturnType(const DataTypes & arguments) const +{ + if (arguments.size() != 2) + throw Exception{ + "Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 2.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH + }; + + if (!typeid_cast(arguments[0].get())) + throw Exception{ + "Illegal type " + arguments[0]->getName() + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT + }; + + if (!typeid_cast(arguments[1].get())) + throw Exception{ + "Illegal type " + arguments[1]->getName() + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT + }; + + return std::make_shared(); +} + + +void FunctionAppendTrailingCharIfAbsent::execute(Block & block, const ColumnNumbers & arguments, const size_t result) +{ + const auto & column = block.getByPosition(arguments[0]).column; + const auto & column_char = block.getByPosition(arguments[1]).column; + + if (!typeid_cast(column_char.get())) + throw Exception{ + "Second argument of function " + getName() + " must be a constant string", + ErrorCodes::ILLEGAL_COLUMN + }; + + const auto & trailing_char_str = static_cast(*column_char).getData(); + + if (trailing_char_str.size() != 1) + throw Exception{ + "Second argument of function " + getName() + " must be a one-character string", + ErrorCodes::BAD_ARGUMENTS + }; + + if (const auto col = typeid_cast(&*column)) + { + auto col_res = std::make_shared(); + block.getByPosition(result).column = col_res; + + const auto & src_data = col->getChars(); + const auto & src_offsets = col->getOffsets(); + + auto & dst_data = col_res->getChars(); + auto & dst_offsets = col_res->getOffsets(); + + const auto size = src_offsets.size(); + dst_data.resize(src_data.size() + size); + dst_offsets.resize(size); + + ColumnString::Offset_t src_offset{}; + ColumnString::Offset_t dst_offset{}; + + for (const auto i : ext::range(0, size)) + { + const auto src_length = src_offsets[i] - src_offset; + memcpySmallAllowReadWriteOverflow15(&dst_data[dst_offset], &src_data[src_offset], src_length); + src_offset = src_offsets[i]; + dst_offset += src_length; + + if (src_length > 1 && dst_data[dst_offset - 2] != trailing_char_str.front()) + { + dst_data[dst_offset - 1] = trailing_char_str.front(); + dst_data[dst_offset] = 0; + ++dst_offset; + } + + dst_offsets[i] = dst_offset; + } + + dst_data.resize_assume_reserved(dst_offset); + } + else if (const auto col = typeid_cast(&*column)) + { + const auto & in_data = col->getData(); + + block.getByPosition(result).column = std::make_shared( + col->size(), + in_data.size() == 0 ? in_data : + in_data.back() == trailing_char_str.front() ? in_data : in_data + trailing_char_str); + } + else + throw Exception{ + "Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN + }; +} + + void registerFunctionsString(FunctionFactory & factory) { factory.registerFunction(); diff --git a/dbms/src/IO/CMakeLists.txt b/dbms/src/IO/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/IO/CMakeLists.txt +++ b/dbms/src/IO/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/IO/tests/CMakeLists.txt b/dbms/src/IO/tests/CMakeLists.txt index d444bdbfbff..cb5e21c3bd7 100644 --- a/dbms/src/IO/tests/CMakeLists.txt +++ b/dbms/src/IO/tests/CMakeLists.txt @@ -65,8 +65,8 @@ target_link_libraries (io_operators dbms) if (NOT APPLE) add_executable(write_buffer_aio write_buffer_aio.cpp) - target_link_libraries (write_buffer_aio dbms ${BOOST_FILESYSTEM_LIB}) + target_link_libraries (write_buffer_aio dbms ${Boost_FILESYSTEM_LIBRARY}) add_executable(read_buffer_aio read_buffer_aio.cpp) - target_link_libraries (read_buffer_aio dbms ${BOOST_FILESYSTEM_LIB}) -endif() + target_link_libraries (read_buffer_aio dbms ${Boost_FILESYSTEM_LIBRARY}) +endif () diff --git a/dbms/src/Interpreters/CMakeLists.txt b/dbms/src/Interpreters/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/Interpreters/CMakeLists.txt +++ b/dbms/src/Interpreters/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/Interpreters/Compiler.cpp b/dbms/src/Interpreters/Compiler.cpp index 3e2ebc58dde..ea8e9972023 100644 --- a/dbms/src/Interpreters/Compiler.cpp +++ b/dbms/src/Interpreters/Compiler.cpp @@ -189,6 +189,7 @@ void Compiler::compile( " -shared -fPIC -fvisibility=hidden -fno-implement-inlines" " -isystem /usr/share/clickhouse/headers/usr/local/include/" " -isystem /usr/share/clickhouse/headers/usr/include/" + " -isystem /usr/share/clickhouse/headers/usr/include/mysql/" " -isystem /usr/share/clickhouse/headers/usr/include/c++/*/" " -isystem /usr/share/clickhouse/headers/usr/include/x86_64-linux-gnu/" " -isystem /usr/share/clickhouse/headers/usr/include/x86_64-linux-gnu/c++/*/" diff --git a/dbms/src/Interpreters/tests/CMakeLists.txt b/dbms/src/Interpreters/tests/CMakeLists.txt index 2984f12fb03..ebc18ae1e17 100644 --- a/dbms/src/Interpreters/tests/CMakeLists.txt +++ b/dbms/src/Interpreters/tests/CMakeLists.txt @@ -51,4 +51,4 @@ target_link_libraries (in_join_subqueries_preprocessor dbms) add_check(in_join_subqueries_preprocessor) add_executable (users users.cpp) -target_link_libraries (users dbms ${BOOST_FILESYSTEM_LIB}) +target_link_libraries (users dbms ${Boost_FILESYSTEM_LIBRARY}) diff --git a/dbms/src/Parsers/CMakeLists.txt b/dbms/src/Parsers/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/Parsers/CMakeLists.txt +++ b/dbms/src/Parsers/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/Storages/CMakeLists.txt b/dbms/src/Storages/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/dbms/src/Storages/CMakeLists.txt +++ b/dbms/src/Storages/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/dbms/src/Storages/MergeTree/LevelMergeSelector.cpp b/dbms/src/Storages/MergeTree/LevelMergeSelector.cpp index 6054ead57ad..5a728af8e2f 100644 --- a/dbms/src/Storages/MergeTree/LevelMergeSelector.cpp +++ b/dbms/src/Storages/MergeTree/LevelMergeSelector.cpp @@ -42,30 +42,11 @@ struct Estimator void selectWithinPartition( const LevelMergeSelector::PartsInPartition & parts, const size_t max_total_size_to_merge, - const time_t current_min_part_age, Estimator & estimator, const LevelMergeSelector::Settings & settings) { size_t parts_size = parts.size(); - if (parts_size <= 1) - return; - - /// Will lower 'min_parts_to_merge' if all parts are old enough. - /// NOTE It is called base, because it is a base of logarithm, that determines merge tree depth. - double actual_base = settings.min_parts_to_merge; - - if (current_min_part_age > settings.lower_base_after) - { - actual_base -= log2(current_min_part_age - settings.lower_base_after); - if (actual_base < 2) - actual_base = 2; - } - - if (parts.size() > settings.fallback_after_num_parts) - actual_base = 2; - - /// Not enough parts to merge. - if (parts.size() < actual_base) + if (parts_size < settings.parts_to_merge) return; /// To easily calculate sum size in any range. @@ -79,79 +60,43 @@ void selectWithinPartition( prefix_sums[i + 1] = prefix_sum; } - size_t max_level = 0; - size_t prev_level = -1; - bool has_range_of_same_level = false; + /// Use "corrected" level. It will be non-decreasing while traversing parts right to left. + /// This is done for compatibility with another algorithms. + size_t corrected_level_at_left = 0; + size_t corrected_level_at_right = 0; - for (const auto & part : parts) + size_t range_end = parts_size; + size_t range_begin = range_end - settings.parts_to_merge; + + for (size_t i = range_begin; i < range_end; ++i) + if (corrected_level_at_left < parts[i].level) + corrected_level_at_left = parts[i].level; + + while (true) { - if (part.level > max_level) - max_level = part.level; + if (corrected_level_at_left < parts[range_begin].level) + corrected_level_at_left = parts[range_begin].level; - if (part.level == prev_level) - has_range_of_same_level = true; + if (corrected_level_at_right < parts[range_end - 1].level) + corrected_level_at_right = parts[range_end - 1].level; - prev_level = part.level; - } - - /// If no ranges of same level - then nothing to merge - /// except case when parts are old (much time has passed) and 'base' was lowered to minimum. - if (!has_range_of_same_level && actual_base > 2) - return; - - /// For each level, try to select range of parts with that level. - for (size_t level = 0; level <= max_level; ++level) - { - bool in_range = false; - size_t range_begin = 0; - size_t range_end = 0; - - for (size_t i = 0; i <= parts_size; ++i) + /// Leftmost range of same corrected level. + if (corrected_level_at_left == corrected_level_at_right + && (range_begin == 0 || parts[range_begin - 1].level > corrected_level_at_left)) { - /// But if !has_range_of_same_level - it is allowed to select parts with any different levels. - if (i < parts_size && (parts[i].level == level || !has_range_of_same_level)) - { - if (!in_range) - { - in_range = true; - range_begin = i; - } - } - else - { - if (in_range) - { - in_range = false; - range_end = i; + size_t range_size = prefix_sums[range_end] - prefix_sums[range_begin]; - size_t range_length = range_end - range_begin; + if (range_size <= max_total_size_to_merge) + estimator.consider(parts.begin() + range_begin, parts.begin() + range_end, range_size); - /// Length of range is enough. - if (range_length >= actual_base) - { - /// If length of range is larger than 'max_parts_to_merge' - split it to subranges of almost equal lengths. - /// For example, if 'max_parts_to_merge' == 100 and 'range_length' = 101, split it to subranges of lengths 50 and 51. - size_t num_subranges = (range_length + settings.max_parts_to_merge - 1) / settings.max_parts_to_merge; - - for (size_t subrange_index = 0; subrange_index < num_subranges; ++subrange_index) - { - size_t subrange_begin = range_begin + subrange_index * range_length / num_subranges; - size_t subrange_end = range_begin + (subrange_index + 1) * range_length / num_subranges; - - size_t size_of_subrange = prefix_sums[subrange_end] - prefix_sums[subrange_begin]; - - /// Don't consider this range if its size is too large. - if (!max_total_size_to_merge || size_of_subrange <= max_total_size_to_merge) - estimator.consider(parts.begin() + subrange_begin, parts.begin() + subrange_end, size_of_subrange); - } - } - } - } + break; /// Minumum level is enough. } - /// If we don't care of levels, first iteration was enough. - if (!has_range_of_same_level) + if (range_begin == 0) break; + + --range_begin; + --range_end; } } @@ -162,16 +107,10 @@ LevelMergeSelector::PartsInPartition LevelMergeSelector::select( const Partitions & partitions, const size_t max_total_size_to_merge) { - time_t min_age = -1; - for (const auto & partition : partitions) - for (const auto & part : partition) - if (min_age == -1 || part.age < min_age) - min_age = part.age; - Estimator estimator; for (const auto & partition : partitions) - selectWithinPartition(partition, max_total_size_to_merge, min_age, estimator, settings); + selectWithinPartition(partition, max_total_size_to_merge, estimator, settings); return estimator.getBest(); } diff --git a/dbms/src/Storages/MergeTree/MergeTreeBlockInputStream.cpp b/dbms/src/Storages/MergeTree/MergeTreeBlockInputStream.cpp index aa9345041d8..1f4f6c4e6af 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeBlockInputStream.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeBlockInputStream.cpp @@ -21,7 +21,7 @@ MergeTreeBlockInputStream::MergeTreeBlockInputStream(const String & path_, /// const MarkRanges & mark_ranges_, bool use_uncompressed_cache_, ExpressionActionsPtr prewhere_actions_, String prewhere_column_, bool check_columns, size_t min_bytes_to_use_direct_io_, size_t max_read_buffer_size_, - bool save_marks_in_cache_) + bool save_marks_in_cache_, bool quiet) : path(path_), block_size(block_size_), storage(storage_), owned_data_part(owned_data_part_), @@ -97,6 +97,7 @@ MergeTreeBlockInputStream::MergeTreeBlockInputStream(const String & path_, /// total_rows += range.end - range.begin; total_rows *= storage.index_granularity; + if (!quiet) LOG_TRACE(log, "Reading " << all_mark_ranges.size() << " ranges from part " << owned_data_part->name << ", approx. " << total_rows << (all_mark_ranges.size() > 1 diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp index fb047b6c7b5..7b0b5710e1e 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -16,11 +16,13 @@ #include #include #include +#include #include #include #include #include +#include namespace ProfileEvents @@ -42,6 +44,10 @@ namespace ErrorCodes extern const int ABORTED; } + +using MergeAlgorithm = MergeTreeDataMerger::MergeAlgorithm; + + namespace { @@ -95,10 +101,16 @@ size_t MergeTreeDataMerger::getMaxPartsSizeForMerge(size_t pool_size, size_t poo if (pool_used > pool_size) throw Exception("Logical error: invalid arguments passed to getMaxPartsSizeForMerge: pool_used > pool_size", ErrorCodes::LOGICAL_ERROR); - size_t max_size = interpolateExponential( - data.settings.max_bytes_to_merge_at_min_space_in_pool, - data.settings.max_bytes_to_merge_at_max_space_in_pool, - static_cast(pool_size - pool_used) / pool_size); + size_t free_entries = pool_size - pool_used; + + size_t max_size = 0; + if (free_entries >= data.settings.number_of_free_entries_in_pool_to_lower_max_size_of_merge) + max_size = data.settings.max_bytes_to_merge_at_max_space_in_pool; + else + max_size = interpolateExponential( + data.settings.max_bytes_to_merge_at_min_space_in_pool, + data.settings.max_bytes_to_merge_at_max_space_in_pool, + static_cast(free_entries) / data.settings.number_of_free_entries_in_pool_to_lower_max_size_of_merge); return std::min(max_size, static_cast(DiskSpaceMonitor::getUnreservedFreeSpace(data.full_path) / DISK_USAGE_COEFFICIENT_TO_SELECT)); } @@ -283,7 +295,159 @@ MergeTreeData::DataPartsVector MergeTreeDataMerger::selectAllPartsFromPartition( } -/// parts должны быть отсортированы. +/// PK columns are sorted and merged, ordinary columns are gathered using info from merge step +static void extractMergingAndGatheringColumns(const NamesAndTypesList & all_columns, ExpressionActionsPtr primary_key_expressions, + const MergeTreeData::MergingParams & merging_params, + NamesAndTypesList & gathering_columns, Names & gathering_column_names, + NamesAndTypesList & merging_columns, Names & merging_column_names +) +{ + Names key_columns_dup = primary_key_expressions->getRequiredColumns(); + std::set key_columns(key_columns_dup.cbegin(), key_columns_dup.cend()); + + /// Force sign column for Collapsing mode + if (merging_params.mode == MergeTreeData::MergingParams::Collapsing) + key_columns.emplace(merging_params.sign_column); + + /// TODO: also force "summing" and "aggregating" columns to make Horizontal merge only for such columns + + for (auto & column : all_columns) + { + auto it = std::find(key_columns.cbegin(), key_columns.cend(), column.name); + + if (key_columns.end() == it) + { + gathering_columns.emplace_back(column); + gathering_column_names.emplace_back(column.name); + } + else + { + merging_columns.emplace_back(column); + merging_column_names.emplace_back(column.name); + } + } +} + +/* Allow to compute more accurate progress statistics */ +class ColumnSizeEstimator +{ + MergeTreeData::DataPart::ColumnToSize map; +public: + + /// Stores approximate size of columns in bytes + /// Exact values are not required since it used for relative values estimation (progress). + size_t sum_total = 0; + size_t sum_index_columns = 0; + size_t sum_ordinary_columns = 0; + + ColumnSizeEstimator(const MergeTreeData::DataPart::ColumnToSize & map_, const Names & key_columns, const Names & ordinary_columns) + : map(map_) + { + for (const auto & name : key_columns) + if (!map.count(name)) map[name] = 0; + for (const auto & name : ordinary_columns) + if (!map.count(name)) map[name] = 0; + + for (const auto & name : key_columns) + sum_index_columns += map.at(name); + + for (const auto & name : ordinary_columns) + sum_ordinary_columns += map.at(name); + + sum_total = std::max(1UL, sum_index_columns + sum_ordinary_columns); + } + + /// Approximate size of num_rows column elements if column contains num_total_rows elements + Float64 columnSize(const String & column, size_t num_rows, size_t num_total_rows) const + { + return static_cast(map.at(column)) / num_total_rows * num_rows; + } + + /// Relative size of num_rows column elements (in comparison with overall size of all columns) if column contains num_total_rows elements + Float64 columnProgress(const String & column, size_t num_rows, size_t num_total_rows) const + { + return columnSize(column, num_rows, num_total_rows) / sum_total; + } + + /// Like columnSize, but takes into account only PK columns + Float64 keyColumnsSize(size_t num_rows, size_t num_total_rows) const + { + return static_cast(sum_index_columns) / num_total_rows * num_rows; + } + + /// Like columnProgress, but takes into account only PK columns + Float64 keyColumnsProgress(size_t num_rows, size_t num_total_rows) const + { + return keyColumnsSize(num_rows, num_total_rows) / sum_total; + } +}; + + +class MergeProgressCallback : public ProgressCallback +{ +public: + MergeProgressCallback(MergeList::Entry & merge_entry_) : merge_entry(merge_entry_) {} + + MergeProgressCallback(MergeList::Entry & merge_entry_, MergeTreeDataMerger::MergeAlgorithm merge_alg_, size_t num_total_rows, + const ColumnSizeEstimator & column_sizes) + : merge_entry(merge_entry_), merge_alg(merge_alg_) + { + if (merge_alg == MergeAlgorithm::Horizontal) + average_elem_progress = 1.0 / num_total_rows; + else + average_elem_progress = column_sizes.keyColumnsProgress(1, num_total_rows); + } + + MergeList::Entry & merge_entry; + const MergeAlgorithm merge_alg{MergeAlgorithm::Vertical}; + Float64 average_elem_progress; + + void operator() (const Progress & value) + { + ProfileEvents::increment(ProfileEvents::MergedUncompressedBytes, value.bytes); + merge_entry->bytes_read_uncompressed += value.bytes; + merge_entry->rows_with_key_columns_read += value.rows; + + if (merge_alg == MergeAlgorithm::Horizontal) + { + ProfileEvents::increment(ProfileEvents::MergedRows, value.rows); + merge_entry->rows_read += value.rows; + merge_entry->progress = average_elem_progress * merge_entry->rows_read; + } + else + { + merge_entry->progress = average_elem_progress * merge_entry->rows_with_key_columns_read; + } + }; +}; + +class MergeProgressCallbackVerticalStep : public MergeProgressCallback +{ +public: + + MergeProgressCallbackVerticalStep(MergeList::Entry & merge_entry_, size_t num_total_rows_exact, + const ColumnSizeEstimator & column_sizes, const String & column_name) + : MergeProgressCallback(merge_entry_), initial_progress(merge_entry->progress) + { + average_elem_progress = column_sizes.columnProgress(column_name, 1, num_total_rows_exact); + } + + Float64 initial_progress; + /// NOTE: not thread safe (to be copyable). It is OK in current single thread use case + size_t rows_read_internal{0}; + + void operator() (const Progress & value) + { + merge_entry->bytes_read_uncompressed += value.bytes; + ProfileEvents::increment(ProfileEvents::MergedUncompressedBytes, value.bytes); + + rows_read_internal += value.rows; + Float64 local_progress = average_elem_progress * rows_read_internal; + merge_entry->progress = initial_progress + local_progress; + }; +}; + +/// parts should be sorted. MergeTreeData::MutableDataPartPtr MergeTreeDataMerger::mergePartsToTemporaryPart( MergeTreeData::DataPartsVector & parts, const String & merged_name, MergeList::Entry & merge_entry, size_t aio_threshold, time_t time_of_merge, DiskSpaceMonitor::Reservation * disk_reservation) @@ -308,55 +472,62 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMerger::mergePartsToTemporaryPart } MergeTreeData::DataPart::ColumnToSize merged_column_to_size; - if (aio_threshold > 0) - { - for (const MergeTreeData::DataPartPtr & part : parts) - part->accumulateColumnSizes(merged_column_to_size); - } + for (const MergeTreeData::DataPartPtr & part : parts) + part->accumulateColumnSizes(merged_column_to_size); - Names column_names = data.getColumnNamesList(); - NamesAndTypesList column_names_and_types = data.getColumnsList(); + Names all_column_names = data.getColumnNamesList(); + NamesAndTypesList all_columns = data.getColumnsList(); + SortDescription sort_desc = data.getSortDescription(); + + NamesAndTypesList gathering_columns, merging_columns; + Names gathering_column_names, merging_column_names; + extractMergingAndGatheringColumns(all_columns, data.getPrimaryExpression(), data.merging_params, + gathering_columns, gathering_column_names, merging_columns, merging_column_names); MergeTreeData::MutableDataPartPtr new_data_part = std::make_shared(data); ActiveDataPartSet::parsePartName(merged_name, *new_data_part); new_data_part->name = "tmp_" + merged_name; new_data_part->is_temp = true; + size_t sum_input_rows_upper_bound = merge_entry->total_size_marks * data.index_granularity; + + MergedRowSources merged_rows_sources; + MergedRowSources * merged_rows_sources_ptr = &merged_rows_sources; + MergeAlgorithm merge_alg = chooseMergeAlgorithm(data, parts, sum_input_rows_upper_bound, merged_rows_sources); + + LOG_DEBUG(log, "Selected MergeAlgorithm: " << ((merge_alg == MergeAlgorithm::Vertical) ? "Vertical" : "Horizontal")); + + if (merge_alg != MergeAlgorithm::Vertical) + { + merged_rows_sources_ptr = nullptr; + merging_columns = all_columns; + merging_column_names = all_column_names; + gathering_columns.clear(); + gathering_column_names.clear(); + } + + ColumnSizeEstimator column_sizes(merged_column_to_size, merging_column_names, gathering_column_names); + /** Читаем из всех кусков, сливаем и пишем в новый. * Попутно вычисляем выражение для сортировки. */ BlockInputStreams src_streams; - size_t sum_rows_approx = 0; - - const auto rows_total = merge_entry->total_size_marks * data.index_granularity; - for (size_t i = 0; i < parts.size(); ++i) { - MarkRanges ranges{{0, parts[i]->size}}; - String part_path = data.getFullPath() + parts[i]->name + '/'; + auto input = std::make_unique( - part_path, DEFAULT_MERGE_BLOCK_SIZE, column_names, data, - parts[i], ranges, false, nullptr, "", true, aio_threshold, DBMS_DEFAULT_BUFFER_SIZE, false); + part_path, DEFAULT_MERGE_BLOCK_SIZE, merging_column_names, data, parts[i], + MarkRanges(1, MarkRange(0, parts[i]->size)), false, nullptr, "", true, aio_threshold, DBMS_DEFAULT_BUFFER_SIZE, false); - input->setProgressCallback([&merge_entry, rows_total] (const Progress & value) - { - const auto new_rows_read = merge_entry->rows_read += value.rows; - merge_entry->progress = static_cast(new_rows_read) / rows_total; - merge_entry->bytes_read_uncompressed += value.bytes; - - ProfileEvents::increment(ProfileEvents::MergedRows, value.rows); - ProfileEvents::increment(ProfileEvents::MergedUncompressedBytes, value.bytes); - }); + input->setProgressCallback(MergeProgressCallback{merge_entry, merge_alg, sum_input_rows_upper_bound, column_sizes}); if (data.merging_params.mode != MergeTreeData::MergingParams::Unsorted) src_streams.emplace_back(std::make_shared( std::make_shared(BlockInputStreamPtr(std::move(input)), data.getPrimaryExpression()))); else src_streams.emplace_back(std::move(input)); - - sum_rows_approx += parts[i]->size * data.index_granularity; } /// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника. @@ -368,32 +539,32 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMerger::mergePartsToTemporaryPart { case MergeTreeData::MergingParams::Ordinary: merged_stream = std::make_unique( - src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE); + src_streams, sort_desc, DEFAULT_MERGE_BLOCK_SIZE, 0, merged_rows_sources_ptr); break; case MergeTreeData::MergingParams::Collapsing: merged_stream = std::make_unique( - src_streams, data.getSortDescription(), data.merging_params.sign_column, DEFAULT_MERGE_BLOCK_SIZE); + src_streams, sort_desc, data.merging_params.sign_column, DEFAULT_MERGE_BLOCK_SIZE, merged_rows_sources_ptr); break; case MergeTreeData::MergingParams::Summing: merged_stream = std::make_unique( - src_streams, data.getSortDescription(), data.merging_params.columns_to_sum, DEFAULT_MERGE_BLOCK_SIZE); + src_streams, sort_desc, data.merging_params.columns_to_sum, DEFAULT_MERGE_BLOCK_SIZE); break; case MergeTreeData::MergingParams::Aggregating: merged_stream = std::make_unique( - src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE); + src_streams, sort_desc, DEFAULT_MERGE_BLOCK_SIZE); break; case MergeTreeData::MergingParams::Replacing: merged_stream = std::make_unique( - src_streams, data.getSortDescription(), data.merging_params.version_column, DEFAULT_MERGE_BLOCK_SIZE); + src_streams, sort_desc, data.merging_params.version_column, DEFAULT_MERGE_BLOCK_SIZE); break; case MergeTreeData::MergingParams::Graphite: merged_stream = std::make_unique( - src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE, + src_streams, sort_desc, DEFAULT_MERGE_BLOCK_SIZE, data.merging_params.graphite_params, time_of_merge); break; @@ -412,7 +583,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMerger::mergePartsToTemporaryPart static_cast(merge_entry->total_size_bytes_compressed) / data.getTotalActiveSizeInBytes()); MergedBlockOutputStream to{ - data, new_part_tmp_path, column_names_and_types, compression_method, merged_column_to_size, aio_threshold}; + data, new_part_tmp_path, merging_columns, compression_method, merged_column_to_size, aio_threshold}; merged_stream->readPrefix(); to.writePrefix(); @@ -426,19 +597,94 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMerger::mergePartsToTemporaryPart rows_written += block.rows(); to.write(block); - merge_entry->rows_written = merged_stream->getProfileInfo().rows; + if (merge_alg == MergeAlgorithm::Horizontal) + merge_entry->rows_written = merged_stream->getProfileInfo().rows; + merge_entry->rows_with_key_columns_written = merged_stream->getProfileInfo().rows; merge_entry->bytes_written_uncompressed = merged_stream->getProfileInfo().bytes; - if (disk_reservation) - disk_reservation->update(static_cast((1 - std::min(1., 1. * rows_written / sum_rows_approx)) * initial_reservation)); + /// This update is unactual for VERTICAL algorithm sicne it requires more accurate per-column updates + /// Reservation updates is not performed yet, during the merge it may lead to higher free space requirements + if (disk_reservation && merge_alg == MergeAlgorithm::Horizontal) + { + Float64 relative_rows_written = std::min(1., 1. * rows_written / sum_input_rows_upper_bound); + disk_reservation->update(static_cast((1. - relative_rows_written) * initial_reservation)); + } } if (isCancelled()) throw Exception("Cancelled merging parts", ErrorCodes::ABORTED); + MergeTreeData::DataPart::Checksums checksums_ordinary_columns; + + /// Gather ordinary columns + if (merge_alg == MergeAlgorithm::Vertical) + { + size_t sum_input_rows_exact = merge_entry->rows_with_key_columns_read; + merge_entry->columns_written = merging_column_names.size(); + merge_entry->progress = column_sizes.keyColumnsProgress(sum_input_rows_exact, sum_input_rows_exact); + + BlockInputStreams column_part_streams(parts.size()); + NameSet offset_columns_written; + + auto it_name_and_type = gathering_columns.cbegin(); + + for (size_t column_num = 0; column_num < gathering_column_names.size(); ++column_num, it_name_and_type++) + { + const String & column_name = it_name_and_type->name; + const DataTypePtr & column_type = it_name_and_type->type; + const String offset_column_name = DataTypeNested::extractNestedTableName(column_name); + Names column_name_(1, column_name); + NamesAndTypesList column_name_and_type_(1, *it_name_and_type); + Float64 progress_before = merge_entry->progress; + bool offset_written = offset_columns_written.count(offset_column_name); + + LOG_TRACE(log, "Gathering column " << column_name << " " << column_type->getName()); + + for (size_t part_num = 0; part_num < parts.size(); ++part_num) + { + String part_path = data.getFullPath() + parts[part_num]->name + '/'; + + /// TODO: test perfomance with more accurate settings + auto column_part_stream = std::make_shared( + part_path, DEFAULT_MERGE_BLOCK_SIZE, column_name_, data, parts[part_num], + MarkRanges(1, MarkRange(0, parts[part_num]->size)), false, nullptr, "", true, aio_threshold, DBMS_DEFAULT_BUFFER_SIZE, + false, true); + + column_part_stream->setProgressCallback( + MergeProgressCallbackVerticalStep{merge_entry, sum_input_rows_exact, column_sizes, column_name}); + + column_part_streams[part_num] = std::move(column_part_stream); + } + + ColumnGathererStream column_gathered_stream(column_part_streams, column_name, merged_rows_sources, DEFAULT_BLOCK_SIZE); + MergedColumnOnlyOutputStream column_to(data, new_part_tmp_path, true, compression_method, offset_written); + + column_to.writePrefix(); + while ((block = column_gathered_stream.read())) + { + column_to.write(block); + } + /// NOTE: nested column contains duplicates checksums (and files) + checksums_ordinary_columns.add(column_to.writeSuffixAndGetChecksums()); + + if (typeid_cast(column_type.get())) + offset_columns_written.emplace(offset_column_name); + + merge_entry->columns_written = merging_column_names.size() + column_num; + merge_entry->bytes_written_uncompressed += column_gathered_stream.getProfileInfo().bytes; + merge_entry->progress = progress_before + column_sizes.columnProgress(column_name, sum_input_rows_exact, sum_input_rows_exact); + + if (isCancelled()) + throw Exception("Cancelled merging parts", ErrorCodes::ABORTED); + } + } + merged_stream->readSuffix(); - new_data_part->columns = column_names_and_types; - new_data_part->checksums = to.writeSuffixAndGetChecksums(); + new_data_part->columns = all_columns; + if (merge_alg != MergeAlgorithm::Vertical) + new_data_part->checksums = to.writeSuffixAndGetChecksums(); + else + new_data_part->checksums = to.writeSuffixAndGetChecksums(all_columns, &checksums_ordinary_columns); new_data_part->index.swap(to.getIndex()); /// Для удобства, даже CollapsingSortedBlockInputStream не может выдать ноль строк. @@ -454,6 +700,43 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMerger::mergePartsToTemporaryPart } +MergeTreeDataMerger::MergeAlgorithm MergeTreeDataMerger::chooseMergeAlgorithm( + const MergeTreeData & data, const MergeTreeData::DataPartsVector & parts, + size_t sum_rows_upper_bound, MergedRowSources & rows_sources_to_alloc) const +{ + if (data.context.getMergeTreeSettings().enable_vertical_merge_algorithm == 0) + return MergeAlgorithm::Horizontal; + + bool is_supported_storage = + data.merging_params.mode == MergeTreeData::MergingParams::Ordinary || + data.merging_params.mode == MergeTreeData::MergingParams::Collapsing; + + bool enough_ordinary_cols = data.getColumnNamesList().size() > data.getSortDescription().size(); + + bool enough_total_rows = sum_rows_upper_bound >= DEFAULT_MERGE_BLOCK_SIZE; + + bool no_parts_overflow = parts.size() <= RowSourcePart::MAX_PARTS; + + auto merge_alg = (is_supported_storage && enough_total_rows && enough_ordinary_cols && no_parts_overflow) ? + MergeAlgorithm::Vertical : MergeAlgorithm::Horizontal; + + if (merge_alg == MergeAlgorithm::Vertical) + { + try + { + rows_sources_to_alloc.reserve(sum_rows_upper_bound); + } + catch (...) + { + /// Not enough memory for VERTICAL merge algorithm, make sense for very large tables + merge_alg = MergeAlgorithm::Horizontal; + } + } + + return merge_alg; +} + + MergeTreeData::DataPartPtr MergeTreeDataMerger::renameMergedTemporaryPart( MergeTreeData::DataPartsVector & parts, MergeTreeData::MutableDataPartPtr & new_data_part, diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp index 11c4b09a08f..9341f6e6801 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataPart.cpp @@ -222,6 +222,14 @@ void MergeTreeDataPartChecksums::addFile(const String & file_name, size_t file_s files[file_name] = Checksum(file_size, file_hash); } +void MergeTreeDataPartChecksums::add(MergeTreeDataPartChecksums && rhs_checksums) +{ + for (auto & checksum : rhs_checksums.files) + files[std::move(checksum.first)] = std::move(checksum.second); + + rhs_checksums.files.clear(); +} + /// Контрольная сумма от множества контрольных сумм .bin файлов. void MergeTreeDataPartChecksums::summaryDataChecksum(SipHash & hash) const { diff --git a/dbms/src/Storages/MergeTree/MergeTreeReader.cpp b/dbms/src/Storages/MergeTree/MergeTreeReader.cpp index 6274c2370c7..9639529f7a4 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeReader.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeReader.cpp @@ -215,6 +215,13 @@ MergeTreeReader::Stream::Stream( } } +std::unique_ptr MergeTreeReader::Stream::createEmptyPtr() +{ + std::unique_ptr res(new Stream); + res->is_empty = true; + return res; +} + void MergeTreeReader::Stream::loadMarks(MarkCache * cache, bool save_in_cache) { @@ -285,26 +292,39 @@ void MergeTreeReader::addStream(const String & name, const IDataType & type, con { String escaped_column_name = escapeForFileName(name); - /** Если файла с данными нет - то не будем пытаться открыть его. - * Это нужно, чтобы можно было добавлять новые столбцы к структуре таблицы без создания файлов для старых кусков. - */ - if (!Poco::File(path + escaped_column_name + ".bin").exists()) + const DataTypeArray * type_arr = typeid_cast(&type); + bool data_file_exists = Poco::File(path + escaped_column_name + ".bin").exists(); + bool is_column_of_nested_type = type_arr && level == 0 && DataTypeNested::extractNestedTableName(name) != name; + + /** If data file is missing then we will not try to open it. + * It is necessary since it allows to add new column to structure of the table without creating new files for old parts. + * But we should try to load offset data for array columns of Nested subtable (their data will be filled by default value). + */ + if (!data_file_exists && !is_column_of_nested_type) return; /// Для массивов используются отдельные потоки для размеров. - if (const DataTypeArray * type_arr = typeid_cast(&type)) + if (type_arr) { String size_name = DataTypeNested::extractNestedTableName(name) + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); String escaped_size_name = escapeForFileName(DataTypeNested::extractNestedTableName(name)) + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); + String size_path = path + escaped_size_name + ".bin"; + + /// We don't have neither offsets neither data -> skipping, default values will be filled after + if (!data_file_exists && !Poco::File(size_path).exists()) + return; if (!streams.count(size_name)) streams.emplace(size_name, std::make_unique( path + escaped_size_name, uncompressed_cache, mark_cache, save_marks_in_cache, all_mark_ranges, aio_threshold, max_read_buffer_size, profile_callback, clock_type)); - addStream(name, *type_arr->getNestedType(), all_mark_ranges, profile_callback, clock_type, level + 1); + if (data_file_exists) + addStream(name, *type_arr->getNestedType(), all_mark_ranges, profile_callback, clock_type, level + 1); + else + streams.emplace(name, Stream::createEmptyPtr()); } else streams.emplace(name, std::make_unique( @@ -344,10 +364,11 @@ void MergeTreeReader::readData(const String & name, const IDataType & type, ICol required_internal_size - array.getData().size(), level + 1); + size_t read_internal_size = array.getData().size(); + /** Исправление для ошибочно записанных пустых файлов с данными массива. * Такое бывает после ALTER с добавлением новых столбцов во вложенную структуру данных. */ - size_t read_internal_size = array.getData().size(); if (required_internal_size != read_internal_size) { if (read_internal_size != 0) @@ -369,6 +390,11 @@ void MergeTreeReader::readData(const String & name, const IDataType & type, ICol else { Stream & stream = *streams[name]; + + /// It means that data column of array column will be empty, and it will be replaced by const data column + if (stream.is_empty) + return; + double & avg_value_size_hint = avg_value_size_hints[name]; stream.seekToMark(from_mark); type.deserializeBinary(column, *stream.data_buffer, max_rows_to_read, avg_value_size_hint); diff --git a/dbms/src/Storages/MergeTree/SimpleMergeSelector.cpp b/dbms/src/Storages/MergeTree/SimpleMergeSelector.cpp index a790aa6fa30..0777b4edab2 100644 --- a/dbms/src/Storages/MergeTree/SimpleMergeSelector.cpp +++ b/dbms/src/Storages/MergeTree/SimpleMergeSelector.cpp @@ -59,8 +59,12 @@ struct Estimator * It must be lower, and thus we decide, what range is better to merge. * * The integral is lower iff the following formula is lower: + * + * sum_size / (count - 1) + * + * But we have some tunes to prefer longer ranges. */ - return (sum_size + sum_size_fixed_cost * count) / (count - 1); + return (sum_size + sum_size_fixed_cost * count) / (count - 1.9); } double min_score = 0; @@ -124,7 +128,7 @@ bool allow( // std::cerr << "combined_ratio: " << combined_ratio << "\n"; - double lowered_base = interpolateLinear(settings.base, 1.0, combined_ratio); + double lowered_base = interpolateLinear(settings.base, 2.0, combined_ratio); // std::cerr << "------- lowered_base: " << lowered_base << "\n"; diff --git a/dbms/src/Storages/StorageLog.cpp b/dbms/src/Storages/StorageLog.cpp index 293fe0f4b22..6cfe8c59bef 100644 --- a/dbms/src/Storages/StorageLog.cpp +++ b/dbms/src/Storages/StorageLog.cpp @@ -691,7 +691,7 @@ BlockInputStreams StorageLog::read( max_block_size, column_names, *this, - 0, std::numeric_limits::max(), + 0, marksCount() ? std::numeric_limits::max() : 0, settings.max_read_buffer_size)); } else diff --git a/dbms/src/Storages/System/StorageSystemMerges.cpp b/dbms/src/Storages/System/StorageSystemMerges.cpp index d78111e05ff..4bdce11acc0 100644 --- a/dbms/src/Storages/System/StorageSystemMerges.cpp +++ b/dbms/src/Storages/System/StorageSystemMerges.cpp @@ -24,7 +24,10 @@ StorageSystemMerges::StorageSystemMerges(const std::string & name) { "bytes_read_uncompressed", std::make_shared() }, { "rows_read", std::make_shared() }, { "bytes_written_uncompressed", std::make_shared() }, - { "rows_written", std::make_shared() } + { "rows_written", std::make_shared() }, + { "columns_written", std::make_shared() }, + { "rows_with_key_columns_read", std::make_shared() }, + { "rows_with_key_columns_written", std::make_shared() } } { } @@ -58,13 +61,16 @@ BlockInputStreams StorageSystemMerges::read( ColumnWithTypeAndName col_rows_read{std::make_shared(), std::make_shared(), "rows_read"}; ColumnWithTypeAndName col_bytes_written_uncompressed{std::make_shared(), std::make_shared(), "bytes_written_uncompressed"}; ColumnWithTypeAndName col_rows_written{std::make_shared(), std::make_shared(), "rows_written"}; + ColumnWithTypeAndName col_columns_written{std::make_shared(), std::make_shared(), "columns_written"}; + ColumnWithTypeAndName col_rows_with_key_columns_read{std::make_shared(), std::make_shared(), "rows_with_key_columns_read"}; + ColumnWithTypeAndName col_rows_with_key_columns_written{std::make_shared(), std::make_shared(), "rows_with_key_columns_written"}; for (const auto & merge : context.getMergeList().get()) { col_database.column->insert(merge.database); col_table.column->insert(merge.table); col_elapsed.column->insert(merge.watch.elapsedSeconds()); - col_progress.column->insert(merge.progress); + col_progress.column->insert(std::min(1., merge.progress)); /// little cheat col_num_parts.column->insert(merge.num_parts); col_result_part_name.column->insert(merge.result_part_name); col_total_size_bytes_compressed.column->insert(merge.total_size_bytes_compressed); @@ -73,6 +79,9 @@ BlockInputStreams StorageSystemMerges::read( col_rows_read.column->insert(merge.rows_read.load(std::memory_order_relaxed)); col_bytes_written_uncompressed.column->insert(merge.bytes_written_uncompressed.load(std::memory_order_relaxed)); col_rows_written.column->insert(merge.rows_written.load(std::memory_order_relaxed)); + col_columns_written.column->insert(merge.columns_written.load(std::memory_order_relaxed)); + col_rows_with_key_columns_read.column->insert(merge.rows_with_key_columns_read.load(std::memory_order_relaxed)); + col_rows_with_key_columns_written.column->insert(merge.rows_with_key_columns_written.load(std::memory_order_relaxed)); } Block block{ @@ -87,7 +96,10 @@ BlockInputStreams StorageSystemMerges::read( col_bytes_read_uncompressed, col_rows_read, col_bytes_written_uncompressed, - col_rows_written + col_rows_written, + col_columns_written, + col_rows_with_key_columns_read, + col_rows_with_key_columns_written }; return BlockInputStreams{1, std::make_shared(block)}; diff --git a/dbms/src/Storages/tests/CMakeLists.txt b/dbms/src/Storages/tests/CMakeLists.txt index 76e211eb225..f4e9b5e8f46 100644 --- a/dbms/src/Storages/tests/CMakeLists.txt +++ b/dbms/src/Storages/tests/CMakeLists.txt @@ -33,3 +33,5 @@ target_link_libraries (merge_selector dbms) add_executable (merge_selector2 merge_selector2.cpp) target_link_libraries (merge_selector2 dbms) + +add_executable (row_source_bitwise_compatibility row_source_bitwise_test.cpp) diff --git a/dbms/src/Storages/tests/merge_selector2.cpp b/dbms/src/Storages/tests/merge_selector2.cpp index e27fbed8cd5..db7bd8ee6c8 100644 --- a/dbms/src/Storages/tests/merge_selector2.cpp +++ b/dbms/src/Storages/tests/merge_selector2.cpp @@ -4,6 +4,7 @@ #include #include #include +#include /** This program tests merge-selecting algorithm. @@ -21,13 +22,11 @@ int main(int argc, char ** argv) IMergeSelector::Partitions partitions(1); IMergeSelector::PartsInPartition & parts = partitions.back(); - SimpleMergeSelector::Settings settings; - SimpleMergeSelector selector(settings); +/* SimpleMergeSelector::Settings settings; + SimpleMergeSelector selector(settings);*/ -/* LevelMergeSelector::Settings settings; - settings.min_parts_to_merge = 8; - settings.max_parts_to_merge = 16; - LevelMergeSelector selector(settings);*/ + LevelMergeSelector::Settings settings; + LevelMergeSelector selector(settings); ReadBufferFromFileDescriptor in(STDIN_FILENO); @@ -41,18 +40,19 @@ int main(int argc, char ** argv) IMergeSelector::Part part; in >> part.size >> "\t" >> part.age >> "\t" >> part.level >> "\t" >> part_names.back() >> "\n"; part.data = part_names.back().data(); - part.level = 0; +// part.level = 0; parts.emplace_back(part); sum_parts_size += part.size; } + size_t total_size_merged = 0; size_t sum_size_written = sum_parts_size; size_t num_merges = 1; size_t age_passed = 0; while (parts.size() > 1) { - IMergeSelector::PartsInPartition selected_parts = selector.select(partitions, 0 /*100ULL * 1024 * 1024 * 1024*/); + IMergeSelector::PartsInPartition selected_parts = selector.select(partitions, 100ULL * 1024 * 1024 * 1024); if (selected_parts.empty()) { @@ -109,6 +109,8 @@ int main(int argc, char ** argv) std::cout << '\n'; sum_size_written += sum_merged_size; + total_size_merged += sum_merged_size; + ++num_merges; double time_to_merge = sum_merged_size / (1048576 * 10.0); @@ -117,7 +119,9 @@ int main(int argc, char ** argv) for (auto & part : parts) part.age += time_to_merge; - std::cout << "Time passed: " << age_passed << ", num parts: " << parts.size() << '\n'; + std::cout << "Time passed: " << age_passed << ", num parts: " << parts.size() + << ", merged " << selected_parts.size() << " parts, " << formatReadableSizeWithBinarySuffix(sum_merged_size) + << ", total written: " << formatReadableSizeWithBinarySuffix(total_size_merged) << '\n'; } std::cout << std::fixed << std::setprecision(2) diff --git a/dbms/src/Storages/tests/row_source_bitwise_test.cpp b/dbms/src/Storages/tests/row_source_bitwise_test.cpp new file mode 100644 index 00000000000..90d9e44fbf0 --- /dev/null +++ b/dbms/src/Storages/tests/row_source_bitwise_test.cpp @@ -0,0 +1,34 @@ +#include +#include +using DB::RowSourcePart; + +static void check(const RowSourcePart & s, size_t num, bool flag) +{ + if ((s.getSourceNum() != num || s.getSkipFlag() != flag) || (!flag && s.getData() != num)) + { + printf("FAIL"); + std::exit(-1); + } +} + +int main(int, char **) +{ + check(RowSourcePart(0, false), 0, false); + check(RowSourcePart(0, true), 0, true); + check(RowSourcePart(1, false), 1, false); + check(RowSourcePart(1, true), 1, true); + check(RowSourcePart(RowSourcePart::MAX_PARTS, false), RowSourcePart::MAX_PARTS, false); + check(RowSourcePart(RowSourcePart::MAX_PARTS, true), RowSourcePart::MAX_PARTS, true); + + RowSourcePart p{80, false}; + check(p, 80, false); + p.setSkipFlag(true); + check(p, 80, true); + p.setSkipFlag(false); + check(p, 80, false); + p.setSourceNum(RowSourcePart::MAX_PARTS); + check(p, RowSourcePart::MAX_PARTS, false); + + printf("PASSED"); + return 0; +} diff --git a/dbms/tests/queries/0_stateless/00394_new_nested_column_keeps_offsets.reference b/dbms/tests/queries/0_stateless/00394_new_nested_column_keeps_offsets.reference new file mode 100644 index 00000000000..3607938c252 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00394_new_nested_column_keeps_offsets.reference @@ -0,0 +1,9 @@ +[0] [1] ['1'] +[0,0] [1,2] ['1','12'] +[0,0,0] [1,2,3] ['1','12','123'] +[0] +[0,0] +[0,0,0] +[0] +[0,0] +[0,0,0] diff --git a/dbms/tests/queries/0_stateless/00394_new_nested_column_keeps_offsets.sql b/dbms/tests/queries/0_stateless/00394_new_nested_column_keeps_offsets.sql new file mode 100644 index 00000000000..e0f38ed48c1 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00394_new_nested_column_keeps_offsets.sql @@ -0,0 +1,17 @@ +DROP TABLE IF EXISTS test.alter; +CREATE TABLE test.alter (d Date, k UInt64, i32 Int32, n Nested(ui8 UInt8, s String)) ENGINE=MergeTree(d, k, 8192); + +INSERT INTO test.alter VALUES ('2015-01-01', 3, 30, [1,2,3], ['1','12','123']); +INSERT INTO test.alter VALUES ('2015-01-01', 2, 20, [1,2], ['1','12']); +INSERT INTO test.alter VALUES ('2015-01-01', 1, 10, [1], ['1']); + +ALTER TABLE test.alter ADD COLUMN `n.i8` Array(Int8) AFTER i32; + +SELECT `n.i8`, `n.ui8`, `n.s` FROM test.alter ORDER BY k; +SELECT `n.i8` FROM test.alter ORDER BY k; + +OPTIMIZE TABLE test.alter; + +SELECT `n.i8` FROM test.alter ORDER BY k; + +DROP TABLE IF EXISTS test.alter; diff --git a/dbms/tests/queries/0_stateless/00394_replaceall_vector_fixed.reference b/dbms/tests/queries/0_stateless/00394_replaceall_vector_fixed.reference new file mode 100644 index 00000000000..5b9a519f2f2 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00394_replaceall_vector_fixed.reference @@ -0,0 +1,4 @@ +bao ba* +bar bar +boa b*a +foo f** diff --git a/dbms/tests/queries/0_stateless/00394_replaceall_vector_fixed.sql b/dbms/tests/queries/0_stateless/00394_replaceall_vector_fixed.sql new file mode 100644 index 00000000000..479d3299487 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00394_replaceall_vector_fixed.sql @@ -0,0 +1,15 @@ +DROP TABLE IF EXISTS test.replaceall; +CREATE TABLE test.replaceall (str FixedString(3)) ENGINE = Memory; + +INSERT INTO test.replaceall VALUES ('foo'); +INSERT INTO test.replaceall VALUES ('boa'); +INSERT INTO test.replaceall VALUES ('bar'); +INSERT INTO test.replaceall VALUES ('bao'); + +SELECT + str, + replaceAll(str, 'o', '*') AS replaced +FROM test.replaceall +ORDER BY str ASC; + +DROP TABLE test.replaceall; diff --git a/doc/build.md b/doc/build.md index d831205ce17..fcbea117f1c 100644 --- a/doc/build.md +++ b/doc/build.md @@ -111,7 +111,7 @@ This is rarely used but enabled by default. If you don't need it, you could set variable and skip installation step: ``` -export DISABLE_MONGODB=1 +export ENABLE_MONGODB=0 ``` Otherwise: diff --git a/doc/build_osx.md b/doc/build_osx.md index 23338842924..efe423a6934 100644 --- a/doc/build_osx.md +++ b/doc/build_osx.md @@ -24,7 +24,7 @@ brew install boost --cc=gcc-6 ## Install required libraries ``` -brew install icu4c mysql openssl unixodbc glib libtool gettext +brew install icu4c mysql openssl unixodbc glib libtool gettext homebrew/dupes/libiconv homebrew/dupes/zlib ``` ## Install optional libraries @@ -54,7 +54,7 @@ export CXX=g++-6 ## Disable MongoDB binding ``` -export DISABLE_MONGODB=1 +export ENABLE_MONGODB=0 ``` ## Detect number of threads @@ -72,3 +72,7 @@ cmake .. make -j $THREADS cd .. ``` + +# Caveats + +If you intend to run clickhouse-server, make sure to increase system's maxfiles variable. See [MacOS.md](https://github.com/yandex/ClickHouse/blob/master/MacOS.md) for more details. diff --git a/doc/developers/style_ru.md b/doc/developers/style_ru.md index 69fa678ece3..4d821fb49d2 100644 --- a/doc/developers/style_ru.md +++ b/doc/developers/style_ru.md @@ -194,8 +194,7 @@ QueryProcessingStage::Enum stage = QueryProcessingStage::Complete); /// До какой стадии выполнять SELECT запрос. ``` -4. Комментарии следует писать на русском или английском языке. - Если вы хорошо умеете выразить мысль на английском, то лучше писать на английском языке. +4. Комментарии следует писать только на английском языке. 5. При написании библиотеки, разместите подробный комментарий о том, что это такое, в самом главном заголовочном файле. diff --git a/doc/getting_started/getting_started_en.md b/doc/getting_started/getting_started_en.md index 96ffc467a2b..42a810f40f9 100644 --- a/doc/getting_started/getting_started_en.md +++ b/doc/getting_started/getting_started_en.md @@ -33,7 +33,7 @@ https://github.com/yandex/ClickHouse/blob/master/doc/build.md Install required packages. After that let's run the following command from directory with source code of ClickHouse: ``` -~/ClickHouse$ DISABLE_MONGODB=1 ./release --standalone +~/ClickHouse$ ENABLE_MONGODB=0 ./release --standalone ``` The build successfully completed: diff --git a/doc/getting_started/getting_started_ru.md b/doc/getting_started/getting_started_ru.md index 785c04f5398..69cce63ea5b 100644 --- a/doc/getting_started/getting_started_ru.md +++ b/doc/getting_started/getting_started_ru.md @@ -33,7 +33,7 @@ https://github.com/yandex/ClickHouse/blob/master/doc/build.md Установим необходимые пакеты. После этого выполним следующую команду из директории с исходными кодами ClickHouse: ``` -~/ClickHouse$ DISABLE_MONGODB=1 ./release --standalone +~/ClickHouse$ ENABLE_MONGODB=0 ./release --standalone ``` Сборка успешно завершена: diff --git a/doc/reference_en.html b/doc/reference_en.html index 5e2a938622f..c4cc59afd3c 100644 --- a/doc/reference_en.html +++ b/doc/reference_en.html @@ -441,7 +441,7 @@ By default, access is allowed from everywhere for the default user without a pas ===Installing from source=== -Build following the instructions in build.md +To build, follow the instructions in build.md (for Linux) or in build_osx.md (for Mac OS X). You can compile packages and install them. You can also use programs without installing packages. @@ -4607,73 +4607,73 @@ Casting to FixedString(N) works only for String and FixedString(N). ==Functions for working with dates and times== ===toYear=== -- Converts a date or date with time to a UInt16 number containing the year number (AD). +Converts a date or date with time to a UInt16 number containing the year number (AD). ===toMonth=== -- Converts a date or date with time to a UInt8 number containing the month number (1-12). +Converts a date or date with time to a UInt8 number containing the month number (1-12). ===toDayOfMonth=== -- Converts a date or date with time to a UInt8 number containing the number of the day of the month (1-31). +Converts a date or date with time to a UInt8 number containing the number of the day of the month (1-31). ===toDayOfWeek=== -- Converts a date or date with time to a UInt8 number containing the number of the day of the week (Monday is 1, and Sunday is 7). +Converts a date or date with time to a UInt8 number containing the number of the day of the week (Monday is 1, and Sunday is 7). ===toHour=== -- Converts a date with time to a UInt8 number containing the number of the hour in 24-hour time (0-23). +Converts a date with time to a UInt8 number containing the number of the hour in 24-hour time (0-23). This function assumes that if clocks are moved ahead, it is by one hour and occurs at 2 a.m., and if clocks are moved back, it is by one hour and occurs at 3 a.m. (which is not always true - even in Moscow the clocks were once changed at a different time). ===toMinute=== -- Converts a date with time to a UInt8 number containing the number of the minute of the hour (0-59). +Converts a date with time to a UInt8 number containing the number of the minute of the hour (0-59). ===toSecond=== -- Converts a date with time to a UInt8 number containing the number of the second in the minute (0-59). +Converts a date with time to a UInt8 number containing the number of the second in the minute (0-59). Leap seconds are not accounted for. ===toMonday=== -- Rounds down a date or date with time to the nearest Monday. +Rounds down a date or date with time to the nearest Monday. Returns the date. ===toStartOfMonth=== -- Rounds down a date or date with time to the first day of the month. +Rounds down a date or date with time to the first day of the month. Returns the date. ===toStartOfQuarter=== -- Rounds down a date or date with time to the first day of the quarter. +Rounds down a date or date with time to the first day of the quarter. The first day of the quarter is either 1 January, 1 April, 1 July, or 1 October. Returns the date. ===toStartOfYear=== -- Rounds down a date or date with time to the first day of the year. +Rounds down a date or date with time to the first day of the year. Returns the date. ===toStartOfMinute=== -- Rounds down a date with time to the start of the minute. +Rounds down a date with time to the start of the minute. ===toStartOfHour=== -- Rounds down a date with time to the start of the hour. +Rounds down a date with time to the start of the hour. ===toTime=== -- Converts a date with time to the date of the start of the Unix Epoch, while preserving the time. +Converts a date with time to the date of the start of the Unix Epoch, while preserving the time. ===toRelativeYearNum=== -- Converts a date with time or date to the number of the year, starting from a certain fixed point in the past. +Converts a date with time or date to the number of the year, starting from a certain fixed point in the past. ===toRelativeMonthNum=== -- Converts a date with time or date to the number of the month, starting from a certain fixed point in the past. +Converts a date with time or date to the number of the month, starting from a certain fixed point in the past. ===toRelativeWeekNum=== -- Converts a date with time or date to the number of the week, starting from a certain fixed point in the past. +Converts a date with time or date to the number of the week, starting from a certain fixed point in the past. ===toRelativeDayNum=== -- Converts a date with time or date to the number of the day, starting from a certain fixed point in the past. +Converts a date with time or date to the number of the day, starting from a certain fixed point in the past. ===toRelativeHourNum=== -- Converts a date with time or date to the number of the hour, starting from a certain fixed point in the past. +Converts a date with time or date to the number of the hour, starting from a certain fixed point in the past. ===toRelativeMinuteNum=== -- Converts a date with time or date to the number of the minute, starting from a certain fixed point in the past. +Converts a date with time or date to the number of the minute, starting from a certain fixed point in the past. ===toRelativeSecondNum=== -- Converts a date with time or date to the number of the second, starting from a certain fixed point in the past. +Converts a date with time or date to the number of the second, starting from a certain fixed point in the past. ===now=== Accepts zero arguments and returns the current time at one of the moments of request execution. @@ -4688,11 +4688,11 @@ Accepts zero arguments and returns yesterday's date at one of the moments of The same as 'today() - 1'. ===timeSlot=== -- Rounds the time to the half hour. +Rounds the time to the half hour. This function is specific to Yandex.Metrica, since half an hour is the minimum amount of time for breaking a session into two sessions if a counter shows a single user's consecutive pageviews that differ in time by strictly more than this amount. This means that tuples (the counter number, user ID, and time slot) can be used to search for pageviews that are included in the corresponding session. ===timeSlots(StartTime, Duration)=== -- For a time interval starting at 'StartTime' and continuing for 'Duration' seconds, it returns an array of moments in time, consisting of points from this interval rounded down to the half hour. +For a time interval starting at 'StartTime' and continuing for 'Duration' seconds, it returns an array of moments in time, consisting of points from this interval rounded down to the half hour. For example, %%timeSlots(toDateTime('2012-01-01 12:20:00'), toUInt32(600)) = [toDateTime('2012-01-01 12:00:00'), toDateTime('2012-01-01 12:30:00')]%%. This is necessary for searching for pageviews in the corresponding session. @@ -4700,52 +4700,52 @@ This is necessary for searching for pageviews in the corresponding session. ==Functions for working with strings== ===empty=== -- Returns 1 for an empty string or 0 for a non-empty string. +Returns 1 for an empty string or 0 for a non-empty string. The result type is UInt8. A string is considered non-empty if it contains at least one byte, even if this is a space or a null byte. The function also works for arrays. ===notEmpty=== -- Returns 0 for an empty string or 1 for a non-empty string. +Returns 0 for an empty string or 1 for a non-empty string. The result type is UInt8. The function also works for arrays. ===length=== -- Returns the length of a string in bytes (not in characters, and not in code points). +Returns the length of a string in bytes (not in characters, and not in code points). The result type is UInt64. The function also works for arrays. ===lengthUTF8=== -- Returns the length of a string in Unicode code points (not in characters), assuming that the string contains a set of bytes that make up UTF-8 encoded text. If this assumption is not met, it returns some result (it doesn't throw an exception). +Returns the length of a string in Unicode code points (not in characters), assuming that the string contains a set of bytes that make up UTF-8 encoded text. If this assumption is not met, it returns some result (it doesn't throw an exception). The result type is UInt64. ===lower=== -- Converts ASCII Latin symbols in a string to lowercase. +Converts ASCII Latin symbols in a string to lowercase. ===upper=== -- Converts ASCII Latin symbols in a string to uppercase. +Converts ASCII Latin symbols in a string to uppercase. ===lowerUTF8=== -- Converts a string to lowercase, assuming the string contains a set of bytes that make up a UTF-8 encoded text. It doesn't detect the language. So for Turkish the result might not be exactly correct. +Converts a string to lowercase, assuming the string contains a set of bytes that make up a UTF-8 encoded text. It doesn't detect the language. So for Turkish the result might not be exactly correct. If length of UTF-8 sequence is different for upper and lower case of code point, then result for that code point could be incorrect. If value contains invalid UTF-8, the behavior is unspecified. ===upperUTF8=== -- Converts a string to uppercase, assuming the string contains a set of bytes that make up a UTF-8 encoded text. It doesn't detect the language. So for Turkish the result might not be exactly correct. +Converts a string to uppercase, assuming the string contains a set of bytes that make up a UTF-8 encoded text. It doesn't detect the language. So for Turkish the result might not be exactly correct. If length of UTF-8 sequence is different for upper and lower case of code point, then result for that code point could be incorrect. If value contains invalid UTF-8, the behavior is unspecified. ===reverse=== -- Reverses the string (as a sequence of bytes). +Reverses the string (as a sequence of bytes). ===reverseUTF8=== -- Reverses a sequence of Unicode code points, assuming that the string contains a set of bytes representing a UTF-8 text. Otherwise, it does something else (it doesn't throw an exception). +Reverses a sequence of Unicode code points, assuming that the string contains a set of bytes representing a UTF-8 text. Otherwise, it does something else (it doesn't throw an exception). ===concat(s1, s2, ...)=== -- Concatenates strings from the function arguments, without a separator. +Concatenates strings from the function arguments, without a separator. ===substring(s, offset, length)=== -- Returns a substring starting with the byte from the 'offset' index that is 'length' bytes long. Character indexing starts from one (as in standard SQL). The 'offset' and 'length' arguments must be constants. +Returns a substring starting with the byte from the 'offset' index that is 'length' bytes long. Character indexing starts from one (as in standard SQL). The 'offset' and 'length' arguments must be constants. ===substringUTF8(s, offset, length)=== The same as 'substring', but for Unicode code points. Works under the assumption that the string contains a set of bytes representing a UTF-8 encoded text. If this assumption is not met, it returns some result (it doesn't throw an exception). @@ -4753,6 +4753,8 @@ The same as 'substring', but for Unicode code points. Works under the as ===appendTrailingCharIfAbsent(s, c)=== If the %%s%% string is non-empty and does not contain the %%c%% character at the end, it appends the %%c%% character to the end. +===convertCharset(s, from, to)=== +Returns a string with the data %%s%% (encoded as %%from%% charset) that was converted to the %%to%% charset. ==Functions for searching strings== @@ -4760,14 +4762,16 @@ The search is case-sensitive in all these functions. The search substring or regular expression must be a constant in all these functions. ===position(haystack, needle)=== -- Search for the 'needle' substring in the 'haystack' string. +Searches for the 'needle' substring in the 'haystack' string. Returns the position (in bytes) of the found substring, starting from 1, or returns 0 if the substring was not found. +There's also positionCaseInsensitive function. ===positionUTF8(haystack, needle)=== The same as 'position', but the position is returned in Unicode code points. Works under the assumption that the string contains a set of bytes representing a UTF-8 encoded text. If this assumption is not met, it returns some result (it doesn't throw an exception). +There's also positionCaseInsensitiveUTF8 function. ===match(haystack, pattern)=== -- Checks whether the string matches the 'pattern' regular expression. +Checks whether the string matches the 'pattern' regular expression. The regular expression is re2. Returns 0 if it doesn't match, or 1 if it matches. @@ -4784,7 +4788,7 @@ Extracts a fragment of a string using a regular expression. If 'haystack' Extracts all the fragments of a string using a regular expression. If 'haystack' doesn't match the 'pattern' regex, an empty string is returned. Returns an array of strings consisting of all matches to the regex. In general, the behavior is the same as the 'extract' function (it takes the first subpattern, or the entire expression if there isn't a subpattern). ===like(haystack, pattern), haystack LIKE pattern operator=== -- Checks whether a string matches a simple regular expression. The regular expression can contain the metasymbols %%%%% and %%_%%. +Checks whether a string matches a simple regular expression. The regular expression can contain the metasymbols %%%%% and %%_%%. %%%%% indicates any quantity of any bytes (including zero characters). %%_%% indicates any one byte. @@ -4866,17 +4870,17 @@ SELECT replaceRegexpAll('Hello, World!', '^', 'here: ') ==Functions for working with arrays== ===empty=== -- Returns 1 for an empty array, or 0 for a non-empty array. +Returns 1 for an empty array, or 0 for a non-empty array. The result type is UInt8. The function also works for strings. ===notEmpty=== -- Returns 0 for an empty array, or 1 for a non-empty array. +Returns 0 for an empty array, or 1 for a non-empty array. The result type is UInt8. The function also works for strings. ===length=== -- Returns the number of items in the array. +Returns the number of items in the array. The result type is UInt64. The function also works for strings. @@ -4887,17 +4891,20 @@ The function also works for strings. ===emptyArrayString=== Accepts zero arguments and returns an empty array of the appropriate type. +===emptyArrayToSingle=== +Accepts an empty array as argument and returns an array of one element equal to the default value. + ===range(N)=== -- Returns an array of numbers from 0 to N-1. +Returns an array of numbers from 0 to N-1. Just in case, an exception is thrown if arrays with a total length of more than 100,000,000 elements are created in a data block. ===array(x1, ...), [x1, ...] operator=== -- Creates an array from the function arguments. +Creates an array from the function arguments. The arguments must be constants and have types that have the smallest common type. At least one argument must be passed, because otherwise it isn't clear which type of array to create. That is, you can't use this function to create an empty array (to do that, use the 'emptyArray*' function described above). Returns an 'Array(T)' type result, where 'T' is the smallest common type out of the passed arguments. ===arrayElement(arr, n), arr[n] operator=== -- Get the element with the index 'n' from the array 'arr'. +Get the element with the index 'n' from the array 'arr'. 'n' should be any integer type. Indexes in an array begin from one. Negative indexes are supported - in this case, it selects the corresponding element numbered from the end. For example, 'arr[-1]' is the last item in the array. @@ -4907,18 +4914,18 @@ If the index goes beyond the array bounds: - otherwise, a default value is returned (0 for numbers, an empty string for strings, etc.). ===has(arr, elem)=== -- Checking whether the 'arr' array has the 'elem' element. +Checks whether the 'arr' array has the 'elem' element. Returns 0 if the the element is not in the array, or 1 if it is. 'elem' must be a constant. ===indexOf(arr, x)=== -- Returns the index of the 'x' element (starting from 1) if it is in the array, or 0 if it is not. +Returns the index of the 'x' element (starting from 1) if it is in the array, or 0 if it is not. ===countEqual(arr, x)=== -- Returns the number of elements in the array equal to 'x'. Equivalent to arrayCount(elem -> elem = x, arr). +Returns the number of elements in the array equal to 'x'. Equivalent to arrayCount(elem -> elem = x, arr). ===arrayEnumerate(arr)=== -- Returns the array %%[1, 2, 3, ..., length(arr)]%% +Returns the array %%[1, 2, 3, ..., length(arr)]%% This function is normally used together with ARRAY JOIN. It allows counting something just once for each array after applying ARRAY JOIN. Example: @@ -4955,7 +4962,7 @@ WHERE (CounterID = 160656) AND notEmpty(GoalsReached) This function can also be used in higher-order functions. For example, you can use it to get array indexes for elements that match a condition. ===arrayEnumerateUniq(arr, ...)=== -- Returns an array the same size as the source array, indicating for each element what its position is among elements with the same value. +Returns an array the same size as the source array, indicating for each element what its position is among elements with the same value. For example: %%arrayEnumerateUniq([10, 20, 10, 30]) = [1, 1, 2, 1]%%. This function is useful when using ARRAY JOIN and aggregation of array elements. Example: @@ -5003,7 +5010,7 @@ SELECT arrayEnumerateUniq([1, 1, 1, 2, 2, 2], [1, 1, 2, 1, 1, 2]) AS res This is necessary when using ARRAY JOIN with a nested data structure and further aggregation across multiple elements in this structure. ===arrayJoin(arr)=== -- A special function. See the section "arrayJoin function". +A special function. See the section "arrayJoin function". ==Higher-order functions== @@ -5068,20 +5075,20 @@ Returns the index of the first element in the 'arr1' array for which  ==Functions for splitting and merging strings and arrays== ===splitByChar(separator, s)=== -- Splits a string into substrings, using 'separator' as the separator. +Splits a string into substrings, using 'separator' as the separator. 'separator' must be a string constant consisting of exactly one character. Returns an array of selected substrings. Empty substrings may be selected if the separator occurs at the beginning or end of the string, or if there are multiple consecutive separators. ===splitByString(separator, s)=== -- The same as above, but it uses a string of multiple characters as the separator. The string must be non-empty. +The same as above, but it uses a string of multiple characters as the separator. The string must be non-empty. ===arrayStringConcat(arr[, separator])=== -- Concatenates strings from the array elements, using 'separator' as the separator. +Concatenates strings from the array elements, using 'separator' as the separator. 'separator' is a string constant, an optional parameter. By default it is an empty string. Returns a string. ===alphaTokens(s)=== -- Selects substrings of consecutive bytes from the range a-z and A-Z. +Selects substrings of consecutive bytes from the range a-z and A-Z. Returns an array of selected substrings. @@ -5163,19 +5170,19 @@ URLPathHierarchy('https://example.com/browse/CONV-6788') = If the URL doesn't have anything similar, the URL remains unchanged.

cutWWW

-- Removes no more than one 'www.' from the beginning of the URL's domain, if present. +Removes no more than one 'www.' from the beginning of the URL's domain, if present.

cutQueryString

-- Removes the query-string. The question mark is also removed. +Removes the query-string. The question mark is also removed.

cutFragment

-- Removes the fragment identifier. The number sign is also removed. +Removes the fragment identifier. The number sign is also removed.

cutQueryStringAndFragment

-- Removes the query-string and fragment identifier. The question mark and number sign are also removed. +Removes the query-string and fragment identifier. The question mark and number sign are also removed.

cutURLParameter(URL, name)

-- Removes the URL parameter named 'name', if present. This function works under the assumption that the parameter name is encoded in the URL exactly the same way as in the passed argument. +Removes the URL parameter named 'name', if present. This function works under the assumption that the parameter name is encoded in the URL exactly the same way as in the passed argument. ==Functions for working with IP addresses== @@ -5288,11 +5295,11 @@ If an argument is passed, it can be any type, and its value is not used for anyt The only purpose of this argument is to prevent common subexpression elimination, so that two different instances of the same function return different columns with different random numbers. ===rand=== -- Returns a pseudo-random UInt32 number, evenly distributed among all UInt32-type numbers. +Returns a pseudo-random UInt32 number, evenly distributed among all UInt32-type numbers. Uses a linear congruential generator. ===rand64=== -- Returns a pseudo-random UInt64 number, evenly distributed among all UInt64-type numbers. +Returns a pseudo-random UInt64 number, evenly distributed among all UInt64-type numbers. Uses a linear congruential generator. @@ -5301,45 +5308,45 @@ Uses a linear congruential generator. Hash functions can be used for deterministic pseudo-random shuffling of elements. ===halfMD5=== -- Calculates the MD5 from a string. Then it takes the first 8 bytes of the hash and interprets them as UInt64 in big endian. +Calculates the MD5 from a string. Then it takes the first 8 bytes of the hash and interprets them as UInt64 in big endian. Accepts a String-type argument. Returns UInt64. This function works fairly slowly (5 million short strings per second per processor core). If you don't need MD5 in particular, use the 'sipHash64' function instead. ===MD5=== -- Calculates the MD5 from a string and returns the resulting set of bytes as FixedString(16). +Calculates the MD5 from a string and returns the resulting set of bytes as FixedString(16). If you don't need MD5 in particular, but you need a decent cryptographic 128-bit hash, use the 'sipHash128' function instead. If you need the same result as gives 'md5sum' utility, write %%lower(hex(MD5(s)))%%. ===sipHash64=== -- Calculates SipHash from a string. +Calculates SipHash from a string. Accepts a String-type argument. Returns UInt64. SipHash is a cryptographic hash function. It works at least three times faster than MD5. For more information, see https://131002.net/siphash/ ===sipHash128=== -- Calculates SipHash from a string. +Calculates SipHash from a string. Accepts a String-type argument. Returns FixedString(16). Differs from sipHash64 in that the final xor-folding state is only done up to 128 bytes. ===cityHash64=== -- Calculates CityHash64 from a string or a similar hash function for any number of any type of arguments. +Calculates CityHash64 from a string or a similar hash function for any number of any type of arguments. For String-type arguments, CityHash is used. This is a fast non-cryptographic hash function for strings with decent quality. For other types of arguments, a decent implementation-specific fast non-cryptographic hash function is used. If multiple arguments are passed, the function is calculated using the same rules and chain combinations using the CityHash combinator. For example, you can compute the checksum of an entire table with accuracy up to the row order: %%SELECT sum(cityHash64(*)) FROM table%%. ===intHash32=== -- Calculates a 32-bit hash code from any type of integer. +Calculates a 32-bit hash code from any type of integer. This is a relatively fast non-cryptographic hash function of average quality for numbers. ===intHash64=== -- Calculates a 64-bit hash code from any type of integer. +Calculates a 64-bit hash code from any type of integer. It works faster than intHash32. Average quality. ===SHA1=== ===SHA224=== ===SHA256=== -- Calculates SHA-1, SHA-224, or SHA-256 from a string and returns the resulting set of bytes as FixedString(20), FixedString(28), or FixedString(32). +Calculates SHA-1, SHA-224, or SHA-256 from a string and returns the resulting set of bytes as FixedString(20), FixedString(28), or FixedString(32). The function works fairly slowly (SHA-1 processes about 5 million short strings per second per processor core, while SHA-224 and SHA-256 process about 2.2 million). We recommend using this function only in cases when you need a specific hash function and you can't select it. Even in these cases, we recommend applying the function offline and pre-calculating values when inserting them into the table, instead of applying it in SELECTS. @@ -5714,12 +5721,12 @@ See the section "IN operators". ===tuple(x, y, ...), operator (x, y, ...)=== -- A function that allows grouping multiple columns. +A function that allows grouping multiple columns. For columns with the types T1, T2, ..., it returns a Tuple(T1, T2, ...) type tuple containing these columns. There is no cost to execute the function. Tuples are normally used as intermediate values for an argument of IN operators, or for creating a list of formal parameters of lambda functions. Tuples can't be written to a table. ===tupleElement(tuple, n), operator x.N=== -- A function that allows getting columns from a tuple. +A function that allows getting columns from a tuple. 'N' is the column index, starting from 1. 'N' must be a constant. 'N' must be a strict postive integer no greater than the size of the tuple. There is no cost to execute the function. @@ -5727,24 +5734,24 @@ There is no cost to execute the function. ==Other functions== ===hostName()=== -- Returns a string with the name of the host that this function was performed on. For distributed processing, this is the name of the remote server host, if the function is performed on a remote server. +Returns a string with the name of the host that this function was performed on. For distributed processing, this is the name of the remote server host, if the function is performed on a remote server. ===visibleWidth(x)=== -- Calculates the approximate width when outputting values to the console in text format (tab-separated). This function is used by the system for implementing Pretty formats. +Calculates the approximate width when outputting values to the console in text format (tab-separated). This function is used by the system for implementing Pretty formats. ===toTypeName(x)=== -- Gets the type name. Returns a string containing the type name of the passed argument. +Gets the type name. Returns a string containing the type name of the passed argument. ===blockSize()=== -- Gets the size of the block. +Gets the size of the block. In ClickHouse, queries are always run on blocks (sets of column parts). This function allows getting the size of the block that you called it for. ===materialize(x)=== -- Turns a constant into a full column containing just one value. +Turns a constant into a full column containing just one value. In ClickHouse, full columns and constants are represented differently in memory. Functions work differently for constant arguments and normal arguments (different code is executed), although the result is almost always the same. This function is for debugging this behavior. ===ignore(...)=== -- A function that accepts any arguments and always returns 0. +A function that accepts any arguments and always returns 0. However, the argument is still calculated. This can be used for benchmarks. ===sleep(seconds)=== @@ -5891,6 +5898,78 @@ LIMIT 10 └────────────────┴─────────┘ %% +===formatReadableSize(x)=== + +Gets a size (number of bytes). Returns a string that contains rounded size with the suffix (KiB, MiB etc.). + +Example: + +%% +SELECT + arrayJoin([1, 1024, 1024*1024, 192851925]) AS filesize_bytes, + formatReadableSize(filesize_bytes) AS filesize + +┌─filesize_bytes─┬─filesize───┐ +│ 1 │ 1.00 B │ +│ 1024 │ 1.00 KiB │ +│ 1048576 │ 1.00 MiB │ +│ 192851925 │ 183.92 MiB │ +└────────────────┴────────────┘ +%% + +===least(a, b)=== + +Returns the least element of a and b. + +===greatest(a, b)=== + +Returns the greatest element of a and b. + +===uptime()=== + +Returns server's uptime in seconds. + +===version()=== + +Returns server's version as a string. + +===rowNumberInAllBlocks()=== + +Returns an incremental row number within all blocks that were processed by this function. + +===runningDifference(x)=== + +Calculates the difference between consecutive values in the data block. +Result of the function depends on the order of the data in the blocks. + +It works only inside of the each processed block of data. Data splitting in the blocks is not explicitly controlled by the user. +If you specify ORDER BY in subquery and call runningDifference outside of it, you could get an expected result. + +Example: +%% +SELECT + EventID, + EventTime, + runningDifference(EventTime) AS delta +FROM +( + SELECT + EventID, + EventTime + FROM events + WHERE EventDate = '2016-11-24' + ORDER BY EventTime ASC + LIMIT 5 +) + +┌─EventID─┬───────────EventTime─┬─delta─┐ +│ 1106 │ 2016-11-24 00:00:04 │ 0 │ +│ 1107 │ 2016-11-24 00:00:05 │ 1 │ +│ 1108 │ 2016-11-24 00:00:05 │ 0 │ +│ 1109 │ 2016-11-24 00:00:09 │ 4 │ +│ 1110 │ 2016-11-24 00:00:10 │ 1 │ +└─────────┴─────────────────────┴───────┘ +%% ==arrayJoin function== diff --git a/doc/reference_ru.html b/doc/reference_ru.html index 500c6fee7d4..f255b113978 100644 --- a/doc/reference_ru.html +++ b/doc/reference_ru.html @@ -450,7 +450,7 @@ ClickHouse содержит настройки ограничения досту ===Установка из исходников=== -Для сборки воспользуйтесь инструкцией: build.md +Для сборки воспользуйтесь инструкцией build.md (для Linux) или build_osx.md (для Mac OS X). Вы можете собрать пакеты и установить их. Также вы можете использовать программы без установки пакетов. @@ -611,8 +611,8 @@ Date: Fri, 16 Nov 2012 19:21:50 GMT 1 -Как видно, curl немного неудобен тем, что надо URL-эскейпить пробелы. -wget сам всё эскейпит, но его не рекомендуется использовать, так как он плохо работает по HTTP 1.1 при использовании keep-alive и Transfer-Encoding: chunked. +Как видно, curl немного неудобен тем, что надо экранировать пробелы в URL. +wget экранирует самостоятельно, но его не рекомендуется использовать, так как он плохо работает по HTTP 1.1 при использовании keep-alive и Transfer-Encoding: chunked.
 $ echo 'SELECT 1' | curl 'http://localhost:8123/' --data-binary @-
@@ -908,7 +908,7 @@ cat file.csv | clickhouse-client --database=test --query="INSERT INTO test FORMA
 
 == Программа clickhouse-local ==
 
-Программа clickhouse-local позоляет выполнять быструю обработку локальных файлов, хранящих таблицы, не прибегая к развертыванию и настройке clickhouse-server ...
+Программа clickhouse-local позволяет выполнять быструю обработку локальных файлов, хранящих таблицы, не прибегая к развертыванию и настройке clickhouse-server ...
 
 ==Синтаксис==
 
@@ -943,7 +943,7 @@ cat file.csv | clickhouse-client --database=test --query="INSERT INTO test FORMA
 ===Идентификаторы===
 
 Идентификаторы (имена столбцов, функций, типов данных) могут быть квотированными или не квотированными.
-Не квотированные идентификаторы начинаются на букву латинского алфавита или подчёркивание; продолжаются на букву латинского алфавита или подчёркивание или цифру. Короче говоря, должны соответствовать регекспу %%^[a-zA-Z_][0-9a-zA-Z_]*$%%. Примеры: %%x%%, %%_1%%, %%X_y__Z123_%%.
+Не квотированные идентификаторы начинаются на букву латинского алфавита или подчёркивание; продолжаются на букву латинского алфавита или подчёркивание или цифру. Короче говоря, должны соответствовать регулярному выражению %%^[a-zA-Z_][0-9a-zA-Z_]*$%%. Примеры: %%x%%, %%_1%%, %%X_y__Z123_%%.
 Квотированные идентификаторы расположены в обратных кавычках %%`id`%% (также, как в MySQL), и могут обозначать произвольный (непустой) набор байт. При этом, внутри записи такого идентификатора, символы (например, символ обратной кавычки) могут экранироваться с помощью обратного слеша. Правила экранирования такие же, как в строковых литералах (см. ниже).
 Рекомендуется использовать идентификаторы, которые не нужно квотировать.
 
@@ -955,8 +955,8 @@ cat file.csv | clickhouse-client --database=test --query="INSERT INTO test FORMA
 
 Числовой литерал пытается распарситься:
 - сначала как 64-битное число без знака, с помощью функции strtoull;
-- если не получилось - то как 64-битное число со знаком, с помощью функци strtoll;
-- если не получилось - то как число с плавающей запятой, с помощью функци strtod;
+- если не получилось - то как 64-битное число со знаком, с помощью функции strtoll;
+- если не получилось - то как число с плавающей запятой, с помощью функции strtod;
 - иначе - ошибка.
 
 Соответствующее значение будет иметь тип минимального размера, который вмещает значение.
@@ -1598,7 +1598,7 @@ ORDER BY PageViews DESC LIMIT 1000%%
 
 Выборка с указанием относительного коэффициента является "согласованной": если рассмотреть все возможные данные, которые могли бы быть в таблице, то выборка (при использовании одного выражения сэмплирования, указанного при создании таблицы), с одинаковым коэффициентом, выбирает всегда одно и то же подмножество этих всевозможных данных. То есть, выборка из разных таблиц, на разных серверах, в разное время, делается одинаковым образом.
 
-Например, выборка по идентификаторам посетителей, выберет из разных таблиц строки с одинаковым подножеством всех возможных идентификаторов посетителей. Это позволяет использовать выборку в подзапросах в секции IN, а также при ручном сопоставлении результатов разных запросов с выборками.
+Например, выборка по идентификаторам посетителей, выберет из разных таблиц строки с одинаковым подмножеством всех возможных идентификаторов посетителей. Это позволяет использовать выборку в подзапросах в секции IN, а также при ручном сопоставлении результатов разных запросов с выборками.
 
 

Секция ARRAY JOIN

@@ -2064,7 +2064,7 @@ WHERE и HAVING отличаются тем, что WHERE выполняется Секция ORDER BY содержит список выражений, к каждому из которых также может быть приписано DESC или ASC (направление сортировки). Если ничего не приписано - это аналогично приписыванию ASC. ASC - сортировка по возрастанию, DESC - сортировка по убыванию. Обозначение направления сортировки действует на одно выражение, а не на весь список. Пример: %%ORDER BY Visits DESC, SearchPhrase%% -Для сортировки по значениям типа String есть возможность указать collation (сравнение). Пример: %%ORDER BY SearchPhrase COLLATE 'tr'%% - для сортировки по поисковой фразе, по возрастанию, с учётом турецкого алфавита, регистронезависимо, при допущении, что строки в кодировке UTF-8. COLLATE может быть указан или не указан для каждого выражения в ORDER BY независимо. Если есть ASC или DESC, то COLLATE указывается после них. При использовании COLLATE сортировка всегда регистронезависимо. +Для сортировки по значениям типа String есть возможность указать collation (сравнение). Пример: %%ORDER BY SearchPhrase COLLATE 'tr'%% - для сортировки по поисковой фразе, по возрастанию, с учётом турецкого алфавита, регистронезависимо, при допущении, что строки в кодировке UTF-8. COLLATE может быть указан или не указан для каждого выражения в ORDER BY независимо. Если есть ASC или DESC, то COLLATE указывается после них. При использовании COLLATE сортировка всегда регистронезависима. Рекомендуется использовать COLLATE только для окончательной сортировки небольшого количества строк, так как производительность сортировки с указанием COLLATE меньше, чем обычной сортировки по байтам. @@ -2428,14 +2428,14 @@ curl -F 'passwd=@passwd.tsv;' 'http://localhost:8123/?query=SELECT+shell,+count( %%Merge(hits, '^WatchLog')%% -- данные будут читаться из таблиц в базе hits, имена которых соответствуют регекспу '^WatchLog'. +- данные будут читаться из таблиц в базе hits, имена которых соответствуют регулярному выражению '^WatchLog'. Вместо имени базы данных может использоваться константное выражение, возвращающее строку. Например, %%currentDatabase()%%. Регулярные выражения - re2 (как PCRE, но без особых извратов), регистрозависимые. Смотрите замечание об экранировании в регулярных выражениях в разделе "match". -При выборе таблиц для чтения, сама Merge-таблица не будет выбрана, даже если попадает под регексп - чтобы не возникло циклов. +При выборе таблиц для чтения, сама Merge-таблица не будет выбрана, даже если попадает под регулярное выражение - чтобы не возникло циклов. Впрочем, вы можете создать две Merge-таблицы, которые будут пытаться бесконечно читать данные друг-друга. Этого делать не нужно. Типичный способ использования движка Merge - возможность работы с большим количеством таблиц типа TinyLog, как с одной. @@ -2567,6 +2567,7 @@ calcs - имя кластера в конфигурационном файле Если после INSERT-а в Distributed таблицу, сервер перестал существовать или был грубо перезапущен (например, в следствие аппаратного сбоя), то записанные данные могут быть потеряны. Если в директории таблицы обнаружен повреждённый кусок данных, то он переносится в поддиректорию broken и больше не используется. +При выставлении опции max_parallel_replicas выполнение запроса распараллеливается по всем репликам внутри одного шарда. Подробнее смотрите раздел "Настройки, max_parallel_replicas". ==MergeTree== @@ -2643,12 +2644,12 @@ CollapsingMergeTree принимает дополнительный параме Здесь Sign - столбец, содержащий -1 для "старых" значений и 1 для "новых" значений. -При слиянии, для каждой группы идущих подряд одинаковых значений первичного ключа (столбцов, по которым сортируются данные), остаётся не более одной строки со значением столбца sign_column = -1 ("отрицательной строки") и не более одиной строки со значением столбца sign_column = 1 ("положительной строки"). То есть - производится схлопывание записей из лога изменений. +При слиянии, для каждой группы идущих подряд одинаковых значений первичного ключа (столбцов, по которым сортируются данные), остаётся не более одной строки со значением столбца sign_column = -1 ("отрицательной строки") и не более одной строки со значением столбца sign_column = 1 ("положительной строки"). То есть - производится схлопывание записей из лога изменений. Если количество положительных и отрицательных строк совпадает - то пишет первую отрицательную и последнюю положительную строку. Если положительных на 1 больше, чем отрицательных - то пишет только последнюю положительную строку. Если отрицательных на 1 больше, чем положительных - то пишет только первую отрицательную строку. -Иначе - логическая ошибка, и ни одна из таких строк не пишется. (Логическая ошибка может возникать, если случано один кусок лога был вставлен более одного раза. Поэтому, об ошибке всего лишь пишется в лог сервера, и слияние продолжает работать.) +Иначе - логическая ошибка, и ни одна из таких строк не пишется. (Логическая ошибка может возникать, если случайно один кусок лога был вставлен более одного раза. Поэтому, об ошибке всего лишь пишется в лог сервера, и слияние продолжает работать.) Как видно, от схлопывания не должны меняться результаты расчётов статистик. Изменения постепенно схлопываются так что в конце-концов, для почти каждого объекта, остаются лишь его последние значения. @@ -2868,7 +2869,7 @@ min_bytes, max_bytes - условие на количество байт в бу Данные, вставляемые в таблицу Buffer, попадают в подчинённую таблицу в порядке, возможно отличающимся от порядка вставки, и блоками, возможно отличающимися от вставленных блоков. В связи с этим, трудно корректно использовать таблицу типа Buffer для записи в CollapsingMergeTree. Чтобы избежать проблемы, можно выставить num_layers в 1. -Если таблица назначения является реплицируемой, то при записи в таблицу Buffer будут потеряны некоторые ожидаемые свойства реплицируемых таблиц. Из-за произвольного изменения порядка строк и размеров блоков данных, перестаёт работать дедубликация данных, в результате чего исчезает возможность надёжной exactly once записи в реплицируемые таблицы. +Если таблица назначения является реплицируемой, то при записи в таблицу Buffer будут потеряны некоторые ожидаемые свойства реплицируемых таблиц. Из-за произвольного изменения порядка строк и размеров блоков данных, перестаёт работать дедупликация данных, в результате чего исчезает возможность надёжной exactly once записи в реплицируемые таблицы. В связи с этими недостатками, таблицы типа Buffer можно рекомендовать к применению лишь в очень редких случаях. @@ -2928,7 +2929,7 @@ min_bytes, max_bytes - условие на количество байт в бу Каждый блок данных записывается атомарно. Запрос INSERT разбивается на блоки данных размером до max_insert_block_size = 1048576 строк. То есть, если в запросе INSERT менее 1048576 строк, то он делается атомарно. -Блоки данных дедублицируются. При многократной записи одного и того же блока данных (блоков данных одинакового размера, содержащих одни и те же строчки в одном и том же порядке), блок будет записан только один раз. Это сделано для того, чтобы в случае сбоя в сети, когда клиентское приложение не может понять, были ли данные записаны в БД, можно было просто повторить запрос INSERT. При этом не имеет значения, на какую реплику будут отправлены INSERT-ы с одинаковыми данными. То есть, обеспечивается идемпотентность INSERT-ов. Это работает только для последних 100 вставленных в таблицу блоков. +Блоки данных дедуплицируются. При многократной записи одного и того же блока данных (блоков данных одинакового размера, содержащих одни и те же строчки в одном и том же порядке), блок будет записан только один раз. Это сделано для того, чтобы в случае сбоя в сети, когда клиентское приложение не может понять, были ли данные записаны в БД, можно было просто повторить запрос INSERT. При этом не имеет значения, на какую реплику будут отправлены INSERT-ы с одинаковыми данными. То есть, обеспечивается идемпотентность INSERT-ов. Это работает только для последних 100 вставленных в таблицу блоков. При репликации, по сети передаются только исходные вставляемые данные. Дальнейшие преобразования данных (слияния) координируются и делаются на всех репликах одинаковым образом. За счёт этого минимизируется использование сети, и благодаря этому, репликация хорошо работает при расположении реплик в разных датацентрах. (Стоит заметить, что дублирование данных в разных датацентрах, по сути, является основной задачей репликации). @@ -3044,7 +3045,7 @@ min_bytes, max_bytes - условие на количество байт в бу ===Восстановление в случае потери или повреждения метаданных на ZooKeeper кластере=== -Если вы продолбали ZooKeeper, то вы можете сохранить данные, переместив их в нереплицируемую таблицу, как описано в пункте выше. +Если данные в ZooKeeper оказались утеряны или повреждены, то вы можете сохранить данные, переместив их в нереплицируемую таблицу, как описано в пункте выше. ==Перешардирование== @@ -3141,7 +3142,7 @@ TO ... ==system.one== -Таблица содержит одну строку с одним столбцом dummy типа UInt8, содержащим значени 0. +Таблица содержит одну строку с одним столбцом dummy типа UInt8, содержащим значение 0. Эта таблица используется, если в SELECT запросе не указана секция FROM. То есть, это - аналог таблицы DUAL, которую можно найти в других СУБД. @@ -3619,7 +3620,7 @@ localhost Целые числа пишутся в десятичной форме. Числа могут содержать лишний символ "+" в начале (игнорируется при парсинге, а при форматировании не пишется). Неотрицательные числа не могут содержать знак отрицания. При чтении допустим парсинг пустой строки, как числа ноль, или (для знаковых типов) строки, состоящей из одного минуса, как числа ноль. Числа, не помещающиеся в соответствующий тип данных, могут парсится, как некоторое другое число, без сообщения об ошибке. Числа с плавающей запятой пишутся в десятичной форме. При этом, десятичный разделитель - точка. Поддерживается экспоненциальная запись, а также inf, +inf, -inf, nan. Запись числа с плавающей запятой может начинаться или заканчиваться на десятичную точку. -При форматрировании, возможна потеря точности чисел с плавающей запятой. +При форматировании возможна потеря точности чисел с плавающей запятой. При парсинге, допустимо чтение не обязательно наиболее близкого к десятичной записи машинно-представимого числа. Даты выводятся в формате YYYY-MM-DD, парсятся в том же формате, но с любыми символами в качестве разделителей. @@ -3879,7 +3880,7 @@ Extremes: } %% -JSON совместим с JavaScript. Для этого, дополнительно эскейпятся некоторые символы: символ прямого слеша %%/%% экранируется в виде %%\/%%; альтернативные переводы строк %%U+2028%%, %%U+2029%%, на которых ломаются некоторые браузеры, экранируются в виде \uXXXX-последовательностей. Эскейпятся ASCII control characters: backspace, form feed, line feed, carriage return, horizontal tab в виде %%\b%%, %%\f%%, %%\n%%, %%\r%%, %%\t%% соответственно, а также остальные байты из диапазона 00-1F с помощью \uXXXX-последовательностей. Невалидные UTF-8 последовательности заменяются на replacement character %%�%% и, таким образом, выводимый текст будет состоять из валидных UTF-8 последовательностей. Числа типа UInt64 и Int64, для совместимости с JavaScript, по умолчанию выводятся в двойных кавычках, чтобы они выводились без кавычек можно установить конфигурационный параметр output_format_json_quote_64bit_integers равным 0. +JSON совместим с JavaScript. Для этого, дополнительно экранируются некоторые символы: символ прямого слеша %%/%% экранируется в виде %%\/%%; альтернативные переводы строк %%U+2028%%, %%U+2029%%, на которых ломаются некоторые браузеры, экранируются в виде \uXXXX-последовательностей. Экранируются ASCII control characters: backspace, form feed, line feed, carriage return, horizontal tab в виде %%\b%%, %%\f%%, %%\n%%, %%\r%%, %%\t%% соответственно, а также остальные байты из диапазона 00-1F с помощью \uXXXX-последовательностей. Невалидные UTF-8 последовательности заменяются на replacement character %%�%% и, таким образом, выводимый текст будет состоять из валидных UTF-8 последовательностей. Числа типа UInt64 и Int64, для совместимости с JavaScript, по умолчанию выводятся в двойных кавычках, чтобы они выводились без кавычек можно установить конфигурационный параметр output_format_json_quote_64bit_integers равным 0. %%rows%% - общее количество выведенных строчек. %%rows_before_limit_at_least%% - не менее скольких строчек получилось бы, если бы не было LIMIT-а. Выводится только если запрос содержит LIMIT. @@ -4424,7 +4425,7 @@ END Функции могут быть по-разному реализованы для константных и не константных аргументов (выполняется разный код). Но результат работы для константы и полноценного столбца, содержащего только одно такое же значение, должен совпадать. -===Иммутабельность=== +===Неизменяемость=== Функции не могут поменять значения своих аргументов - любые изменения возвращаются в качестве результата. Соответственно, от порядка записи функций в запросе, результат вычислений отдельных функций не зависит. @@ -4564,9 +4565,9 @@ END ===greater, оператор >=== -===lessOrEquals, оператор <==== +===lessOrEquals, оператор <==== -===greaterOrEquals, оператор >==== +===greaterOrEquals, оператор >==== ==Логические функции== @@ -4647,7 +4648,7 @@ SELECT ===reinterpretAsString=== -Функция принимает число или дату или дату-с-временем и возвращает строку, содержающую байты, представляющие соответствующее значение в host order (little endian). При этом, отбрасываются нулевые байты с конца. Например, значение 255 типа UInt32 будет строкой длины 1 байт. +Функция принимает число или дату или дату-с-временем и возвращает строку, содержащую байты, представляющие соответствующее значение в host order (little endian). При этом, отбрасываются нулевые байты с конца. Например, значение 255 типа UInt32 будет строкой длины 1 байт. ===CAST(x, t)=== @@ -4692,79 +4693,79 @@ SELECT ===toYear=== -- переводит дату или дату-с-временем в число типа UInt16, содержащее номер года (AD). +Переводит дату или дату-с-временем в число типа UInt16, содержащее номер года (AD). ===toMonth=== -- переводит дату или дату-с-временем в число типа UInt8, содержащее номер месяца (1-12). +Переводит дату или дату-с-временем в число типа UInt8, содержащее номер месяца (1-12). ===toDayOfMonth=== -- переводит дату или дату-с-временем в число типа UInt8, содержащее номер дня в месяце (1-31). +Переводит дату или дату-с-временем в число типа UInt8, содержащее номер дня в месяце (1-31). ===toDayOfWeek=== -- переводит дату или дату-с-временем в число типа UInt8, содержащее номер дня в неделе (понедельник - 1, воскресенье - 7). +Переводит дату или дату-с-временем в число типа UInt8, содержащее номер дня в неделе (понедельник - 1, воскресенье - 7). ===toHour=== -- переводит дату-с-временем в число типа UInt8, содержащее номер часа в сутках (0-23). +Переводит дату-с-временем в число типа UInt8, содержащее номер часа в сутках (0-23). Функция исходит из допущения, что перевод стрелок вперёд, если осуществляется, то на час, в два часа ночи, а перевод стрелок назад, если осуществляется, то на час, в три часа ночи (что, в общем, не верно - даже в Москве два раза перевод стрелок был осуществлён в другое время). ===toMinute=== -- переводит дату-с-временем в число типа UInt8, содержащее номер минуты в часе (0-59). +Переводит дату-с-временем в число типа UInt8, содержащее номер минуты в часе (0-59). ===toSecond=== -- переводит дату-с-временем в число типа UInt8, содержащее номер секунды в минуте (0-59). +Переводит дату-с-временем в число типа UInt8, содержащее номер секунды в минуте (0-59). Секунды координации не учитываются. ===toMonday=== -- округляет дату или дату-с-временем вниз до ближайшего понедельника. +Округляет дату или дату-с-временем вниз до ближайшего понедельника. Возвращается дата. ===toStartOfMonth=== -- округляет дату или дату-с-временем вниз до первого дня месяца. +Округляет дату или дату-с-временем вниз до первого дня месяца. Возвращается дата. ===toStartOfQuarter=== -- округляет дату или дату-с-временем вниз до первого дня квартала. +Округляет дату или дату-с-временем вниз до первого дня квартала. Первый день квартала - это одно из 1 января, 1 апреля, 1 июля, 1 октября. Возвращается дата. ===toStartOfYear=== -- округляет дату или дату-с-временем вниз до первого дня года. +Округляет дату или дату-с-временем вниз до первого дня года. Возвращается дата. ===toStartOfMinute=== -- округляет дату-с-временем вниз до начала минуты. +Округляет дату-с-временем вниз до начала минуты. ===toStartOfFiveMinute=== -- округляет дату-с-временем вниз до начала пятиминутного интервала. +Округляет дату-с-временем вниз до начала пятиминутного интервала. Замечание: если вам нужно округлить дату-с-временем до какого-либо другого количества секунд, минут или часов, вы можете перевести её в число с помощью функции %%toUInt32%%, затем округлить число с помощью функции %%intDiv%% и умножения, а затем перевести обратно, с помощью функции %%toDateTime%%. ===toStartOfHour=== -- округляет дату-с-временем вниз до начала часа. +Округляет дату-с-временем вниз до начала часа. ===toTime=== -- переводит дату-с-временем на дату начала unix-эпохи, сохраняя при этом время. +Переводит дату-с-временем на дату начала unix-эпохи, сохраняя при этом время. ===toRelativeYearNum=== -- переводит дату-с-временем или дату в номер года, начиная с некоторого фиксированного момента в прошлом. +Переводит дату-с-временем или дату в номер года, начиная с некоторого фиксированного момента в прошлом. ===toRelativeMonthNum=== -- переводит дату-с-временем или дату в номер месяца, начиная с некоторого фиксированного момента в прошлом. +Переводит дату-с-временем или дату в номер месяца, начиная с некоторого фиксированного момента в прошлом. ===toRelativeWeekNum=== -- переводит дату-с-временем или дату в номер недели, начиная с некоторого фиксированного момента в прошлом. +Переводит дату-с-временем или дату в номер недели, начиная с некоторого фиксированного момента в прошлом. ===toRelativeDayNum=== -- переводит дату-с-временем или дату в номер дня, начиная с некоторого фиксированного момента в прошлом. +Переводит дату-с-временем или дату в номер дня, начиная с некоторого фиксированного момента в прошлом. ===toRelativeHourNum=== -- переводит дату-с-временем в номер часа, начиная с некоторого фиксированного момента в прошлом. +Переводит дату-с-временем в номер часа, начиная с некоторого фиксированного момента в прошлом. ===toRelativeMinuteNum=== -- переводит дату-с-временем в номер минуты, начиная с некоторого фиксированного момента в прошлом. +Переводит дату-с-временем в номер минуты, начиная с некоторого фиксированного момента в прошлом. ===toRelativeSecondNum=== -- переводит дату-с-временем в номер секунды, начиная с некоторого фиксированного момента в прошлом. +Переводит дату-с-временем в номер секунды, начиная с некоторого фиксированного момента в прошлом. ===now=== Принимает ноль аргументов и возвращает текущее время на один из моментов выполнения запроса. @@ -4779,11 +4780,11 @@ SELECT Делает то же самое, что today() - 1. ===timeSlot=== -- округляет время до получаса. +Округляет время до получаса. Эта функция является специфичной для Яндекс.Метрики, так как пол часа - минимальное время, для которого, если соседние по времени хиты одного посетителя на одном счётчике отстоят друг от друга строго более, чем на это время, визит может быть разбит на два визита. То есть, кортежи (номер счётчика, идентификатор посетителя, тайм-слот) могут использоваться для поиска хитов, входящий в соответствующий визит. ===timeSlots(StartTime, Duration)=== -- для интервала времени, начинающегося в StartTime и продолжающегося Duration секунд, возвращает массив моментов времени, состоящий из округлений вниз до получаса точек из этого интервала. +Для интервала времени, начинающегося в StartTime и продолжающегося Duration секунд, возвращает массив моментов времени, состоящий из округлений вниз до получаса точек из этого интервала. Например, %%timeSlots(toDateTime('2012-01-01 12:20:00'), toUInt32(600)) = [toDateTime('2012-01-01 12:00:00'), toDateTime('2012-01-01 12:30:00')]%%. Это нужно для поиска хитов, входящих в соответствующий визит. @@ -4791,54 +4792,54 @@ SELECT ==Функции для работы со строками== ===empty=== -- возвращает 1 для пустой строки, и 0 для непустой строки. +Возвращает 1 для пустой строки, и 0 для непустой строки. Тип результата - UInt8. Строка считается непустой, если содержит хотя бы один байт, пусть даже это пробел или нулевой байт. Функция также работает для массивов. ===notEmpty=== -- возвращает 0 для пустой строки, и 1 для непустой строки. +Возвращает 0 для пустой строки, и 1 для непустой строки. Тип результата - UInt8. Функция также работает для массивов. ===length=== -- возвращает длину строки в байтах (не символах, не кодовых точках). +Возвращает длину строки в байтах (не символах, не кодовых точках). Тип результата - UInt64. Функция также работает для массивов. ===lengthUTF8=== -- возвращает длину строки в кодовых точках Unicode (не символах), при допущении, что строка содержит набор байт, являющийся текстом в кодировке UTF-8. Если допущение не выполнено - то возвращает какой-нибудь результат (не кидает исключение). +Возвращает длину строки в кодовых точках Unicode (не символах), при допущении, что строка содержит набор байт, являющийся текстом в кодировке UTF-8. Если допущение не выполнено - то возвращает какой-нибудь результат (не кидает исключение). Тип результата - UInt64. ===lower=== -- переводит ASCII-символы латиницы в строке в нижний регистр. +Переводит ASCII-символы латиницы в строке в нижний регистр. ===upper=== -- переводит ASCII-символы латиницы в строке в верхний регистр. +Переводит ASCII-символы латиницы в строке в верхний регистр. ===lowerUTF8=== -- переводит строку в нижний регистр, при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. +Переводит строку в нижний регистр, при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. Не учитывает язык. То есть, для турецкого языка, результат может быть не совсем верным. Если длина UTF-8 последовательности байт различна для верхнего и нижнего регистра кодовой точки, то для этой кодовой точки, результат работы может быть некорректным. Если строка содержит набор байт, не являющийся UTF-8, то поведение не определено. ===upperUTF8=== -- переводит строку в верхний регистр, при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. +Переводит строку в верхний регистр, при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. Не учитывает язык. То есть, для турецкого языка, результат может быть не совсем верным. Если длина UTF-8 последовательности байт различна для верхнего и нижнего регистра кодовой точки, то для этой кодовой точки, результат работы может быть некорректным. Если строка содержит набор байт, не являющийся UTF-8, то поведение не определено. ===reverse=== -- разворачивает строку (как последовательность байт). +Разворачивает строку (как последовательность байт). ===reverseUTF8=== -- разворачивает последовательность кодовых точек Unicode, при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. Иначе - что-то делает (не кидает исключение). +Разворачивает последовательность кодовых точек Unicode, при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. Иначе - что-то делает (не кидает исключение). ===concat(s1, s2, ...)=== -- склеивает строки, перечисленные в аргументах, без разделителей. +Склеивает строки, перечисленные в аргументах, без разделителей. ===substring(s, offset, length)=== -- возвращает подстроку, начиная с байта по индексу offset, длины length байт. Индексация символов - начиная с единицы (как в стандартном SQL). Аргументы offset и length должны быть константами. +Возвращает подстроку, начиная с байта по индексу offset, длины length байт. Индексация символов - начиная с единицы (как в стандартном SQL). Аргументы offset и length должны быть константами. ===substringUTF8(s, offset, length)=== Так же, как substring, но для кодовых точек Unicode. Работает при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. Если допущение не выполнено - то возвращает какой-нибудь результат (не кидает исключение). @@ -4846,6 +4847,8 @@ SELECT ===appendTrailingCharIfAbsent(s, c)=== Если строка %%s%% непустая и не содержит символ %%c%% на конце, то добавляет символ %%c%% в конец. +===convertCharset(s, from, to)=== +Возвращает сконвертированную из кодировки from в кодировку to строку s. ==Функции поиска в строках== @@ -4853,14 +4856,16 @@ SELECT Во всех функциях, подстрока для поиска или регулярное выражение, должно быть константой. ===position(haystack, needle)=== -- поиск подстроки needle в строке haystack. +Поиск подстроки needle в строке haystack. Возвращает позицию (в байтах) найденной подстроки, начиная с 1, или 0, если подстрока не найдена. +Есть также функция positionCaseInsensitive. ===positionUTF8(haystack, needle)=== Так же, как position, но позиция возвращается в кодовых точках Unicode. Работает при допущении, что строка содержит набор байт, представляющий текст в кодировке UTF-8. Если допущение не выполнено - то возвращает какой-нибудь результат (не кидает исключение). +Есть также функция positionCaseInsensitiveUTF8. ===match(haystack, pattern)=== -- проверка строки на соответствие регулярному выражению pattern. Регулярное выражение re2. +Проверка строки на соответствие регулярному выражению pattern. Регулярное выражение re2. Возвращает 0 (если не соответствует) или 1 (если соответствует). Обратите внимание, что для экранирования в регулярном выражении, используется символ %%\%% (обратный слеш). Этот же символ используется для экранирования в строковых литералах. Поэтому, чтобы экранировать символ в регулярном выражении, необходимо написать в строковом литерале %%\\%% (два обратных слеша). @@ -4875,7 +4880,7 @@ SELECT Извлечение всех фрагментов строки по регулярному выражению. Если haystack не соответствует регулярному выражению pattern, то возвращается пустая строка. Возвращается массив строк, состоящий из всех соответствий регулярному выражению. В остальном, поведение аналогично функции extract (по прежнему, вынимается первый subpattern, или всё выражение, если subpattern-а нет). ===like(haystack, pattern), оператор haystack LIKE pattern=== -- проверка строки на соответствие простому регулярному выражению. +Проверка строки на соответствие простому регулярному выражению. Регулярное выражение может содержать метасимволы %%%%% и %%_%%. %%%%% обозначает любое количество любых байт (в том числе, нулевое количество символов). %%_%% обозначает один любой байт. @@ -4959,17 +4964,17 @@ SELECT replaceRegexpAll('Hello, World!', '^', 'here: ') AS res ==Функции по работе с массивами== ===empty=== -- возвращает 1 для пустого массива, и 0 для непустого массива. +Возвращает 1 для пустого массива, и 0 для непустого массива. Тип результата - UInt8. Функция также работает для строк. ===notEmpty=== -- возвращает 0 для пустого массива, и 1 для непустого массива. +Возвращает 0 для пустого массива, и 1 для непустого массива. Тип результата - UInt8. Функция также работает для строк. ===length=== -- возвращает количество элементов в массиве. +Возвращает количество элементов в массиве. Тип результата - UInt64. Функция также работает для строк. @@ -4980,17 +4985,20 @@ SELECT replaceRegexpAll('Hello, World!', '^', 'here: ') AS res ===emptyArrayString=== Принимает ноль аргументов и возвращает пустой массив соответствующего типа. +===emptyArrayToSingle=== +Принимает пустой массив и возвращает массив из одного элемента, равного значению по умолчанию. + ===range(N)=== -- возвращает массив чисел от 0 до N-1. +Возвращает массив чисел от 0 до N-1. На всякий случай, если на блок данных, создаются массивы суммарной длины больше 100 000 000 элементов, то кидается исключение. ===array(x1, ...), оператор [x1, ...]=== -- создаёт массив из аргументов функции. +Создаёт массив из аргументов функции. Аргументы должны быть константами и иметь типы, для которых есть наименьший общий тип. Должен быть передан хотя бы один аргумент, так как иначе непонятно, какого типа создавать массив. То есть, с помощью этой функции невозможно создать пустой массив (для этого используйте функции emptyArray*, описанные выше). Возвращает результат типа Array(T), где T - наименьший общий тип от переданных аргументов. ===arrayElement(arr, n), оператор arr[n]=== -- достать элемент с индексом n из массива arr. +Достаёт элемент с индексом n из массива arr. n должен быть любым целочисленным типом. Индексы в массиве начинаются с единицы. Поддерживаются отрицательные индексы - в этом случае, будет выбран соответствующий по номеру элемент с конца. Например, arr[-1] - последний элемент массива. @@ -5000,18 +5008,18 @@ n должен быть любым целочисленным типом. - иначе, возвращается некоторое значение по умолчанию (0 для чисел, пустая строка для строк и т. п.). ===has(arr, elem)=== -- проверка наличия элемента elem в массиве arr. +Проверяет наличие элемента elem в массиве arr. Возвращает 0, если элемента в массиве нет, или 1, если есть. elem должен быть константой. ===indexOf(arr, x)=== -- возвращает индекс элемента x (начиная с 1), если он есть в массиве, или 0, если его нет. +Возвращает индекс элемента x (начиная с 1), если он есть в массиве, или 0, если его нет. ===countEqual(arr, x)=== -- возвращает количество элементов массива, равных x. Эквивалентно arrayCount(elem -> elem = x, arr). +Возвращает количество элементов массива, равных x. Эквивалентно arrayCount(elem -> elem = x, arr). ===arrayEnumerate(arr)=== -- возаращает массив %%[1, 2, 3, ..., length(arr)]%% +Возвращает массив %%[1, 2, 3, ..., length(arr)]%% Эта функция обычно используется совместно с ARRAY JOIN. Она позволяет, после применения ARRAY JOIN, посчитать что-либо только один раз для каждого массива. Пример: @@ -5048,7 +5056,7 @@ WHERE (CounterID = 160656) AND notEmpty(GoalsReached) Также эта функция может быть использована в функциях высшего порядка. Например, с её помощью можно достать индексы массива для элементов, удовлетворяющих некоторому условию. ===arrayEnumerateUniq(arr, ...)=== -- возаращает массив, такого же размера, как исходный, где для каждого элемента указано, какой он по счету среди элементов с таким же значением. +Возвращает массив, такого же размера, как исходный, где для каждого элемента указано, какой он по счету среди элементов с таким же значением. Например: %%arrayEnumerateUniq([10, 20, 10, 30]) = [1, 1, 2, 1]%%. Эта функция полезна при использовании ARRAY JOIN и агрегации по элементам массива. Пример: @@ -5096,7 +5104,7 @@ SELECT arrayEnumerateUniq([1, 1, 1, 2, 2, 2], [1, 1, 2, 1, 1, 2]) AS res Это нужно при использовании ARRAY JOIN с вложенной структурой данных и затем агрегации по нескольким элементам этой структуры. ===arrayJoin(arr)=== -- особенная функция. Смотрите раздел "Функция arrayJoin". +Особенная функция. Смотрите раздел "Функция arrayJoin". ==Функции высшего порядка== @@ -5161,20 +5169,20 @@ SELECT ==Функции разбиения и слияния строк и массивов== ===splitByChar(separator, s)=== -- разбивает строку на подстроки, используя в качестве разделителя separator. +Разбивает строку на подстроки, используя в качестве разделителя separator. separator должен быть константной строкой из ровно одного символа. Возвращается массив выделенных подстрок. Могут выделяться пустые подстроки, если разделитель идёт в начале или в конце строки, или если идёт более одного разделителя подряд. ===splitByString(separator, s)=== -- то же самое, но использует строку из нескольких символов в качестве разделителя. Строка должна быть непустой. +То же самое, но использует строку из нескольких символов в качестве разделителя. Строка должна быть непустой. ===arrayStringConcat(arr[, separator])=== -- склеивает строки, перечисленные в массиве, с разделителем separator. +Склеивает строки, перечисленные в массиве, с разделителем separator. separator - необязательный параметр, константная строка, по умолчанию равен пустой строке. Возвращается строка. ===alphaTokens(s)=== -- выделяет подстроки из подряд идущих байт из диапазонов a-z и A-Z. +Выделяет подстроки из подряд идущих байт из диапазонов a-z и A-Z. Возвращается массив выделенных подстрок. @@ -5187,59 +5195,59 @@ separator - необязательный параметр, константна Если в URL-е нет ничего похожего, то возвращается пустая строка.

protocol

-- выделить протокол. Примеры: http, ftp, mailto, magnet... +Возвращает протокол. Примеры: http, ftp, mailto, magnet...

domain

-- выделить домен. +Возвращает домен.

domainWithoutWWW

-- выделить домен, удалив не более одного 'www.' с начала, если есть. +Возвращает домен, удалив не более одного 'www.' с начала, если есть.

topLevelDomain

-- выделить домен верхнего уровня. Пример: .ru. +Возвращает домен верхнего уровня. Пример: .ru.

firstSignificantSubdomain

-- выделить "первый существенный поддомен". Это понятие является нестандартным и специфично для Яндекс.Метрики. +Возвращает "первый существенный поддомен". Это понятие является нестандартным и специфично для Яндекс.Метрики. Первый существенный поддомен - это домен второго уровня, если он не равен одному из com, net, org, co, или домен третьего уровня, иначе. Например, firstSignificantSubdomain('https://news.yandex.ru/') = 'yandex', firstSignificantSubdomain('https://news.yandex.com.tr/') = 'yandex'. Список "несущественных" доменов второго уровня и другие детали реализации могут изменяться в будущем.

cutToFirstSignificantSubdomain

-- выделяет часть домена, включающую поддомены верхнего уровня до "первого существенного поддомена" (см. выше). +Возвращает часть домена, включающую поддомены верхнего уровня до "первого существенного поддомена" (см. выше). Например, cutToFirstSignificantSubdomain('https://news.yandex.com.tr/') = 'yandex.com.tr'.

path

-- выделить путь. Пример: /top/news.html -Путь не включает в себя query-string. +Возвращает путь. Пример: /top/news.html +Путь не включает в себя query string.

pathFull

-- то же самое, но включая query-string и fragment. Пример: /top/news.html?page=2#comments +То же самое, но включая query string и fragment. Пример: /top/news.html?page=2#comments

queryString

-- выделить query-string. Пример: page=1&lr=213. +Возвращает query-string. Пример: page=1&lr=213. query-string не включает в себя начальный знак вопроса, а также # и всё, что после #.

fragment

-- выделить fragment identifier. +Возвращает fragment identifier. fragment не включает в себя начальный символ решётки.

queryStringAndFragment

-- выделить query-string и fragment identifier. Пример: страница=1#29390. +Возвращает query string и fragment identifier. Пример: страница=1#29390.

extractURLParameter(URL, name)

-- достать значение параметра name в URL, если такой есть; или пустую строку, иначе; если параметров с таким именем много - вернуть первый попавшийся. Функция работает при допущении, что имя параметра закодировано в URL в точности таким же образом, что и в переданном аргументе. +Возвращает значение параметра name в URL, если такой есть; или пустую строку, иначе; если параметров с таким именем много - вернуть первый попавшийся. Функция работает при допущении, что имя параметра закодировано в URL в точности таким же образом, что и в переданном аргументе.

extractURLParameters(URL)

-- получить массив строк вида name=value, соответствующих параметрам URL. Значения никак не декодируются. +Возвращает массив строк вида name=value, соответствующих параметрам URL. Значения никак не декодируются.

extractURLParameterNames(URL)

-- получить массив строк вида name, соответствующих именам параметров URL. Значения никак не декодируются. +Возвращает массив строк вида name, соответствующих именам параметров URL. Значения никак не декодируются.

URLHierarchy(URL)

-- получить массив, содержащий URL, обрезанный с конца по символам %%/%%, %%?%% в пути и query-string. Подряд идущие символы-разделители считаются за один. Резка производится в позиции после всех подряд идущих символов-разделителей. Пример: +Возвращает массив, содержащий URL, обрезанный с конца по символам %%/%%, %%?%% в пути и query-string. Подряд идущие символы-разделители считаются за один. Резка производится в позиции после всех подряд идущих символов-разделителей. Пример:

URLPathHierarchy(URL)

-- то же самое, но без протокола и хоста в результате. Элемент / (корень) не включается. Пример: +То же самое, но без протокола и хоста в результате. Элемент / (корень) не включается. Пример: Функция используется для реализации древовидных отчётов по URL в Яндекс.Метрике. @@ -5256,19 +5264,19 @@ URLPathHierarchy('https://example.com/browse/CONV-6788') = Если в URL-е нет ничего похожего, то URL остаётся без изменений.

cutWWW

-- удалить не более одного 'www.' с начала домена URL-а, если есть. +Удаляет не более одного 'www.' с начала домена URL-а, если есть.

cutQueryString

-- удалить query-string. Знак вопроса тоже удаляется. +Удаляет query string. Знак вопроса тоже удаляется.

cutFragment

-- удалить fragment identifier. Символ решётки тоже удаляется. +Удаляет fragment identifier. Символ решётки тоже удаляется.

cutQueryStringAndFragment

-- удалить query-string и fragment identifier. Знак вопроса и символ решётки тоже удаляются. +Удаляет query string и fragment identifier. Знак вопроса и символ решётки тоже удаляются.

cutURLParameter(URL, name)

-- удалить параметр URL с именем name, если такой есть. Функция работает при допущении, что имя параметра закодировано в URL в точности таким же образом, что и в переданном аргументе. +Удаляет параметр URL с именем name, если такой есть. Функция работает при допущении, что имя параметра закодировано в URL в точности таким же образом, что и в переданном аргументе. ==Функции для работы с IP-адресами== @@ -5381,11 +5389,11 @@ HEX может быть в любом регистре. Этот аргумент нужен только для того, чтобы предотвратить склейку одинаковых выражений - чтобы две разные записи одной функции возвращали разные столбцы, с разными случайными числами. ===rand=== -- возвращает псевдослучайное число типа UInt32, равномерно распределённое среди всех чисел типа UInt32. +Возвращает псевдослучайное число типа UInt32, равномерно распределённое среди всех чисел типа UInt32. Используется linear congruential generator. ===rand64=== -- возвращает псевдослучайное число типа UInt64, равномерно распределённое среди всех чисел типа UInt64. +Возвращает псевдослучайное число типа UInt64, равномерно распределённое среди всех чисел типа UInt64. Используется linear congruential generator. @@ -5394,46 +5402,46 @@ HEX может быть в любом регистре. Функции хэширования могут использоваться для детерминированного псевдослучайного разбрасывания элементов. ===halfMD5=== -- вычисляет MD5 от строки. Затем берёт первые 8 байт от хэша и интерпретирует их как UInt64 в big endian. +Вычисляет MD5 от строки. Затем берёт первые 8 байт от хэша и интерпретирует их как UInt64 в big endian. Принимает аргумент типа String. Возвращает UInt64. Функция работает достаточно медленно (5 миллионов коротких строк в секунду на одном процессорном ядре). Если вам не нужен конкретно MD5, то используйте вместо этого функцию sipHash64. ===MD5=== -- вычисляет MD5 от строки и возвращает полученный набор байт в виде FixedString(16). +Вычисляет MD5 от строки и возвращает полученный набор байт в виде FixedString(16). Если вам не нужен конкретно MD5, а нужен неплохой криптографический 128-битный хэш, то используйте вместо этого функцию sipHash128. Если вы хотите получить такой же результат, как выдаёт утилита md5sum, напишите %%lower(hex(MD5(s)))%%. ===sipHash64=== -- вычисляет SipHash от строки. +Вычисляет SipHash от строки. Принимает аргумент типа String. Возвращает UInt64. SipHash - криптографическая хэш-функция. Работает быстрее чем MD5 не менее чем в 3 раза. Подробнее смотрите по ссылке: https://131002.net/siphash/ ===sipHash128=== -- вычисляет SipHash от строки. +Вычисляет SipHash от строки. Принимает аргумент типа String. Возвращает FixedString(16). Отличается от sipHash64 тем, что финальный xor-folding состояния делается только до 128 бит. ===cityHash64=== -- вычисляет CityHash64 от строки или похожую хэш-функцию для произвольного количества аргументов произвольного типа. +Вычисляет CityHash64 от строки или похожую хэш-функцию для произвольного количества аргументов произвольного типа. Если аргумент имеет тип String, то используется CityHash. Это быстрая некриптографическая хэш-функция неплохого качества для строк. Если аргумент имеет другой тип, то используется implementation specific быстрая некриптографическая хэш-функция неплохого качества. Если передано несколько аргументов, то функция вычисляется по тем же правилам, с помощью комбинации по цепочке с использованием комбинатора из CityHash. Например, так вы можете вычислить чексумму всей таблицы с точностью до порядка строк: %%SELECT sum(cityHash64(*)) FROM table%%. ===intHash32=== -- вычисляет 32-битный хэш-код от целого числа любого типа. +Вычисляет 32-битный хэш-код от целого числа любого типа. Это сравнительно быстрая некриптографическая хэш-функция среднего качества для чисел. ===intHash64=== -- вычисляет 64-битный хэш-код от целого числа любого типа. +Вычисляет 64-битный хэш-код от целого числа любого типа. Работает быстрее, чем intHash32. Качество среднее. ===SHA1=== ===SHA224=== ===SHA256=== -- вычисляет SHA-1, SHA-224, SHA-256 от строки и возвращает полученный набор байт в виде FixedString(20), FixedString(28), FixedString(32). +Вычисляет SHA-1, SHA-224, SHA-256 от строки и возвращает полученный набор байт в виде FixedString(20), FixedString(28), FixedString(32). Функция работает достаточно медленно (SHA-1 - примерно 5 миллионов коротких строк в секунду на одном процессорном ядре, SHA-224 и SHA-256 - примерно 2.2 миллионов). Рекомендуется использовать эти функции лишь в тех случаях, когда вам нужна конкретная хэш-функция и вы не можете её выбрать. Даже в этих случаях, рекомендуется применять функцию оффлайн - заранее вычисляя значения при вставке в таблицу, вместо того, чтобы применять её при SELECT-ах. @@ -5447,10 +5455,10 @@ URLHash(s, N) - вычислить хэш от строки до N-го уров ==Функции кодирования== ===hex=== -Принимает строку, число, дату или дату-с-временем. Возвращает строку, содержащую шестнадцатеричное представление аргумента. Используются заглавные буквы A-F. Не используются префиксы %%0x%% и суффиксы %%h%%. Для строк просто все байты кодируются в виде двух шестнадцатиричных цифр. Числа выводятся в big endian ("человеческом") формате. Для чисел вырезаются старшие нули, но только по целым байтам. Например, %%hex(1) = '01'%%. Даты кодируются как число дней с начала unix-эпохи. Даты-с-временем кодируются как число секунд с начала unix-эпохи. +Принимает строку, число, дату или дату-с-временем. Возвращает строку, содержащую шестнадцатеричное представление аргумента. Используются заглавные буквы A-F. Не используются префиксы %%0x%% и суффиксы %%h%%. Для строк просто все байты кодируются в виде двух шестнадцатеричных цифр. Числа выводятся в big endian ("человеческом") формате. Для чисел вырезаются старшие нули, но только по целым байтам. Например, %%hex(1) = '01'%%. Даты кодируются как число дней с начала unix-эпохи. Даты-с-временем кодируются как число секунд с начала unix-эпохи. ===unhex(str)=== -Принимает строку, содержащую произвольное количество шестнадцатиричных цифр, и возвращает строку, содержащую соответствующие байты. Поддерживаются как строчные, так и заглавные буквы A-F. Число шестнадцатиричных цифр не обязано быть чётным. Если оно нечётное - последняя цифра интерпретируется как младшая половинка байта 00-0F. Если строка-аргумент содержит что-либо кроме шестнадцатиричных цифр, то будет возвращён какой-либо implementation-defined результат (не кидается исключение). +Принимает строку, содержащую произвольное количество шестнадцатеричных цифр, и возвращает строку, содержащую соответствующие байты. Поддерживаются как строчные, так и заглавные буквы A-F. Число шестнадцатеричных цифр не обязано быть чётным. Если оно нечётное - последняя цифра интерпретируется как младшая половинка байта 00-0F. Если строка-аргумент содержит что-либо кроме шестнадцатеричных цифр, то будет возвращён какой-либо implementation-defined результат (не кидается исключение). Если вы хотите преобразовать результат в число, то вы можете использовать функции reverse и reinterpretAsType. ===bitmaskToList(num)=== @@ -5463,7 +5471,7 @@ URLHash(s, N) - вычислить хэш от строки до N-го уров ==Функции округления== ===floor(x[, N])=== -Вернуть наибольшее круглое число, которое меньше или равно, чем x. +Возвращает наибольшее круглое число, которое меньше или равно, чем x. Круглым называется число, кратное 1 / 10N или ближайшее к нему число соответствующего типа данных, если 1 / 10N не представимо точно. N - целочисленная константа, не обязательный параметр. По умолчанию - ноль, что означает - округлять до целого числа. N может быть отрицательным. @@ -5473,11 +5481,11 @@ x - любой числовой тип. Результат - число того В случае переполнения при округлении (например, %%floor(-128, -1)%%), возвращается implementation specific результат. ===ceil(x[, N])=== -Вернуть наименьшее круглое число, которое больше или равно, чем x. +Возвращает наименьшее круглое число, которое больше или равно, чем x. В остальном, аналогично функции floor, см. выше. ===round(x[, N])=== -Вернуть ближайшее к num круглое число, которое может быть меньше или больше или равно x. +Возвращает ближайшее к num круглое число, которое может быть меньше или больше или равно x. Если x находится посередине от ближайших круглых чисел, то возвращается какое-либо одно из них (implementation specific). Число -0. может считаться или не считаться круглым (implementation specific). В остальном, аналогично функциям floor и ceil, см. выше. @@ -5801,7 +5809,7 @@ id должен иметь тип UInt64. ===visitParamExtractString(params, name)=== -Распарсить строку в двойных кавычках. Значение разэскейпливается. Если разэскейпить не удалось, то возвращается пустая строка. Примеры: +Распарсить строку в двойных кавычках. У значения убирается экранирование. Если убрать экранированные символы не удалось, то возвращается пустая строка. Примеры: %%visitParamExtractString('{"abc":"\\n\\u0000"}', 'abc') = '\n\0'%% %%visitParamExtractString('{"abc":"\\u263a"}', 'abc') = '☺'%% %%visitParamExtractString('{"abc":"\\u263"}', 'abc') = ''%% @@ -5817,12 +5825,12 @@ id должен иметь тип UInt64. ===tuple(x, y, ...), оператор (x, y, ...)=== -- функция, позволяющая сгруппировать несколько столбцов. +Функция, позволяющая сгруппировать несколько столбцов. Для столбцов, имеющих типы T1, T2, ... возвращает кортеж типа Tuple(T1, T2, ...), содержащий эти столбцы. Выполнение функции ничего не стоит. Кортежи обычно используются как промежуточное значение в качестве аргумента операторов IN, или для создания списка формальных параметров лямбда-функций. Кортежи не могут быть записаны в таблицу. ===tupleElement(tuple, n), оператор x.N=== -- функция, позволяющая достать столбец из кортежа. +Функция, позволяющая достать столбец из кортежа. N - индекс столбца начиная с 1. N должно быть константой. N должно быть целым строго положительным числом не большим размера кортежа. Выполнение функции ничего не стоит. @@ -5830,25 +5838,25 @@ N - индекс столбца начиная с 1. N должно быть к ==Прочие функции== ===hostName()=== -- возвращает строку - имя хоста, на котором эта функция была выполнена. При распределённой обработке запроса, это будет имя хоста удалённого сервера, если функция выполняется на удалённом сервере. +Возвращает строку - имя хоста, на котором эта функция была выполнена. При распределённой обработке запроса, это будет имя хоста удалённого сервера, если функция выполняется на удалённом сервере. ===visibleWidth(x)=== -- вычисляет приблизительную ширину при выводе значения в текстовом (tab-separated) виде на консоль. +Вычисляет приблизительную ширину при выводе значения в текстовом (tab-separated) виде на консоль. Функция используется системой для реализации Pretty форматов. ===toTypeName(x)=== -- получить имя типа. Возвращает строку, содержащую имя типа переданного аргумента. +Возвращает строку, содержащую имя типа переданного аргумента. ===blockSize()=== -- получить размер блока. +Получить размер блока. В ClickHouse выполнение запроса всегда идёт по блокам (наборам кусочков столбцов). Функция позволяет получить размер блока, для которого её вызвали. ===materialize(x)=== -- превратить константу в полноценный столбец, содержащий только одно значение. +Превращает константу в полноценный столбец, содержащий только одно значение. В ClickHouse полноценные столбцы и константы представлены в памяти по-разному. Функции по-разному работают для аргументов-констант и обычных аргументов (выполняется разный код), хотя результат почти всегда должен быть одинаковым. Эта функция предназначена для отладки такого поведения. ===ignore(...)=== -- функция, принимающая любые аргументы, и всегда возвращающая 0. +Принимает любые аргументы, всегда возвращает 0. При этом, аргумент всё равно вычисляется. Это может использоваться для бенчмарков. ===sleep(seconds)=== @@ -5871,7 +5879,7 @@ N - индекс столбца начиная с 1. N должно быть к Принимает константные строки - имя базы данных, имя таблицы и название столбца. Возвращает константное выражение типа UInt8, равное 1, если есть столбец, иначе 0. Функция кидает исключение, если таблица не существует. -Для элементов вложенной структуры данных функция проверяет сушествование столбца. Для самой же вложенной структуры данных функция возвращает 0. +Для элементов вложенной структуры данных функция проверяет существование столбца. Для самой же вложенной структуры данных функция возвращает 0. ===bar=== Позволяет построить unicode-art диаграмму. @@ -5994,6 +6002,79 @@ LIMIT 10 └────────────────┴─────────┘ %% +===formatReadableSize(x)=== + +Принимает размер (число байт). Возвращает округленный размер с суффиксом (KiB, MiB и т.д.) в виде строки. + +Пример: + +%% +SELECT + arrayJoin([1, 1024, 1024*1024, 192851925]) AS filesize_bytes, + formatReadableSize(filesize_bytes) AS filesize + +┌─filesize_bytes─┬─filesize───┐ +│ 1 │ 1.00 B │ +│ 1024 │ 1.00 KiB │ +│ 1048576 │ 1.00 MiB │ +│ 192851925 │ 183.92 MiB │ +└────────────────┴────────────┘ +%% + +===least(a, b)=== + +Возвращает наименьшее значение из a и b. + +===greatest(a, b)=== + +Возвращает наибольшее значение из a и b. + +===uptime()=== + +Возвращает аптайм сервера в секундах. + +===version()=== + +Возвращает версию сервера в виде строки. + +===rowNumberInAllBlocks()=== + +Возвращает порядковый номер строки в блоке данных. Функция учитывает только задействованные блоки данных. + +===runningDifference(x)=== + +Считает разницу между последовательными значениями строк в блоке данных. +Возвращает 0 для первой строки и разницу с предыдущей строкой для каждой последующей строки. + +Результат функции зависит от затронутых блоков данных и порядка данных в блоке. +Если сделать подзапрос с ORDER BY и вызывать функцию извне подзапроса, можно будет получить ожидаемый результат. + +Пример: +%% +SELECT + EventID, + EventTime, + runningDifference(EventTime) AS delta +FROM +( + SELECT + EventID, + EventTime + FROM events + WHERE EventDate = '2016-11-24' + ORDER BY EventTime ASC + LIMIT 5 +) + +┌─EventID─┬───────────EventTime─┬─delta─┐ +│ 1106 │ 2016-11-24 00:00:04 │ 0 │ +│ 1107 │ 2016-11-24 00:00:05 │ 1 │ +│ 1108 │ 2016-11-24 00:00:05 │ 0 │ +│ 1109 │ 2016-11-24 00:00:09 │ 4 │ +│ 1110 │ 2016-11-24 00:00:10 │ 1 │ +└─────────┴─────────────────────┴───────┘ +%% + ==Функция arrayJoin== @@ -6418,7 +6499,7 @@ regions_hierarchy*.txt: TabSeparated (без заголовка), столбцы regions_names_*.txt: TabSeparated (без заголовка), столбцы: - идентификатор региона (UInt32); -- имя региона (String) - не может содержать табы или переводы строк, даже заэскейпленные. +- имя региона (String) - не может содержать табы или переводы строк, даже экранированные. Для хранения в оперативке используется плоский массив. Поэтому, идентификаторы не должны быть больше миллиона. @@ -6629,9 +6710,9 @@ id рекламодателя дата начала действия скид Для работы с такими словарями, функции dictGetT должны принимать ещё один аргумент - дату: dictGetT('dict_name', 'attr_name', id, date) - -Функция достаёт значение для данного id и для диапазона дат, в который входит переданная дата. Если не найден id или для найденного id не найден диапазон, то возвращается значение по-умолчанию для словаря. - + +Функция достаёт значение для данного id и для диапазона дат, в который входит переданная дата. Если не найден id или для найденного id не найден диапазон, то возвращается значение по умолчанию для словаря. + Если есть перекрывающиеся диапазоны, то можно использовать любой подходящий. Если граница диапазона является NULL или является некорректной датой (1900-01-01, 2039-01-01), то диапазон следует считать открытым. Диапазон может быть открытым с обеих сторон. @@ -6910,6 +6991,13 @@ dictGetT('dict_name', 'attr_name', id, date) Если равно 1 - сэмплирование по умолчанию не делается. +==max_parallel_replicas== + +Максимальное количество используемых реплик каждого шарда при выполнении запроса. +Для консистентности (чтобы получить разные части одного и того же разбиения), эта опция работает только при заданном ключе сэмплирования. +Отставание реплик не контролируется. + + ==compile== Включить компиляцию запросов. По умолчанию - 0 (выключено). @@ -6919,7 +7007,7 @@ dictGetT('dict_name', 'attr_name', id, date) ==min_count_to_compile== -После скольки раз, когда скомпилированный кусок кода мог пригодиться, выполнить его компиляцию. по умолчанию, 3. +После скольких раз, когда скомпилированный кусок кода мог пригодиться, выполнить его компиляцию. По умолчанию - 3. В случае, если значение равно нулю, то компиляция выполняется синхронно, и запрос будет ждать окончания процесса компиляции перед продолжением выполнения. Это можно использовать для тестирования, иначе используйте значения, начиная с 1. Как правило, компиляция занимает по времени около 5-10 секунд. В случае, если значение равно 1 или больше, компиляция выполняется асинхронно, в отдельном потоке. При готовности результата, он сразу же будет использован, в том числе, уже выполняющимися в данный момент запросами. @@ -7225,10 +7313,10 @@ SET profile = 'web' Каждый элемент списка имеет одну из следующих форм: <ip> IP-адрес или маска подсети. Например, 222.111.222.3 или 10.0.0.1/8 или 2a02:6b8::3 или 2a02:6b8::3/64. <host> Имя хоста. Например: example01. Для проверки делается DNS-запрос, и все полученные адреса сравниваются с адресом клиента. - <host_regexp> Регексп для имён хостов. Например, ^example\d\d-\d\d-\d\.yandex\.ru$ - Для проверки, для адреса клиента делается DNS PTR-запрос и к результату применяется регексп. + <host_regexp> Регулярное выражение для имён хостов. Например, ^example\d\d-\d\d-\d\.yandex\.ru$ + Для проверки, для адреса клиента делается DNS PTR-запрос и к результату применяется регулярное выражение. Потом для результата PTR-запроса делается снова DNS-запрос, и все полученные адреса сравниваются с адресом клиента. - Настоятельно рекомендуется, чтобы регексп заканчивался на \.yandex\.ru$. + Настоятельно рекомендуется, чтобы регулярное выражение заканчивалось на \.yandex\.ru$. Если вы устанавливаете ClickHouse самостоятельно, укажите здесь: <networks> diff --git a/libs/libcommon/CMakeLists.txt b/libs/libcommon/CMakeLists.txt index c3e1ed5a1a3..707e93d322b 100644 --- a/libs/libcommon/CMakeLists.txt +++ b/libs/libcommon/CMakeLists.txt @@ -59,37 +59,21 @@ add_library (common ${REVISIONFILE} ) -# TESTIRT-3687 DISABLE_LIBTCMALLOC - when testing for memory leaks, disable libtcmalloc -IF($ENV{DISABLE_LIBTCMALLOC}) - message(STATUS "Disabling libtcmalloc for valgrind better analysis") -ELSE($ENV{DISABLE_LIBTCMALLOC}) - IF($ENV{DEBUG_LIBTCMALLOC}) - message(STATUS "Link libtcmalloc_minimal_debug for testing") - SET(MALLOC_LIBRARIES libtcmalloc_minimal_debug.a) - ELSE($ENV{DEBUG_LIBTCMALLOC}) - message(STATUS "Link libtcmalloc_minimal") - SET(MALLOC_LIBRARIES tcmalloc_minimal_internal) - ENDIF($ENV{DEBUG_LIBTCMALLOC}) -ENDIF($ENV{DISABLE_LIBTCMALLOC}) - -if (APPLE) - SET(RT_LIBRARIES "apple_rt") -else() - SET(RT_LIBRARIES "librt.a") -endif() - -set (GLIB_HINTS "/usr/local/opt/glib/lib") -find_library (GLIB_LIB libglib-2.0.a HINTS ${GLIB_HINTS}) -if (APPLE) - set (INTL_HINTS "/usr/local/opt/gettext/lib") - find_library (INTL_LIB libintl.a HINTS ${INTL_HINTS}) - find_library (CORE_FOUNDATION_LIB CoreFoundation) - find_library (CARBON_LIB Carbon) - set (GLIB_LIBS ${GLIB_LIB} ${INTL_LIB} libiconv.a ${CORE_FOUNDATION_LIB} ${CARBON_LIB}) +# When testing for memory leaks, disable libtcmalloc. +if (ENABLE_LIBTCMALLOC) + if (DEBUG_LIBTCMALLOC) + message (STATUS "Link libtcmalloc_minimal_debug for testing") + set (MALLOC_LIBRARIES libtcmalloc_minimal_debug.a) + else () + message (STATUS "Link libtcmalloc_minimal") + set (MALLOC_LIBRARIES tcmalloc_minimal_internal) + endif () else () - set (GLIB_LIBS ${GLIB_LIB}) + message (STATUS "Disabling libtcmalloc for valgrind better analysis") endif () +include (${ClickHouse_SOURCE_DIR}/cmake/find_glib.cmake) + target_link_libraries ( common pocoext @@ -99,6 +83,6 @@ target_link_libraries ( ${ICU_LIBS} ${RT_LIBRARIES}) -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (src/tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/libs/libcommon/src/get_revision_lib.sh b/libs/libcommon/src/get_revision_lib.sh index b4a270f652e..e1f683ca7cd 100644 --- a/libs/libcommon/src/get_revision_lib.sh +++ b/libs/libcommon/src/get_revision_lib.sh @@ -5,6 +5,10 @@ function tag_filter { # Get last revision number function get_revision { + BASEDIR=`dirname "$0"` + CURDIR=`pwd` + cd ${BASEDIR} git fetch --tags git tag | tag_filter | tail -1 | sed 's/^v1\.1\.\(.*\)-testing$/\1/' + cd ${CURDIR} } diff --git a/libs/libcommon/src/tests/CMakeLists.txt b/libs/libcommon/src/tests/CMakeLists.txt index d81fc17768c..6504a76d88d 100644 --- a/libs/libcommon/src/tests/CMakeLists.txt +++ b/libs/libcommon/src/tests/CMakeLists.txt @@ -8,18 +8,12 @@ add_executable (multi_version multi_version.cpp) add_executable (json_test json_test.cpp) add_executable (strong_typedef strong_typedef.cpp) -if (APPLE) - set (RT_LIBRARIES "") -else() - set (RT_LIBRARIES "rt") -endif() - target_link_libraries (date_lut_init common ${ICU_LIBS} dl) target_link_libraries (date_lut2 common ${ICU_LIBS} dl) target_link_libraries (date_lut3 common ${ICU_LIBS} dl) target_link_libraries (date_lut4 common ${ICU_LIBS} dl) -target_link_libraries (multi_version dbms ${BOOST_SYSTEM_LIB} ${RT_LIBRARIES}) -target_link_libraries (json_test dbms ${BOOST_SYSTEM_LIB} ${RT_LIBRARIES}) +target_link_libraries (multi_version dbms ${Boost_SYSTEM_LIBRARY} ${RT_LIBRARIES}) +target_link_libraries (json_test dbms ${Boost_SYSTEM_LIBRARY} ${RT_LIBRARIES}) target_link_libraries (strong_typedef common) add_check (json_test) diff --git a/libs/libmysqlxx/CMakeLists.txt b/libs/libmysqlxx/CMakeLists.txt index aad3816962d..6270092e617 100644 --- a/libs/libmysqlxx/CMakeLists.txt +++ b/libs/libmysqlxx/CMakeLists.txt @@ -26,20 +26,27 @@ add_library (mysqlxx add_dependencies (mysqlxx common) -set (MYSQL_HINTS "/usr/local/opt/mysql/lib") -find_library (MYSQLCLIENT_LIB libmysqlclient.a HINTS ${MYSQL_HINTS}) -set (OUR_MYSQLCLIENT_LIB ${CMAKE_CURRENT_BINARY_DIR}/libmysqlclient.a) +set (Z_HINTS "/usr/local/opt/zlib/lib") +if (USE_STATIC_LIBRARIES) + find_library (Z_LIB libz.a HINTS ${Z_HINTS}) +else () + find_library (Z_LIB z HINTS ${Z_HINTS}) +endif () -target_link_libraries (mysqlxx common ${OUR_MYSQLCLIENT_LIB} ${OPENSSL_LIBS} libz.a dl) +if (USE_STATIC_LIBRARIES) + set (MYSQLCLIENT_LIB ${CMAKE_CURRENT_BINARY_DIR}/libmysqlclient.a) + target_link_libraries (mysqlxx common ${MYSQLCLIENT_LIB} ${OPENSSL_LIBRARIES} ${Z_LIB} dl) + add_custom_command ( + OUTPUT ${MYSQLCLIENT_LIB} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/patch.sh ${STATIC_MYSQLCLIENT_LIB} ${MYSQLCLIENT_LIB} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Patching mysqlclient library.") + add_custom_target (our_mysql_client DEPENDS ${MYSQLCLIENT_LIB}) + add_dependencies (mysqlxx our_mysql_client) +endif () -add_custom_command( - OUTPUT ${OUR_MYSQLCLIENT_LIB} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/patch.sh ${MYSQLCLIENT_LIB} ${OUR_MYSQLCLIENT_LIB} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Patching mysqlclient library.") -add_custom_target(our_mysql_client DEPENDS ${OUR_MYSQLCLIENT_LIB}) -add_dependencies(mysqlxx our_mysql_client) +target_link_libraries (mysqlxx common ${MYSQLCLIENT_LIB} ${OPENSSL_LIBRARIES} ${Z_LIB} dl) -if (TESTS) +if (ENABLE_TESTS) add_subdirectory (src/tests) -endif (TESTS) +endif (ENABLE_TESTS) diff --git a/libs/libmysqlxx/include/mysqlxx/Exception.h b/libs/libmysqlxx/include/mysqlxx/Exception.h index ab21cca95b0..73891b20f17 100644 --- a/libs/libmysqlxx/include/mysqlxx/Exception.h +++ b/libs/libmysqlxx/include/mysqlxx/Exception.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include diff --git a/libs/libmysqlxx/include/mysqlxx/Pool.h b/libs/libmysqlxx/include/mysqlxx/Pool.h index 9ebcb1063b1..29eb7e60f46 100644 --- a/libs/libmysqlxx/include/mysqlxx/Pool.h +++ b/libs/libmysqlxx/include/mysqlxx/Pool.h @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include diff --git a/libs/libmysqlxx/include/mysqlxx/Types.h b/libs/libmysqlxx/include/mysqlxx/Types.h index b887c527ca8..05830963224 100644 --- a/libs/libmysqlxx/include/mysqlxx/Types.h +++ b/libs/libmysqlxx/include/mysqlxx/Types.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include diff --git a/libs/libmysqlxx/src/tests/CMakeLists.txt b/libs/libmysqlxx/src/tests/CMakeLists.txt index 827ef651868..693c89f2412 100644 --- a/libs/libmysqlxx/src/tests/CMakeLists.txt +++ b/libs/libmysqlxx/src/tests/CMakeLists.txt @@ -3,10 +3,6 @@ include_directories (${CMAKE_CURRENT_BINARY_DIR}) add_executable (mysqlxx_test mysqlxx_test.cpp) add_executable (failover failover.cpp) -if (APPLE) - SET(RT_LIBRARIES "") -else() - SET(RT_LIBRARIES "rt") -endif() target_link_libraries (mysqlxx_test mysqlxx dbms ${RT_LIBRARIES}) target_link_libraries (failover mysqlxx PocoUtil PocoFoundation) +target_link_rt_by_force (failover) diff --git a/libs/libzkutil/src/CMakeLists.txt b/libs/libzkutil/src/CMakeLists.txt index 76bc4c6ff73..3a3bef57e68 100644 --- a/libs/libzkutil/src/CMakeLists.txt +++ b/libs/libzkutil/src/CMakeLists.txt @@ -1,3 +1,3 @@ -IF(TESTS) +if (ENABLE_TESTS) add_subdirectory (tests) -ENDIF(TESTS) +endif (ENABLE_TESTS) diff --git a/release b/release index f9dfecfa182..58ead26da74 100755 --- a/release +++ b/release @@ -51,7 +51,7 @@ make_control "$CONTROL" "$DAEMONS" gen_changelog "$REVISION" "$CHDATE" "$AUTHOR" "$CHLOG" "$DAEMONS" # Build (only binary packages). -debuild -e DAEMONS="${DAEMONS}" -e DISABLE_MONGODB -e GLIBC_COMPATIBILITY -e CC -e CXX -b ${DEBUILD_NOSIGN_OPTIONS} +debuild -e DAEMONS="${DAEMONS}" -e ENABLE_MONGODB -e GLIBC_COMPATIBILITY -e CC -e CXX -b ${DEBUILD_NOSIGN_OPTIONS} if [[ $STANDALONE != 'yes' ]] then diff --git a/utils/compressor/CMakeLists.txt b/utils/compressor/CMakeLists.txt index 376e6e17293..79f64a480e0 100644 --- a/utils/compressor/CMakeLists.txt +++ b/utils/compressor/CMakeLists.txt @@ -1,8 +1,7 @@ add_executable (compressor main.cpp) -target_link_libraries (compressor dbms ${BOOST_PROGRAM_OPTIONS_LIB}) - -INSTALL(TARGETS compressor RUNTIME DESTINATION bin COMPONENT compressor) +target_link_libraries (compressor dbms ${Boost_PROGRAM_OPTIONS_LIBRARY}) +install (TARGETS compressor RUNTIME DESTINATION bin COMPONENT compressor) add_executable (zstd_test zstd_test.cpp) target_link_libraries (zstd_test zstd) diff --git a/utils/wikistat-loader/CMakeLists.txt b/utils/wikistat-loader/CMakeLists.txt index dc24f03a84a..96f5e5ba07f 100644 --- a/utils/wikistat-loader/CMakeLists.txt +++ b/utils/wikistat-loader/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable (wikistat-loader main.cpp ${SRCS}) -target_link_libraries (wikistat-loader dbms ${BOOST_PROGRAM_OPTIONS_LIB}) +target_link_libraries (wikistat-loader dbms ${Boost_PROGRAM_OPTIONS_LIBRARY}) diff --git a/utils/zookeeper-create-entry-to-download-part/CMakeLists.txt b/utils/zookeeper-create-entry-to-download-part/CMakeLists.txt index d95f3c59c05..39e6f843087 100644 --- a/utils/zookeeper-create-entry-to-download-part/CMakeLists.txt +++ b/utils/zookeeper-create-entry-to-download-part/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable (zookeeper-create-entry-to-download-part main.cpp ${SRCS}) -target_link_libraries (zookeeper-create-entry-to-download-part zkutil dbms ${BOOST_PROGRAM_OPTIONS_LIB}) +target_link_libraries (zookeeper-create-entry-to-download-part zkutil dbms ${Boost_PROGRAM_OPTIONS_LIBRARY}) diff --git a/utils/zookeeper-dump-tree/CMakeLists.txt b/utils/zookeeper-dump-tree/CMakeLists.txt index 46b9d1f7a3c..299f326fcf6 100644 --- a/utils/zookeeper-dump-tree/CMakeLists.txt +++ b/utils/zookeeper-dump-tree/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable (zookeeper-dump-tree main.cpp ${SRCS}) -target_link_libraries (zookeeper-dump-tree zkutil dbms ${BOOST_PROGRAM_OPTIONS_LIB}) +target_link_libraries (zookeeper-dump-tree zkutil dbms ${Boost_PROGRAM_OPTIONS_LIBRARY}) diff --git a/utils/zookeeper-remove-by-list/CMakeLists.txt b/utils/zookeeper-remove-by-list/CMakeLists.txt index 13203f6805d..084f06ae169 100644 --- a/utils/zookeeper-remove-by-list/CMakeLists.txt +++ b/utils/zookeeper-remove-by-list/CMakeLists.txt @@ -1,3 +1,2 @@ - add_executable (zookeeper-remove-by-list main.cpp ${SRCS}) -target_link_libraries (zookeeper-remove-by-list zkutil dbms ${BOOST_PROGRAM_OPTIONS_LIB}) +target_link_libraries (zookeeper-remove-by-list zkutil dbms ${Boost_PROGRAM_OPTIONS_LIBRARY})