Provide textual information for inefficient plans with notifications (#1343)
This commit is contained in:
@@ -4,7 +4,8 @@ include(GNUInstallDirs)
|
|||||||
|
|
||||||
include(ProcessorCount)
|
include(ProcessorCount)
|
||||||
ProcessorCount(NPROC)
|
ProcessorCount(NPROC)
|
||||||
if (NPROC EQUAL 0)
|
|
||||||
|
if(NPROC EQUAL 0)
|
||||||
set(NPROC 1)
|
set(NPROC 1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ find_package(Boost 1.78 REQUIRED)
|
|||||||
find_package(BZip2 1.0.6 REQUIRED)
|
find_package(BZip2 1.0.6 REQUIRED)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
set(GFLAGS_NOTHREADS OFF)
|
set(GFLAGS_NOTHREADS OFF)
|
||||||
|
|
||||||
# NOTE: config/generate.py depends on the gflags help XML format.
|
# NOTE: config/generate.py depends on the gflags help XML format.
|
||||||
find_package(gflags REQUIRED)
|
find_package(gflags REQUIRED)
|
||||||
find_package(fmt 8.0.1)
|
find_package(fmt 8.0.1)
|
||||||
@@ -23,24 +25,27 @@ set(LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
|||||||
function(import_header_library name include_dir)
|
function(import_header_library name include_dir)
|
||||||
add_library(${name} INTERFACE IMPORTED GLOBAL)
|
add_library(${name} INTERFACE IMPORTED GLOBAL)
|
||||||
set_property(TARGET ${name} PROPERTY
|
set_property(TARGET ${name} PROPERTY
|
||||||
INTERFACE_INCLUDE_DIRECTORIES ${include_dir})
|
INTERFACE_INCLUDE_DIRECTORIES ${include_dir})
|
||||||
string(TOUPPER ${name} _upper_name)
|
string(TOUPPER ${name} _upper_name)
|
||||||
set(${_upper_name}_INCLUDE_DIR ${include_dir} CACHE FILEPATH
|
set(${_upper_name}_INCLUDE_DIR ${include_dir} CACHE FILEPATH
|
||||||
"Path to ${name} include directory" FORCE)
|
"Path to ${name} include directory" FORCE)
|
||||||
mark_as_advanced(${_upper_name}_INCLUDE_DIR)
|
mark_as_advanced(${_upper_name}_INCLUDE_DIR)
|
||||||
add_library(lib::${name} ALIAS ${name})
|
add_library(lib::${name} ALIAS ${name})
|
||||||
endfunction(import_header_library)
|
endfunction(import_header_library)
|
||||||
|
|
||||||
function(import_library name type location include_dir)
|
function(import_library name type location include_dir)
|
||||||
add_library(${name} ${type} IMPORTED GLOBAL)
|
add_library(${name} ${type} IMPORTED GLOBAL)
|
||||||
if (${ARGN})
|
|
||||||
|
if(${ARGN})
|
||||||
# Optional argument is the name of the external project that we need to
|
# Optional argument is the name of the external project that we need to
|
||||||
# depend on.
|
# depend on.
|
||||||
add_dependencies(${name} ${ARGN0})
|
add_dependencies(${name} ${ARGN0})
|
||||||
else()
|
else()
|
||||||
add_dependencies(${name} ${name}-proj)
|
add_dependencies(${name} ${name}-proj)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_property(TARGET ${name} PROPERTY IMPORTED_LOCATION ${location})
|
set_property(TARGET ${name} PROPERTY IMPORTED_LOCATION ${location})
|
||||||
|
|
||||||
# We need to create the include directory first in order to be able to add it
|
# We need to create the include directory first in order to be able to add it
|
||||||
# as an include directory. The header files in the include directory will be
|
# as an include directory. The header files in the include directory will be
|
||||||
# generated later during the build process.
|
# generated later during the build process.
|
||||||
@@ -60,29 +65,34 @@ function(add_external_project name)
|
|||||||
set(options NO_C_COMPILER)
|
set(options NO_C_COMPILER)
|
||||||
set(one_value_kwargs SOURCE_DIR BUILD_IN_SOURCE)
|
set(one_value_kwargs SOURCE_DIR BUILD_IN_SOURCE)
|
||||||
set(multi_value_kwargs CMAKE_ARGS DEPENDS INSTALL_COMMAND BUILD_COMMAND
|
set(multi_value_kwargs CMAKE_ARGS DEPENDS INSTALL_COMMAND BUILD_COMMAND
|
||||||
CONFIGURE_COMMAND)
|
CONFIGURE_COMMAND)
|
||||||
cmake_parse_arguments(KW "${options}" "${one_value_kwargs}" "${multi_value_kwargs}" ${ARGN})
|
cmake_parse_arguments(KW "${options}" "${one_value_kwargs}" "${multi_value_kwargs}" ${ARGN})
|
||||||
set(source_dir ${CMAKE_CURRENT_SOURCE_DIR}/${name})
|
set(source_dir ${CMAKE_CURRENT_SOURCE_DIR}/${name})
|
||||||
if (KW_SOURCE_DIR)
|
|
||||||
|
if(KW_SOURCE_DIR)
|
||||||
set(source_dir ${KW_SOURCE_DIR})
|
set(source_dir ${KW_SOURCE_DIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(build_in_source 0)
|
set(build_in_source 0)
|
||||||
if (KW_BUILD_IN_SOURCE)
|
|
||||||
|
if(KW_BUILD_IN_SOURCE)
|
||||||
set(build_in_source ${KW_BUILD_IN_SOURCE})
|
set(build_in_source ${KW_BUILD_IN_SOURCE})
|
||||||
endif()
|
endif()
|
||||||
if (NOT KW_NO_C_COMPILER)
|
|
||||||
|
if(NOT KW_NO_C_COMPILER)
|
||||||
set(KW_CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} ${KW_CMAKE_ARGS})
|
set(KW_CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} ${KW_CMAKE_ARGS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
ExternalProject_Add(${name}-proj DEPENDS ${KW_DEPENDS}
|
ExternalProject_Add(${name}-proj DEPENDS ${KW_DEPENDS}
|
||||||
PREFIX ${source_dir} SOURCE_DIR ${source_dir}
|
PREFIX ${source_dir} SOURCE_DIR ${source_dir}
|
||||||
BUILD_IN_SOURCE ${build_in_source}
|
BUILD_IN_SOURCE ${build_in_source}
|
||||||
CONFIGURE_COMMAND ${KW_CONFIGURE_COMMAND}
|
CONFIGURE_COMMAND ${KW_CONFIGURE_COMMAND}
|
||||||
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release
|
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release
|
||||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||||
-DCMAKE_INSTALL_PREFIX=${source_dir}
|
-DCMAKE_INSTALL_PREFIX=${source_dir}
|
||||||
${KW_CMAKE_ARGS}
|
${KW_CMAKE_ARGS}
|
||||||
INSTALL_COMMAND ${KW_INSTALL_COMMAND}
|
INSTALL_COMMAND ${KW_INSTALL_COMMAND}
|
||||||
BUILD_COMMAND ${KW_BUILD_COMMAND})
|
BUILD_COMMAND ${KW_BUILD_COMMAND})
|
||||||
endfunction(add_external_project)
|
endfunction(add_external_project)
|
||||||
|
|
||||||
# Calls `add_external_project`, sets NAME_LIBRARY, NAME_INCLUDE_DIR variables
|
# Calls `add_external_project`, sets NAME_LIBRARY, NAME_INCLUDE_DIR variables
|
||||||
@@ -91,9 +101,9 @@ macro(import_external_library name type library_location include_dir)
|
|||||||
add_external_project(${name} ${ARGN})
|
add_external_project(${name} ${ARGN})
|
||||||
string(TOUPPER ${name} _upper_name)
|
string(TOUPPER ${name} _upper_name)
|
||||||
set(${_upper_name}_LIBRARY ${library_location} CACHE FILEPATH
|
set(${_upper_name}_LIBRARY ${library_location} CACHE FILEPATH
|
||||||
"Path to ${name} library" FORCE)
|
"Path to ${name} library" FORCE)
|
||||||
set(${_upper_name}_INCLUDE_DIR ${include_dir} CACHE FILEPATH
|
set(${_upper_name}_INCLUDE_DIR ${include_dir} CACHE FILEPATH
|
||||||
"Path to ${name} include directory" FORCE)
|
"Path to ${name} include directory" FORCE)
|
||||||
mark_as_advanced(${_upper_name}_LIBRARY ${_upper_name}_INCLUDE_DIR)
|
mark_as_advanced(${_upper_name}_LIBRARY ${_upper_name}_INCLUDE_DIR)
|
||||||
import_library(${name} ${type} ${${_upper_name}_LIBRARY} ${${_upper_name}_INCLUDE_DIR})
|
import_library(${name} ${type} ${${_upper_name}_LIBRARY} ${${_upper_name}_INCLUDE_DIR})
|
||||||
endmacro(import_external_library)
|
endmacro(import_external_library)
|
||||||
@@ -115,10 +125,10 @@ import_external_library(antlr4 STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/antlr4/runtime/Cpp/include/antlr4-runtime
|
${CMAKE_CURRENT_SOURCE_DIR}/antlr4/runtime/Cpp/include/antlr4-runtime
|
||||||
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/antlr4/runtime/Cpp
|
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/antlr4/runtime/Cpp
|
||||||
CMAKE_ARGS # http://stackoverflow.com/questions/37096062/get-a-basic-c-program-to-compile-using-clang-on-ubuntu-16/38385967#38385967
|
CMAKE_ARGS # http://stackoverflow.com/questions/37096062/get-a-basic-c-program-to-compile-using-clang-on-ubuntu-16/38385967#38385967
|
||||||
-DWITH_LIBCXX=OFF # because of debian bug
|
-DWITH_LIBCXX=OFF # because of debian bug
|
||||||
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||||
-DCMAKE_CXX_STANDARD=20
|
-DCMAKE_CXX_STANDARD=20
|
||||||
-DANTLR_BUILD_CPP_TESTS=OFF
|
-DANTLR_BUILD_CPP_TESTS=OFF
|
||||||
BUILD_COMMAND $(MAKE) antlr4_static
|
BUILD_COMMAND $(MAKE) antlr4_static
|
||||||
INSTALL_COMMAND $(MAKE) install)
|
INSTALL_COMMAND $(MAKE) install)
|
||||||
|
|
||||||
@@ -126,6 +136,7 @@ import_external_library(antlr4 STATIC
|
|||||||
import_external_library(benchmark STATIC
|
import_external_library(benchmark STATIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark/${CMAKE_INSTALL_LIBDIR}/libbenchmark.a
|
${CMAKE_CURRENT_SOURCE_DIR}/benchmark/${CMAKE_INSTALL_LIBDIR}/libbenchmark.a
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark/include
|
${CMAKE_CURRENT_SOURCE_DIR}/benchmark/include
|
||||||
|
|
||||||
# Skip testing. The tests don't compile with Clang 8.
|
# Skip testing. The tests don't compile with Clang 8.
|
||||||
CMAKE_ARGS -DBENCHMARK_ENABLE_TESTING=OFF)
|
CMAKE_ARGS -DBENCHMARK_ENABLE_TESTING=OFF)
|
||||||
|
|
||||||
@@ -141,15 +152,15 @@ add_subdirectory(rapidcheck EXCLUDE_FROM_ALL)
|
|||||||
# setup google test
|
# setup google test
|
||||||
add_external_project(gtest SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest)
|
add_external_project(gtest SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest)
|
||||||
set(GTEST_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest/include
|
set(GTEST_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest/include
|
||||||
CACHE PATH "Path to gtest and gmock include directory" FORCE)
|
CACHE PATH "Path to gtest and gmock include directory" FORCE)
|
||||||
set(GMOCK_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgmock.a
|
set(GMOCK_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgmock.a
|
||||||
CACHE FILEPATH "Path to gmock library" FORCE)
|
CACHE FILEPATH "Path to gmock library" FORCE)
|
||||||
set(GMOCK_MAIN_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgmock_main.a
|
set(GMOCK_MAIN_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgmock_main.a
|
||||||
CACHE FILEPATH "Path to gmock_main library" FORCE)
|
CACHE FILEPATH "Path to gmock_main library" FORCE)
|
||||||
set(GTEST_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgtest.a
|
set(GTEST_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgtest.a
|
||||||
CACHE FILEPATH "Path to gtest library" FORCE)
|
CACHE FILEPATH "Path to gtest library" FORCE)
|
||||||
set(GTEST_MAIN_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgtest_main.a
|
set(GTEST_MAIN_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/googletest/lib/libgtest_main.a
|
||||||
CACHE FILEPATH "Path to gtest_main library" FORCE)
|
CACHE FILEPATH "Path to gtest_main library" FORCE)
|
||||||
mark_as_advanced(GTEST_INCLUDE_DIR GMOCK_LIBRARY GMOCK_MAIN_LIBRARY GTEST_LIBRARY GTEST_MAIN_LIBRARY)
|
mark_as_advanced(GTEST_INCLUDE_DIR GMOCK_LIBRARY GMOCK_MAIN_LIBRARY GTEST_LIBRARY GTEST_MAIN_LIBRARY)
|
||||||
import_library(gtest STATIC ${GTEST_LIBRARY} ${GTEST_INCLUDE_DIR} gtest-proj)
|
import_library(gtest STATIC ${GTEST_LIBRARY} ${GTEST_INCLUDE_DIR} gtest-proj)
|
||||||
import_library(gtest_main STATIC ${GTEST_MAIN_LIBRARY} ${GTEST_INCLUDE_DIR} gtest-proj)
|
import_library(gtest_main STATIC ${GTEST_MAIN_LIBRARY} ${GTEST_INCLUDE_DIR} gtest-proj)
|
||||||
@@ -167,10 +178,10 @@ import_external_library(rocksdb STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/lib/librocksdb.a
|
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/lib/librocksdb.a
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/include
|
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/include
|
||||||
CMAKE_ARGS -DUSE_RTTI=ON
|
CMAKE_ARGS -DUSE_RTTI=ON
|
||||||
-DWITH_TESTS=OFF
|
-DWITH_TESTS=OFF
|
||||||
-DGFLAGS_NOTHREADS=OFF
|
-DGFLAGS_NOTHREADS=OFF
|
||||||
-DCMAKE_INSTALL_LIBDIR=lib
|
-DCMAKE_INSTALL_LIBDIR=lib
|
||||||
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||||
BUILD_COMMAND $(MAKE) rocksdb)
|
BUILD_COMMAND $(MAKE) rocksdb)
|
||||||
|
|
||||||
# Setup libbcrypt
|
# Setup libbcrypt
|
||||||
@@ -179,8 +190,8 @@ import_external_library(libbcrypt STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt
|
${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt
|
||||||
CONFIGURE_COMMAND sed s/-Wcast-align// -i ${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt/crypt_blowfish/Makefile
|
CONFIGURE_COMMAND sed s/-Wcast-align// -i ${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt/crypt_blowfish/Makefile
|
||||||
BUILD_COMMAND make -C ${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt
|
BUILD_COMMAND make -C ${CMAKE_CURRENT_SOURCE_DIR}/libbcrypt
|
||||||
CC=${CMAKE_C_COMPILER}
|
CC=${CMAKE_C_COMPILER}
|
||||||
CXX=${CMAKE_CXX_COMPILER}
|
CXX=${CMAKE_CXX_COMPILER}
|
||||||
INSTALL_COMMAND true)
|
INSTALL_COMMAND true)
|
||||||
|
|
||||||
# Setup mgclient
|
# Setup mgclient
|
||||||
@@ -188,16 +199,16 @@ import_external_library(mgclient STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/mgclient/lib/libmgclient.a
|
${CMAKE_CURRENT_SOURCE_DIR}/mgclient/lib/libmgclient.a
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/mgclient/include
|
${CMAKE_CURRENT_SOURCE_DIR}/mgclient/include
|
||||||
CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
||||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||||
-DBUILD_TESTING=OFF
|
-DBUILD_TESTING=OFF
|
||||||
-DBUILD_CPP_BINDINGS=ON)
|
-DBUILD_CPP_BINDINGS=ON)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
target_link_libraries(mgclient INTERFACE ${OPENSSL_LIBRARIES})
|
target_link_libraries(mgclient INTERFACE ${OPENSSL_LIBRARIES})
|
||||||
|
|
||||||
add_external_project(mgconsole
|
add_external_project(mgconsole
|
||||||
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mgconsole
|
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mgconsole
|
||||||
CMAKE_ARGS
|
CMAKE_ARGS
|
||||||
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}
|
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}
|
||||||
BUILD_COMMAND $(MAKE) mgconsole)
|
BUILD_COMMAND $(MAKE) mgconsole)
|
||||||
|
|
||||||
add_custom_target(mgconsole DEPENDS mgconsole-proj)
|
add_custom_target(mgconsole DEPENDS mgconsole-proj)
|
||||||
@@ -214,14 +225,15 @@ import_external_library(librdkafka STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/lib/librdkafka.a
|
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/lib/librdkafka.a
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/include
|
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/include
|
||||||
CMAKE_ARGS -DRDKAFKA_BUILD_STATIC=ON
|
CMAKE_ARGS -DRDKAFKA_BUILD_STATIC=ON
|
||||||
-DRDKAFKA_BUILD_EXAMPLES=OFF
|
-DRDKAFKA_BUILD_EXAMPLES=OFF
|
||||||
-DRDKAFKA_BUILD_TESTS=OFF
|
-DRDKAFKA_BUILD_TESTS=OFF
|
||||||
-DWITH_ZSTD=OFF
|
-DWITH_ZSTD=OFF
|
||||||
-DENABLE_LZ4_EXT=OFF
|
-DENABLE_LZ4_EXT=OFF
|
||||||
-DCMAKE_INSTALL_LIBDIR=lib
|
-DCMAKE_INSTALL_LIBDIR=lib
|
||||||
-DWITH_SSL=ON
|
-DWITH_SSL=ON
|
||||||
# If we want SASL, we need to install it on build machines
|
|
||||||
-DWITH_SASL=OFF)
|
# If we want SASL, we need to install it on build machines
|
||||||
|
-DWITH_SASL=OFF)
|
||||||
target_link_libraries(librdkafka INTERFACE ${OPENSSL_LIBRARIES} ZLIB::ZLIB)
|
target_link_libraries(librdkafka INTERFACE ${OPENSSL_LIBRARIES} ZLIB::ZLIB)
|
||||||
|
|
||||||
import_library(librdkafka++ STATIC
|
import_library(librdkafka++ STATIC
|
||||||
@@ -242,24 +254,24 @@ import_external_library(pulsar STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/pulsar/install/include
|
${CMAKE_CURRENT_SOURCE_DIR}/pulsar/install/include
|
||||||
BUILD_IN_SOURCE 1
|
BUILD_IN_SOURCE 1
|
||||||
CONFIGURE_COMMAND cmake pulsar-client-cpp
|
CONFIGURE_COMMAND cmake pulsar-client-cpp
|
||||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/pulsar/install
|
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/pulsar/install
|
||||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||||
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
||||||
-DBUILD_DYNAMIC_LIB=OFF
|
-DBUILD_DYNAMIC_LIB=OFF
|
||||||
-DBUILD_STATIC_LIB=ON
|
-DBUILD_STATIC_LIB=ON
|
||||||
-DBUILD_TESTS=OFF
|
-DBUILD_TESTS=OFF
|
||||||
-DLINK_STATIC=ON
|
-DLINK_STATIC=ON
|
||||||
-DPROTOC_PATH=${PROTOBUF_ROOT}/bin/protoc
|
-DPROTOC_PATH=${PROTOBUF_ROOT}/bin/protoc
|
||||||
-DBOOST_ROOT=${BOOST_ROOT}
|
-DBOOST_ROOT=${BOOST_ROOT}
|
||||||
-DCMAKE_PREFIX_PATH=${PROTOBUF_ROOT}
|
-DCMAKE_PREFIX_PATH=${PROTOBUF_ROOT}
|
||||||
-DProtobuf_INCLUDE_DIRS=${PROTOBUF_ROOT}/include
|
-DProtobuf_INCLUDE_DIRS=${PROTOBUF_ROOT}/include
|
||||||
-DBUILD_PYTHON_WRAPPER=OFF
|
-DBUILD_PYTHON_WRAPPER=OFF
|
||||||
-DBUILD_PERF_TOOLS=OFF
|
-DBUILD_PERF_TOOLS=OFF
|
||||||
-DUSE_LOG4CXX=OFF
|
-DUSE_LOG4CXX=OFF
|
||||||
BUILD_COMMAND $(MAKE) pulsarStaticWithDeps)
|
BUILD_COMMAND $(MAKE) pulsarStaticWithDeps)
|
||||||
add_dependencies(pulsar-proj protobuf)
|
add_dependencies(pulsar-proj protobuf)
|
||||||
|
|
||||||
if (${MG_ARCH} STREQUAL "ARM64")
|
if(${MG_ARCH} STREQUAL "ARM64")
|
||||||
set(MG_LIBRDTSC_CMAKE_ARGS -DLIBRDTSC_ARCH_x86=OFF -DLIBRDTSC_ARCH_ARM64=ON)
|
set(MG_LIBRDTSC_CMAKE_ARGS -DLIBRDTSC_ARCH_x86=OFF -DLIBRDTSC_ARCH_ARM64=ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -280,3 +292,5 @@ add_subdirectory(absl EXCLUDE_FROM_ALL)
|
|||||||
set_path_external_library(jemalloc STATIC
|
set_path_external_library(jemalloc STATIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/jemalloc/lib/libjemalloc.a
|
${CMAKE_CURRENT_SOURCE_DIR}/jemalloc/lib/libjemalloc.a
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/jemalloc/include/)
|
${CMAKE_CURRENT_SOURCE_DIR}/jemalloc/include/)
|
||||||
|
|
||||||
|
import_header_library(rangev3 ${CMAKE_CURRENT_SOURCE_DIR}/rangev3/include)
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ declare -A primary_urls=(
|
|||||||
["ctre"]="http://$local_cache_host/file/hanickadot/compile-time-regular-expressions/v3.7.2/single-header/ctre.hpp"
|
["ctre"]="http://$local_cache_host/file/hanickadot/compile-time-regular-expressions/v3.7.2/single-header/ctre.hpp"
|
||||||
["absl"]="https://$local_cache_host/git/abseil-cpp.git"
|
["absl"]="https://$local_cache_host/git/abseil-cpp.git"
|
||||||
["jemalloc"]="https://$local_cache_host/git/jemalloc.git"
|
["jemalloc"]="https://$local_cache_host/git/jemalloc.git"
|
||||||
|
["range-v3"]="https://$local_cache_host/git/ericniebler/range-v3.git"
|
||||||
)
|
)
|
||||||
|
|
||||||
# The goal of secondary urls is to have links to the "source of truth" of
|
# The goal of secondary urls is to have links to the "source of truth" of
|
||||||
@@ -153,6 +154,7 @@ declare -A secondary_urls=(
|
|||||||
["ctre"]="https://raw.githubusercontent.com/hanickadot/compile-time-regular-expressions/v3.7.2/single-header/ctre.hpp"
|
["ctre"]="https://raw.githubusercontent.com/hanickadot/compile-time-regular-expressions/v3.7.2/single-header/ctre.hpp"
|
||||||
["absl"]="https://github.com/abseil/abseil-cpp.git"
|
["absl"]="https://github.com/abseil/abseil-cpp.git"
|
||||||
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
|
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
|
||||||
|
["range-v3"]="https://github.com/ericniebler/range-v3.git"
|
||||||
)
|
)
|
||||||
|
|
||||||
# antlr
|
# antlr
|
||||||
@@ -255,7 +257,6 @@ cd ..
|
|||||||
absl_ref="20230125.3"
|
absl_ref="20230125.3"
|
||||||
repo_clone_try_double "${primary_urls[absl]}" "${secondary_urls[absl]}" "absl" "$absl_ref"
|
repo_clone_try_double "${primary_urls[absl]}" "${secondary_urls[absl]}" "absl" "$absl_ref"
|
||||||
|
|
||||||
|
|
||||||
# jemalloc ea6b3e973b477b8061e0076bb257dbd7f3faa756
|
# jemalloc ea6b3e973b477b8061e0076bb257dbd7f3faa756
|
||||||
JEMALLOC_COMMIT_VERSION="5.2.1"
|
JEMALLOC_COMMIT_VERSION="5.2.1"
|
||||||
repo_clone_try_double "${secondary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION"
|
repo_clone_try_double "${secondary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION"
|
||||||
@@ -272,3 +273,7 @@ MALLOC_CONF="retain:false,percpu_arena:percpu,oversize_threshold:0,muzzy_decay_m
|
|||||||
|
|
||||||
make -j$CPUS install
|
make -j$CPUS install
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
#range-v3 release-0.12.0
|
||||||
|
range_v3_ref="release-0.12.0"
|
||||||
|
repo_clone_try_double "${primary_urls[range-v3]}" "${secondary_urls[range-v3]}" "rangev3" "$range_v3_ref"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ set(mg_query_sources
|
|||||||
interpret/eval.cpp
|
interpret/eval.cpp
|
||||||
interpreter.cpp
|
interpreter.cpp
|
||||||
metadata.cpp
|
metadata.cpp
|
||||||
|
plan/hint_provider.cpp
|
||||||
plan/operator.cpp
|
plan/operator.cpp
|
||||||
plan/preprocess.cpp
|
plan/preprocess.cpp
|
||||||
plan/pretty_print.cpp
|
plan/pretty_print.cpp
|
||||||
@@ -44,22 +45,25 @@ set(mg_query_sources
|
|||||||
add_library(mg-query STATIC ${mg_query_sources})
|
add_library(mg-query STATIC ${mg_query_sources})
|
||||||
target_include_directories(mg-query PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
target_include_directories(mg-query PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||||
target_link_libraries(mg-query PUBLIC dl
|
target_link_libraries(mg-query PUBLIC dl
|
||||||
cppitertools
|
cppitertools
|
||||||
Python3::Python
|
rangev3
|
||||||
mg-integrations-pulsar
|
Python3::Python
|
||||||
mg-integrations-kafka
|
mg-integrations-pulsar
|
||||||
mg-storage-v2
|
mg-integrations-kafka
|
||||||
mg-license
|
mg-storage-v2
|
||||||
mg-utils
|
mg-license
|
||||||
mg-kvstore
|
mg-utils
|
||||||
mg-memory
|
mg-kvstore
|
||||||
mg::csv
|
mg-memory
|
||||||
mg-flags
|
mg::csv
|
||||||
mg-dbms
|
mg-flags
|
||||||
mg-events)
|
mg-dbms
|
||||||
|
mg-events)
|
||||||
|
|
||||||
if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
|
if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
|
||||||
set(Python3_ROOT_DIR "${MG_PYTHON_PATH}")
|
set(Python3_ROOT_DIR "${MG_PYTHON_PATH}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if("${MG_PYTHON_VERSION}" STREQUAL "")
|
if("${MG_PYTHON_VERSION}" STREQUAL "")
|
||||||
find_package(Python3 3.5 REQUIRED COMPONENTS Development)
|
find_package(Python3 3.5 REQUIRED COMPONENTS Development)
|
||||||
else()
|
else()
|
||||||
@@ -67,7 +71,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Generate Antlr openCypher parser
|
# Generate Antlr openCypher parser
|
||||||
|
|
||||||
set(opencypher_frontend ${CMAKE_CURRENT_SOURCE_DIR}/frontend/opencypher)
|
set(opencypher_frontend ${CMAKE_CURRENT_SOURCE_DIR}/frontend/opencypher)
|
||||||
set(opencypher_generated ${opencypher_frontend}/generated)
|
set(opencypher_generated ${opencypher_frontend}/generated)
|
||||||
set(opencypher_lexer_grammar ${opencypher_frontend}/grammar/MemgraphCypherLexer.g4)
|
set(opencypher_lexer_grammar ${opencypher_frontend}/grammar/MemgraphCypherLexer.g4)
|
||||||
@@ -90,15 +93,15 @@ add_custom_command(
|
|||||||
OUTPUT ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include}
|
OUTPUT ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include}
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${opencypher_generated}
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${opencypher_generated}
|
||||||
COMMAND
|
COMMAND
|
||||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.10.1-complete.jar
|
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.10.1-complete.jar
|
||||||
-Dlanguage=Cpp -visitor -package antlropencypher
|
-Dlanguage=Cpp -visitor -package antlropencypher
|
||||||
-o ${opencypher_generated}
|
-o ${opencypher_generated}
|
||||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||||
DEPENDS
|
DEPENDS
|
||||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||||
${opencypher_frontend}/grammar/CypherLexer.g4
|
${opencypher_frontend}/grammar/CypherLexer.g4
|
||||||
${opencypher_frontend}/grammar/Cypher.g4)
|
${opencypher_frontend}/grammar/Cypher.g4)
|
||||||
|
|
||||||
add_custom_target(generate_opencypher_parser
|
add_custom_target(generate_opencypher_parser
|
||||||
DEPENDS ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include})
|
DEPENDS ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include})
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
#include "query/interpret/eval.hpp"
|
#include "query/interpret/eval.hpp"
|
||||||
#include "query/interpret/frame.hpp"
|
#include "query/interpret/frame.hpp"
|
||||||
#include "query/metadata.hpp"
|
#include "query/metadata.hpp"
|
||||||
|
#include "query/plan/hint_provider.hpp"
|
||||||
#include "query/plan/planner.hpp"
|
#include "query/plan/planner.hpp"
|
||||||
#include "query/plan/profile.hpp"
|
#include "query/plan/profile.hpp"
|
||||||
#include "query/plan/vertex_count_cache.hpp"
|
#include "query/plan/vertex_count_cache.hpp"
|
||||||
@@ -1610,6 +1611,11 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
|||||||
auto plan = CypherQueryToPlan(parsed_query.stripped_query.hash(), std::move(parsed_query.ast_storage), cypher_query,
|
auto plan = CypherQueryToPlan(parsed_query.stripped_query.hash(), std::move(parsed_query.ast_storage), cypher_query,
|
||||||
parsed_query.parameters, plan_cache, dba);
|
parsed_query.parameters, plan_cache, dba);
|
||||||
|
|
||||||
|
auto hints = plan::ProvidePlanHints(&plan->plan(), plan->symbol_table());
|
||||||
|
for (const auto &hint : hints) {
|
||||||
|
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::PLAN_HINTING, hint);
|
||||||
|
}
|
||||||
|
|
||||||
TryCaching(plan->ast_storage(), frame_change_collector);
|
TryCaching(plan->ast_storage(), frame_change_collector);
|
||||||
summary->insert_or_assign("cost_estimate", plan->cost());
|
summary->insert_or_assign("cost_estimate", plan->cost());
|
||||||
auto rw_type_checker = plan::ReadWriteTypeChecker();
|
auto rw_type_checker = plan::ReadWriteTypeChecker();
|
||||||
@@ -1646,7 +1652,8 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
|
PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
|
||||||
InterpreterContext *interpreter_context, CurrentDB ¤t_db) {
|
std::vector<Notification> *notifications, InterpreterContext *interpreter_context,
|
||||||
|
CurrentDB ¤t_db) {
|
||||||
const std::string kExplainQueryStart = "explain ";
|
const std::string kExplainQueryStart = "explain ";
|
||||||
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kExplainQueryStart),
|
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kExplainQueryStart),
|
||||||
"Expected stripped query to start with '{}'", kExplainQueryStart);
|
"Expected stripped query to start with '{}'", kExplainQueryStart);
|
||||||
@@ -1673,6 +1680,11 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
|
|||||||
CypherQueryToPlan(parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage),
|
CypherQueryToPlan(parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage),
|
||||||
cypher_query, parsed_inner_query.parameters, plan_cache, dba);
|
cypher_query, parsed_inner_query.parameters, plan_cache, dba);
|
||||||
|
|
||||||
|
auto hints = plan::ProvidePlanHints(&cypher_query_plan->plan(), cypher_query_plan->symbol_table());
|
||||||
|
for (const auto &hint : hints) {
|
||||||
|
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::PLAN_HINTING, hint);
|
||||||
|
}
|
||||||
|
|
||||||
std::stringstream printed_plan;
|
std::stringstream printed_plan;
|
||||||
plan::PrettyPrint(*dba, &cypher_query_plan->plan(), &printed_plan);
|
plan::PrettyPrint(*dba, &cypher_query_plan->plan(), &printed_plan);
|
||||||
|
|
||||||
@@ -1696,9 +1708,9 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
|
|||||||
}
|
}
|
||||||
|
|
||||||
PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
std::map<std::string, TypedValue> *summary, std::vector<Notification> *notifications,
|
||||||
CurrentDB ¤t_db, utils::MemoryResource *execution_memory,
|
InterpreterContext *interpreter_context, CurrentDB ¤t_db,
|
||||||
std::optional<std::string> const &username,
|
utils::MemoryResource *execution_memory, std::optional<std::string> const &username,
|
||||||
std::atomic<TransactionStatus> *transaction_status,
|
std::atomic<TransactionStatus> *transaction_status,
|
||||||
std::shared_ptr<utils::AsyncTimer> tx_timer,
|
std::shared_ptr<utils::AsyncTimer> tx_timer,
|
||||||
FrameChangeCollector *frame_change_collector) {
|
FrameChangeCollector *frame_change_collector) {
|
||||||
@@ -1766,6 +1778,12 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
|
|||||||
CypherQueryToPlan(parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage),
|
CypherQueryToPlan(parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage),
|
||||||
cypher_query, parsed_inner_query.parameters, plan_cache, dba);
|
cypher_query, parsed_inner_query.parameters, plan_cache, dba);
|
||||||
TryCaching(cypher_query_plan->ast_storage(), frame_change_collector);
|
TryCaching(cypher_query_plan->ast_storage(), frame_change_collector);
|
||||||
|
|
||||||
|
auto hints = plan::ProvidePlanHints(&cypher_query_plan->plan(), cypher_query_plan->symbol_table());
|
||||||
|
for (const auto &hint : hints) {
|
||||||
|
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::PLAN_HINTING, hint);
|
||||||
|
}
|
||||||
|
|
||||||
auto rw_type_checker = plan::ReadWriteTypeChecker();
|
auto rw_type_checker = plan::ReadWriteTypeChecker();
|
||||||
|
|
||||||
rw_type_checker.InferRWType(const_cast<plan::LogicalOperator &>(cypher_query_plan->plan()));
|
rw_type_checker.InferRWType(const_cast<plan::LogicalOperator &>(cypher_query_plan->plan()));
|
||||||
@@ -3732,13 +3750,13 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
|||||||
current_db_, memory_resource, &query_execution->notifications, username_,
|
current_db_, memory_resource, &query_execution->notifications, username_,
|
||||||
&transaction_status_, current_timeout_timer_, &*frame_change_collector_);
|
&transaction_status_, current_timeout_timer_, &*frame_change_collector_);
|
||||||
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
|
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
|
||||||
prepared_query =
|
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary,
|
||||||
PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_, current_db_);
|
&query_execution->notifications, interpreter_context_, current_db_);
|
||||||
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
|
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
|
||||||
prepared_query =
|
prepared_query = PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||||
PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
&query_execution->notifications, interpreter_context_, current_db_,
|
||||||
interpreter_context_, current_db_, &query_execution->execution_memory_with_exception,
|
&query_execution->execution_memory_with_exception, username_,
|
||||||
username_, &transaction_status_, current_timeout_timer_, &*frame_change_collector_);
|
&transaction_status_, current_timeout_timer_, &*frame_change_collector_);
|
||||||
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
|
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
|
||||||
prepared_query = PrepareDumpQuery(std::move(parsed_query), current_db_);
|
prepared_query = PrepareDumpQuery(std::move(parsed_query), current_db_);
|
||||||
} else if (utils::Downcast<IndexQuery>(parsed_query.query)) {
|
} else if (utils::Downcast<IndexQuery>(parsed_query.query)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -62,6 +62,8 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
|||||||
return "IndexDoesNotExist"sv;
|
return "IndexDoesNotExist"sv;
|
||||||
case NotificationCode::NONEXISTENT_CONSTRAINT:
|
case NotificationCode::NONEXISTENT_CONSTRAINT:
|
||||||
return "ConstraintDoesNotExist"sv;
|
return "ConstraintDoesNotExist"sv;
|
||||||
|
case NotificationCode::PLAN_HINTING:
|
||||||
|
return "PlanHinting"sv;
|
||||||
case NotificationCode::REGISTER_REPLICA:
|
case NotificationCode::REGISTER_REPLICA:
|
||||||
return "RegisterReplica"sv;
|
return "RegisterReplica"sv;
|
||||||
case NotificationCode::REPLICA_PORT_WARNING:
|
case NotificationCode::REPLICA_PORT_WARNING:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -39,6 +39,7 @@ enum class NotificationCode : uint8_t {
|
|||||||
LOAD_CSV_TIP,
|
LOAD_CSV_TIP,
|
||||||
NONEXISTENT_INDEX,
|
NONEXISTENT_INDEX,
|
||||||
NONEXISTENT_CONSTRAINT,
|
NONEXISTENT_CONSTRAINT,
|
||||||
|
PLAN_HINTING,
|
||||||
REPLICA_PORT_WARNING,
|
REPLICA_PORT_WARNING,
|
||||||
REGISTER_REPLICA,
|
REGISTER_REPLICA,
|
||||||
SET_REPLICA,
|
SET_REPLICA,
|
||||||
|
|||||||
24
src/query/plan/hint_provider.cpp
Normal file
24
src/query/plan/hint_provider.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
#include "hint_provider.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::query::plan {
|
||||||
|
|
||||||
|
std::vector<std::string> ProvidePlanHints(const LogicalOperator *plan_root, const SymbolTable &symbol_table) {
|
||||||
|
PlanHintsProvider plan_hinter(symbol_table);
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
|
||||||
|
const_cast<LogicalOperator *>(plan_root)->Accept(plan_hinter);
|
||||||
|
|
||||||
|
return plan_hinter.hints();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace memgraph::query::plan
|
||||||
255
src/query/plan/hint_provider.hpp
Normal file
255
src/query/plan/hint_provider.hpp
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
/// @file
|
||||||
|
/// This file provides textual information about possible inefficiencies in the query planner.
|
||||||
|
/// An inefficiency is for example having a sequential scan with filtering, without the usage of indices.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include <range/v3/view.hpp>
|
||||||
|
|
||||||
|
#include "query/plan/operator.hpp"
|
||||||
|
#include "query/plan/preprocess.hpp"
|
||||||
|
#include "utils/logging.hpp"
|
||||||
|
#include "utils/string.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::query::plan {
|
||||||
|
|
||||||
|
std::vector<std::string> ProvidePlanHints(const LogicalOperator *plan_root, const SymbolTable &symbol_table);
|
||||||
|
|
||||||
|
class PlanHintsProvider final : public HierarchicalLogicalOperatorVisitor {
|
||||||
|
public:
|
||||||
|
explicit PlanHintsProvider(const SymbolTable &symbol_table) : symbol_table_(symbol_table) {}
|
||||||
|
|
||||||
|
std::vector<std::string> &hints() { return hints_; }
|
||||||
|
|
||||||
|
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||||
|
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||||
|
using HierarchicalLogicalOperatorVisitor::Visit;
|
||||||
|
|
||||||
|
bool Visit(Once & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Filter & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PostVisit(Filter &op) override {
|
||||||
|
HintIndexUsage(op);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreVisit(ScanAll & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PostVisit(ScanAll & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Expand & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PostVisit(Expand & /*expand*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(ExpandVariable & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PostVisit(ExpandVariable & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Merge &op) override {
|
||||||
|
op.input()->Accept(*this);
|
||||||
|
op.merge_match_->Accept(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PostVisit(Merge & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Optional &op) override {
|
||||||
|
op.input()->Accept(*this);
|
||||||
|
op.optional_->Accept(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PostVisit(Optional & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Cartesian &op) override {
|
||||||
|
op.left_op_->Accept(*this);
|
||||||
|
op.right_op_->Accept(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PostVisit(Cartesian & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Union &op) override {
|
||||||
|
op.left_op_->Accept(*this);
|
||||||
|
op.right_op_->Accept(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PostVisit(Union & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(CreateNode & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(CreateNode & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(CreateExpand & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(CreateExpand & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(ScanAllByLabel & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(ScanAllByLabel & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(ScanAllByLabelPropertyRange & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(ScanAllByLabelPropertyRange & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(ScanAllByLabelPropertyValue & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(ScanAllByLabelPropertyValue & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(ScanAllByLabelProperty & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(ScanAllByLabelProperty & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(ScanAllById & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(ScanAllById & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(ConstructNamedPath & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(ConstructNamedPath & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Produce & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Produce & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(EmptyResult & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(EmptyResult & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Delete & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Delete & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(SetProperty & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(SetProperty & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(SetProperties & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(SetProperties & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(SetLabels & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(SetLabels & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(RemoveProperty & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(RemoveProperty & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(RemoveLabels & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(RemoveLabels & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(EdgeUniquenessFilter & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(EdgeUniquenessFilter & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Accumulate & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Accumulate & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Aggregate & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Aggregate & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Skip & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Skip & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Limit & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Limit & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(OrderBy & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(OrderBy & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Unwind & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Unwind & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Distinct & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(Distinct & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(CallProcedure & /*unused*/) override { return true; }
|
||||||
|
bool PostVisit(CallProcedure & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Foreach &op) override {
|
||||||
|
op.input()->Accept(*this);
|
||||||
|
op.update_clauses_->Accept(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PostVisit(Foreach & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(EvaluatePatternFilter & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PostVisit(EvaluatePatternFilter & /*op*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(Apply &op) override {
|
||||||
|
op.input()->Accept(*this);
|
||||||
|
op.subquery_->Accept(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PostVisit(Apply & /*op*/) override { return true; }
|
||||||
|
|
||||||
|
bool PreVisit(LoadCsv & /*unused*/) override { return true; }
|
||||||
|
|
||||||
|
bool PostVisit(LoadCsv & /*op*/) override { return true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const SymbolTable &symbol_table_;
|
||||||
|
std::vector<std::string> hints_;
|
||||||
|
|
||||||
|
bool DefaultPreVisit() override { LOG_FATAL("Operator not implemented for providing plan hints!"); }
|
||||||
|
|
||||||
|
void HintIndexUsage(Filter &op) {
|
||||||
|
if (auto *maybe_scan_operator = dynamic_cast<ScanAll *>(op.input().get()); !maybe_scan_operator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const scan_symbol = dynamic_cast<ScanAll *>(op.input().get())->output_symbol_;
|
||||||
|
auto const scan_type = op.input()->GetTypeInfo();
|
||||||
|
|
||||||
|
Filters filters;
|
||||||
|
filters.CollectFilterExpression(op.expression_, symbol_table_);
|
||||||
|
const std::string filtered_labels = ExtractAndJoin(filters.FilteredLabels(scan_symbol),
|
||||||
|
[](const auto &item) { return fmt::format(":{0}", item.name); });
|
||||||
|
const std::string filtered_properties =
|
||||||
|
ExtractAndJoin(filters.FilteredProperties(scan_symbol), [](const auto &item) { return item.name; });
|
||||||
|
|
||||||
|
if (filtered_labels.empty() && filtered_properties.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scan_type == ScanAll::kType) {
|
||||||
|
if (!filtered_labels.empty() && !filtered_properties.empty()) {
|
||||||
|
hints_.push_back(
|
||||||
|
fmt::format("Sequential scan will be used on symbol `{0}` although there is a filter on labels {1} and "
|
||||||
|
"properties {2}. Consider "
|
||||||
|
"creating a label-property index.",
|
||||||
|
scan_symbol.name(), filtered_labels, filtered_properties));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filtered_labels.empty()) {
|
||||||
|
hints_.push_back(fmt::format(
|
||||||
|
"Sequential scan will be used on symbol `{0}` although there is a filter on labels {1}. Consider "
|
||||||
|
"creating a label index.",
|
||||||
|
scan_symbol.name(), filtered_labels));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scan_type == ScanAllByLabel::kType && !filtered_properties.empty()) {
|
||||||
|
hints_.push_back(fmt::format(
|
||||||
|
"Label index will be used on symbol `{0}` although there is also a filter on properties {1}. Consider "
|
||||||
|
"creating a label-property index.",
|
||||||
|
scan_symbol.name(), filtered_properties));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ExtractAndJoin(auto &&collection, auto &&projection) {
|
||||||
|
auto elements = collection | ranges::views::transform(projection);
|
||||||
|
return boost::algorithm::join(elements, ", ");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::query::plan
|
||||||
@@ -345,6 +345,17 @@ class Filters final {
|
|||||||
return labels;
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto FilteredProperties(const Symbol &symbol) const -> std::unordered_set<PropertyIx> {
|
||||||
|
std::unordered_set<PropertyIx> properties;
|
||||||
|
|
||||||
|
for (const auto &filter : all_filters_) {
|
||||||
|
if (filter.type == FilterInfo::Type::Property && filter.property_filter->symbol_ == symbol) {
|
||||||
|
properties.insert(filter.property_filter->property_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove a filter; may invalidate iterators.
|
/// Remove a filter; may invalidate iterators.
|
||||||
/// Removal is done by comparing only the expression, so that multiple
|
/// Removal is done by comparing only the expression, so that multiple
|
||||||
/// FilterInfo objects using the same original expression are removed.
|
/// FilterInfo objects using the same original expression are removed.
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ add_unit_test(query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/communicati
|
|||||||
target_link_libraries(${test_prefix}query_plan_edge_cases mg-communication mg-query)
|
target_link_libraries(${test_prefix}query_plan_edge_cases mg-communication mg-query)
|
||||||
|
|
||||||
add_unit_test(query_plan_match_filter_return.cpp)
|
add_unit_test(query_plan_match_filter_return.cpp)
|
||||||
target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query mg-query mg-glue)
|
target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query mg-glue)
|
||||||
|
|
||||||
add_unit_test(query_plan_operator_to_string.cpp)
|
add_unit_test(query_plan_operator_to_string.cpp)
|
||||||
target_link_libraries(${test_prefix}query_plan_operator_to_string mg-query)
|
target_link_libraries(${test_prefix}query_plan_operator_to_string mg-query)
|
||||||
@@ -422,3 +422,5 @@ add_unit_test(distributed_lamport_clock.cpp)
|
|||||||
target_link_libraries(${test_prefix}distributed_lamport_clock mg-distributed)
|
target_link_libraries(${test_prefix}distributed_lamport_clock mg-distributed)
|
||||||
target_include_directories(${test_prefix}distributed_lamport_clock PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
target_include_directories(${test_prefix}distributed_lamport_clock PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
add_unit_test(query_hint_provider.cpp)
|
||||||
|
target_link_libraries(${test_prefix}query_hint_provider mg-query mg-glue)
|
||||||
|
|||||||
166
tests/unit/query_hint_provider.cpp
Normal file
166
tests/unit/query_hint_provider.cpp
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "query_plan_common.hpp"
|
||||||
|
|
||||||
|
#include "query/db_accessor.hpp"
|
||||||
|
#include "query/frontend/semantic/symbol_table.hpp"
|
||||||
|
#include "query/plan/hint_provider.hpp"
|
||||||
|
#include "storage/v2/inmemory/storage.hpp"
|
||||||
|
|
||||||
|
using namespace memgraph::query;
|
||||||
|
using namespace memgraph::query::plan;
|
||||||
|
using namespace memgraph::storage;
|
||||||
|
|
||||||
|
class HintProviderSuite : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Storage> db = std::make_unique<InMemoryStorage>();
|
||||||
|
std::optional<std::unique_ptr<Storage::Accessor>> storage_dba;
|
||||||
|
std::optional<memgraph::query::DbAccessor> dba;
|
||||||
|
LabelId label = db->NameToLabel("label");
|
||||||
|
PropertyId property = db->NameToProperty("property");
|
||||||
|
PropertyId other_property = db->NameToProperty("other_property");
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<LogicalOperator>> pattern_filters_{};
|
||||||
|
|
||||||
|
AstStorage storage;
|
||||||
|
SymbolTable symbol_table;
|
||||||
|
View view = View::OLD;
|
||||||
|
int symbol_count = 0;
|
||||||
|
|
||||||
|
void SetUp() {
|
||||||
|
storage_dba.emplace(db->Access());
|
||||||
|
dba.emplace(storage_dba->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol NextSymbol() { return symbol_table.CreateSymbol("Symbol" + std::to_string(symbol_count++), true); }
|
||||||
|
|
||||||
|
void VerifyHintMessages(LogicalOperator *plan, const std::vector<std::string> &expected_messages) {
|
||||||
|
auto messages = ProvidePlanHints(plan, symbol_table);
|
||||||
|
|
||||||
|
ASSERT_EQ(expected_messages.size(), messages.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < messages.size(); i++) {
|
||||||
|
const auto &expected_message = expected_messages[i];
|
||||||
|
const auto &actual_message = messages[i];
|
||||||
|
|
||||||
|
ASSERT_EQ(expected_message, actual_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<LabelIx> GetLabelIx(std::vector<LabelId> labels) {
|
||||||
|
std::vector<LabelIx> label_ixs{};
|
||||||
|
for (const auto &label : labels) {
|
||||||
|
label_ixs.emplace_back(storage.GetLabelIx(db->LabelToName(label)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return label_ixs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(HintProviderSuite, HintWhenFilteringByLabel) {
|
||||||
|
auto scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||||
|
auto *filter_expr = storage.template Create<LabelsTest>(scan_all.node_->identifier_, GetLabelIx({label}));
|
||||||
|
|
||||||
|
auto filter = std::make_shared<Filter>(scan_all.op_, pattern_filters_, filter_expr);
|
||||||
|
|
||||||
|
const std::vector<std::string> expected_messages{
|
||||||
|
"Sequential scan will be used on symbol `n` although there is a filter on labels :label. Consider "
|
||||||
|
"creating a label index."};
|
||||||
|
|
||||||
|
VerifyHintMessages(filter.get(), expected_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(HintProviderSuite, DontHintWhenLabelOperatorPresent) {
|
||||||
|
auto scan_all_by_label = MakeScanAllByLabel(storage, symbol_table, "n", label);
|
||||||
|
auto produce = MakeProduce(scan_all_by_label.op_, nullptr);
|
||||||
|
|
||||||
|
const std::vector<std::string> expected_messages{};
|
||||||
|
|
||||||
|
VerifyHintMessages(produce.get(), expected_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(HintProviderSuite, HintWhenFilteringByLabelAndProperty) {
|
||||||
|
auto scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||||
|
auto *filter_expr = storage.template Create<AndOperator>(
|
||||||
|
storage.template Create<LabelsTest>(scan_all.node_->identifier_, GetLabelIx({label})),
|
||||||
|
EQ(PROPERTY_LOOKUP(*dba, scan_all.node_->identifier_, property), LITERAL(42)));
|
||||||
|
|
||||||
|
auto filter = std::make_shared<Filter>(scan_all.op_, pattern_filters_, filter_expr);
|
||||||
|
|
||||||
|
const std::vector<std::string> expected_messages{
|
||||||
|
"Sequential scan will be used on symbol `n` although there is a filter on labels :label and properties property. "
|
||||||
|
"Consider "
|
||||||
|
"creating a label-property index."};
|
||||||
|
|
||||||
|
VerifyHintMessages(filter.get(), expected_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(HintProviderSuite, DontHintWhenLabelPropertyOperatorPresent) {
|
||||||
|
auto scan_all_by_label_prop_value = MakeScanAllByLabelPropertyValue(
|
||||||
|
storage, symbol_table, "n", label, property, "property", storage.template Create<Identifier>("n"));
|
||||||
|
auto produce = MakeProduce(scan_all_by_label_prop_value.op_, nullptr);
|
||||||
|
|
||||||
|
const std::vector<std::string> expected_messages{};
|
||||||
|
|
||||||
|
VerifyHintMessages(produce.get(), expected_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(HintProviderSuite, DontHintWhenLabelPropertyOperatorPresentAndAdditionalPropertyFilterPresent) {
|
||||||
|
auto scan_all_by_label_prop_value = MakeScanAllByLabelPropertyValue(
|
||||||
|
storage, symbol_table, "n", label, property, "property", storage.template Create<Identifier>("n"));
|
||||||
|
|
||||||
|
auto *filter_expr =
|
||||||
|
EQ(PROPERTY_LOOKUP(*dba, scan_all_by_label_prop_value.node_->identifier_, other_property), LITERAL(42));
|
||||||
|
auto filter = std::make_shared<Filter>(scan_all_by_label_prop_value.op_, pattern_filters_, filter_expr);
|
||||||
|
const std::vector<std::string> expected_messages{};
|
||||||
|
|
||||||
|
VerifyHintMessages(filter.get(), expected_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(HintProviderSuite, HintWhenLabelOperatorPresentButFilteringAlsoByProperty) {
|
||||||
|
auto scan_all_by_label = MakeScanAllByLabel(storage, symbol_table, "n", label);
|
||||||
|
auto *filter_expr = EQ(PROPERTY_LOOKUP(*dba, scan_all_by_label.node_->identifier_, property), LITERAL(42));
|
||||||
|
|
||||||
|
auto filter = std::make_shared<Filter>(scan_all_by_label.op_, pattern_filters_, filter_expr);
|
||||||
|
|
||||||
|
const std::vector<std::string> expected_messages{
|
||||||
|
"Label index will be used on symbol `n` although there is also a filter on properties property. "
|
||||||
|
"Consider "
|
||||||
|
"creating a label-property index."};
|
||||||
|
|
||||||
|
VerifyHintMessages(filter.get(), expected_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(HintProviderSuite, DoubleHintWhenCartesianInFilters) {
|
||||||
|
auto first_scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||||
|
auto *first_filter_expr = storage.template Create<LabelsTest>(first_scan_all.node_->identifier_, GetLabelIx({label}));
|
||||||
|
auto first_filter = std::make_shared<Filter>(first_scan_all.op_, pattern_filters_, first_filter_expr);
|
||||||
|
|
||||||
|
auto second_scan_all = MakeScanAll(storage, symbol_table, "m");
|
||||||
|
auto *second_filter_expr =
|
||||||
|
storage.template Create<LabelsTest>(second_scan_all.node_->identifier_, GetLabelIx({label}));
|
||||||
|
auto second_filter = std::make_shared<Filter>(second_scan_all.op_, pattern_filters_, second_filter_expr);
|
||||||
|
|
||||||
|
const std::vector<Symbol> empty_symbols{};
|
||||||
|
|
||||||
|
auto cartesian = std::make_shared<Cartesian>(first_filter, empty_symbols, second_filter, empty_symbols);
|
||||||
|
|
||||||
|
const std::vector<std::string> expected_messages{
|
||||||
|
"Sequential scan will be used on symbol `n` although there is a filter on labels :label. Consider "
|
||||||
|
"creating a label index.",
|
||||||
|
"Sequential scan will be used on symbol `m` although there is a filter on labels :label. Consider "
|
||||||
|
"creating a label index."};
|
||||||
|
|
||||||
|
VerifyHintMessages(cartesian.get(), expected_messages);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user