Compare commits

...

122 Commits

Author SHA1 Message Date
Marko Budiselic
979ee3bca6 Add the toolchain-v4 zstd patch script 2024-02-16 18:51:29 +00:00
Marko Budiselic
e8beee10b2 Set the right mgcxx compiler 2024-02-16 17:37:49 +00:00
Marko Budiselic
4787608c43 Fix zstd under text search 2024-02-16 14:00:42 +00:00
Ante Pušić
147c36b07c Add regex search (rough) 2024-02-12 20:15:19 +01:00
Ante Pušić
03d39bc189 Fix mgcxx branch 2024-02-10 11:42:19 +01:00
Ante Pušić
734ccabf3c
Merge branch 'master' into text-search-integration-poc 2024-02-10 11:10:38 +01:00
Ante Pušić
73509269ce Refactor TextIndex 2024-02-10 11:09:10 +01:00
Ante Pušić
ca27f3c21f Smooth out index errors 2024-02-09 22:56:58 +01:00
Ante Pušić
3bc570ead8 Decouple text indices from other index types 2024-02-09 22:31:19 +01:00
Ante Pušić
3114c69627 Switch to dedicated text search exceptions 2024-02-09 21:48:53 +01:00
Ante Pušić
34c7a8b098 Remove boolean update_text_index from method signatures 2024-02-09 21:22:53 +01:00
Ante Pušić
fc0a71f3d5 Implement review suggestions (on smarter naming, types and calls) 2024-02-09 20:55:25 +01:00
Ante Pušić
3e20423050
Merge branch 'master' into text-search-integration-poc 2024-02-09 18:03:59 +01:00
Ante Pušić
35532254a9 Refactor types 2024-02-09 18:03:20 +01:00
Ante Pušić
971dd5c4cc Build without duplicate symbols 2024-02-09 03:28:39 +01:00
Ante Pušić
2731c57a81
Merge branch 'master' into text-search-integration-poc 2024-02-08 11:57:10 +01:00
Ante Pušić
6aa9e2a78b Rename the text search flag and set it to false by default 2024-02-08 00:55:45 +01:00
Ante Pušić
a6eb14f742 Fix the e2e tests 2024-02-08 00:22:57 +01:00
Ante Pušić
743f7b343a Fix text index logic on label removal 2024-02-08 00:14:06 +01:00
Ante Pušić
e9aaf82985
Merge branch 'master' into text-search-integration-poc 2024-02-05 18:20:16 +01:00
Ante Pušić
a07d577bd5 Fix snapshot recovery 2024-02-05 12:19:53 +01:00
Ante Pušić
388eb91320 Fix SetPropertiesCursor and empty properties JSON 2024-02-05 12:02:03 +01:00
Ante Pušić
b0003ac08e Bump up snapshot & WAL file version 2024-02-05 10:01:13 +01:00
Ante Pušić
445eaaf82a Remove unused variables 2024-02-05 09:39:53 +01:00
Ante Pušić
4b46f1bb54 Add durability for text indices 2024-02-05 09:37:45 +01:00
Ante Pušić
ea64f2e906 Remove superfluous comment 2024-02-04 18:24:30 +01:00
Ante Pušić
71d2a2f9ea Fix typo 2024-02-04 18:12:05 +01:00
Ante Pušić
c426f4a4a4 Add commit/rollback 2024-02-04 18:11:58 +01:00
Ante Pušić
a919429ea5 Add more error handling 2024-02-04 12:33:37 +01:00
Ante Pušić
9e0b857e25 Add error handling 2024-02-04 12:09:01 +01:00
Ante Pušić
5cea4d0c40 Improve the text search query module 2024-02-04 02:25:33 +01:00
Ante Pušić
ecbe385e58 Fix text search methods in accessors 2024-02-04 01:09:16 +01:00
Ante Pušić
2ac3d85735
Merge branch 'master' into text-search-integration-poc 2024-02-04 00:52:54 +01:00
Ante Pušić
a90cd8c468 Extend query dump test 2024-02-03 16:02:46 +01:00
Ante Pušić
e762fd8870 Update storage info test 2024-02-03 12:46:21 +01:00
Ante Pušić
d5d101e7dc Add text indices to storage info 2024-02-03 12:45:11 +01:00
Ante Pušić
cdf7b53aa7
Merge branch 'master' into text-search-integration-poc 2024-01-31 00:55:46 +01:00
Ante Pušić
a278ae1139 Apply interpreter code suggestion 2024-01-31 00:26:23 +01:00
Ante Pušić
19642595e3 Fix typo 2024-01-30 20:02:24 +01:00
Ante Pušić
73cb1811e0 Fix unit tests 2024-01-30 19:35:53 +01:00
Ante Pušić
be561bb5d6 Check whether text search is enabled, fix calls accordingly and set default values for the update_text_index parameter 2024-01-30 19:35:31 +01:00
Ante Pušić
212ed5830f Remove superfluous comment 2024-01-30 16:14:26 +01:00
Ante Pušić
b04e40984d Add note for future work 2024-01-30 16:09:20 +01:00
Ante Pušić
59b83d003f Tidy up code 2024-01-30 11:18:17 +01:00
Ante Pušić
ff69a891d8 Fix syntax errors 2024-01-30 10:45:37 +01:00
Ante Pušić
857dbfb84e Add work on durability 2024-01-29 16:50:22 +01:00
Ante Pušić
3e7e0d896c Add text index information to DUMP DATABASE and make it extensible 2024-01-29 12:07:09 +01:00
Ante Pušić
b24afcde0a Shorten demo build time 2024-01-23 15:47:46 +01:00
Ante Pušić
9486c3fc47 Add query module CMakeLists 2024-01-23 15:37:01 +01:00
Ante Pušić
f61bf766bb Use the correct branch of mgcxx 2024-01-23 13:15:37 +01:00
Ante Pušić
55fd6dcc75 Optimize index creation 2024-01-23 12:21:29 +01:00
Ante Pušić
04410d242b Fill out tests 2024-01-22 13:45:59 +01:00
Ante Pušić
c7be58e1dc Improve tests 2024-01-22 10:28:29 +01:00
Ante Pušić
2ef9d81a63 Add clarification 2024-01-22 10:28:01 +01:00
Ante Pušić
db3991db8f Support indexing a wider range of property types 2024-01-22 10:27:47 +01:00
Ante Pušić
5f2de50d28
Merge branch 'master' into text-search-integration-poc 2024-01-22 09:04:59 +01:00
Ante Pušić
c2824e3dc0 Add transaction IDs to Tantivy metadata 2024-01-22 09:04:21 +01:00
Ante Pušić
1334a631b3 Fix expecting exceptions in pytest tests 2024-01-22 09:03:50 +01:00
Ante Pušić
456074e3cf
Merge branch 'master' into text-search-integration-poc 2024-01-19 12:13:05 +01:00
Ante Pušić
b7bc9c651e Remove one extraneous import more 2024-01-19 00:32:19 +01:00
Ante Pušić
7b77981796 Remove the mgcxx mock 2024-01-19 00:31:01 +01:00
Ante Pušić
e4e3044fce Remove extraneous comments and #includes 2024-01-19 00:30:15 +01:00
Ante Pušić
0571da7116 Add error handling attempt 2024-01-19 00:08:56 +01:00
Ante Pušić
64dcc2c60a Add missing calls for removing deleted nodes from text indices 2024-01-18 19:45:20 +01:00
Ante Pušić
e16c4a0208 Rename mgcxx namespace and fix Rust → C++ string handling 2024-01-18 17:38:33 +01:00
Ante Pušić
c60b66fde7 Improve tests 2024-01-18 01:51:28 +01:00
Ante Pušić
2df3b09019 Add more CRUD operations 2024-01-18 01:50:57 +01:00
Ante Pušić
809a990cd5 Add error message 2024-01-18 01:49:22 +01:00
Ante Pušić
88f8a0ae68 Add most of CRUD 2024-01-17 21:48:57 +01:00
Ante Pušić
605a425bb3
Merge branch 'master' into text-search-integration-poc 2024-01-16 16:26:45 +01:00
Ante Pušić
296d6eb4ff Add delete_document 2024-01-16 16:26:36 +01:00
Ante Pušić
60f8c3fc15 Add skeleton for indexed property update 2024-01-16 16:26:12 +01:00
Ante Pušić
0335030c93 Add text indices to SHOW INDEX INFO 2024-01-16 09:01:05 +01:00
Ante Pušić
dbd3835ba2 Remove comment 2024-01-16 07:58:32 +01:00
Ante Pušić
2d2d21bab4 Implement remaining test cases and add more of them 2024-01-16 01:51:39 +01:00
Ante Pušić
5c23e313e4 Add e2e search example 2024-01-16 00:35:46 +01:00
Ante Pušić
085b5b8284 Add new text fixture 2024-01-15 20:30:08 +01:00
Ante Pušić
8f0250c265 Add index creation mappings 2024-01-15 20:29:35 +01:00
Ante Pušić
684ae0192b Clean query module up 2024-01-15 20:28:44 +01:00
Ante Pušić
1d6fa4281d Integrate mgcxx into text search 2024-01-15 17:12:03 +01:00
Ante Pušić
5fac8de106 Rename the text search module to avoid conflicts 2024-01-15 09:41:58 +01:00
Ante Pušić
99417f75b6 Add test cases 2024-01-15 09:38:45 +01:00
Ante Pušić
0c3220839c Fix mgcxx API integration 2024-01-14 22:54:33 +01:00
Ante Pušić
6fb4eb6099 Separate text index header from implementation 2024-01-14 22:53:36 +01:00
Ante Pušić
29916a9db2 Add Tantivy and mgcxx as dependencies 2024-01-11 09:31:47 +01:00
Ante Pušić
e78c69866f Fix the added API methods 2024-01-11 09:31:22 +01:00
Ante Pušić
9a3a9d33e3
Merge branch 'master' into text-search-integration-poc 2024-01-10 01:24:54 +01:00
Ante Pušić
ba581f58ef Extend text index operations 2024-01-10 01:23:28 +01:00
Ante Pušić
00275f5736 Implement the text search query module 2024-01-10 00:47:55 +01:00
Ante Pušić
bed5651716 Fix text search flag 2024-01-10 00:44:16 +01:00
Ante Pušić
753aebb895 Connect text search to its query module 2024-01-09 22:20:35 +01:00
Ante Pušić
6545a741b8 Add partial logic for external CRUD operations 2024-01-09 21:49:04 +01:00
Ante Pušić
b067ded578 Redo text index checking 2024-01-09 21:35:57 +01:00
Ante Pušić
3595a9c3b5 Move text index data CRUD to VertexAccessor 2024-01-09 21:09:38 +01:00
Ante Pušić
5fb9324239 Switch to text indices that are named 2024-01-09 20:46:30 +01:00
Ante Pušić
ce718c4646
Merge branch 'master' into text-search-integration-poc 2024-01-08 01:58:33 +01:00
Ante Pušić
5d5981ffa1 Add proposal for text index checking and communication 2024-01-08 01:57:46 +01:00
Ante Pušić
2ec08f7da5 Add work on the search query procedure 2024-01-08 01:33:34 +01:00
Ante Pušić
584d86e774 Add text-search-enabled flag 2024-01-08 01:25:56 +01:00
Ante Pušić
5b22a7d3a4 Add text index existence checking and search 2024-01-08 00:36:13 +01:00
Ante Pušić
fe7e230e21 Add external storage checks to VertexAccessor 2024-01-08 00:35:05 +01:00
Ante Pušić
ec35144265 Move the mock 2024-01-05 13:54:30 +01:00
Ante Pušić
102bf1df5e Fix minor errors 2024-01-05 13:32:45 +01:00
Ante Pušić
9d06d1b5db
Merge branch 'master' into text-search-integration-poc 2024-01-05 13:31:51 +01:00
Ante Pušić
c4f8512677 Update query module CMakeLists 2024-01-05 10:34:40 +01:00
Ante Pušić
6865616cae Make proof of concept for external storage 2024-01-05 02:14:14 +01:00
Ante Pušić
f0a2b67f33 Remove obsolete unit test 2024-01-05 00:20:27 +01:00
Ante Pušić
6c2b448fe9 Remove mock from PropertyStore 2024-01-05 00:19:40 +01:00
Ante Pušić
3737fac8f8 Add text index creation and deletion 2024-01-05 00:19:13 +01:00
Ante Pušić
a5d849b5e9 Add text index creation and deletion 2024-01-05 00:18:46 +01:00
Ante Pušić
0357d19238 Fix IndexQuery constructor 2024-01-05 00:17:34 +01:00
Ante Pušić
63c9628cef Split the mock into its own file 2024-01-05 00:16:57 +01:00
Ante Pušić
e5f2ac36cc Add text index Cypher syntax 2024-01-05 00:15:47 +01:00
Ante Pušić
b4b5970ba6 Merge branch 'text-search-integration-poc' of https://github.com/memgraph/memgraph into text-search-integration-poc 2024-01-04 11:16:26 +01:00
Ante Pušić
71e03d1fa3 Add draft query module 2024-01-04 10:34:58 +01:00
Ante Pušić
56b86089a6 Add indexing draft 2024-01-04 10:32:54 +01:00
Ante Pušić
d4059a89aa Redo mock and PropertyStore 2024-01-04 10:32:08 +01:00
Ante Pušić
c3208b064b
Merge branch 'master' into text-search-integration-poc 2024-01-03 01:07:43 +01:00
Ante Pušić
0baff463b1 Add docstring for Addocument 2023-12-22 15:18:28 +01:00
Ante Pušić
3d40976756 Add other API mehtods 2023-12-22 14:59:15 +01:00
Ante Pušić
510f2909ae Add functioning basic proof of concept 2023-12-22 14:42:12 +01:00
Ante Pušić
df82b05725 Add preliminary mock and PropertyStore modifications 2023-12-21 13:23:14 +01:00
69 changed files with 1748 additions and 76 deletions

View File

@ -0,0 +1,67 @@
#!/bin/bash -e
# helpers
pushd () { command pushd "$@" > /dev/null; }
popd () { command popd "$@" > /dev/null; }
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
CPUS=$( grep -c processor < /proc/cpuinfo )
cd "$DIR"
source "$DIR/../../util.sh"
DISTRO="$(operating_system)"
function log_tool_name () {
echo ""
echo ""
echo "#### $1 ####"
echo ""
echo ""
}
TOOLCHAIN_VERSION=4
NAME=toolchain-v$TOOLCHAIN_VERSION
PREFIX=/opt/$NAME
rm -rf "$PREFIX/include/zstd.h"
# create archives directory
mkdir -p archives && pushd archives
ZSTD_VERSION=1.5.5
if [ ! -f zstd-$ZSTD_VERSION.tar.gz ]; then
wget https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz -O zstd-$ZSTD_VERSION.tar.gz
fi
popd
# create build directory and activate toolchain
mkdir -p build
pushd build
source $PREFIX/activate
export CC=$PREFIX/bin/clang
export CXX=$PREFIX/bin/clang++
export CFLAGS="$CFLAGS -fPIC"
export PATH=$PREFIX/bin:$PATH
export LD_LIBRARY_PATH=$PREFIX/lib64
COMMON_CMAKE_FLAGS="-DCMAKE_INSTALL_PREFIX=$PREFIX
-DCMAKE_PREFIX_PATH=$PREFIX
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_C_COMPILER=$CC
-DCMAKE_CXX_COMPILER=$CXX
-DBUILD_SHARED_LIBS=OFF
-DCMAKE_CXX_STANDARD=20
-DBUILD_TESTING=OFF
-DCMAKE_REQUIRED_INCLUDES=$PREFIX/include
-DCMAKE_POSITION_INDEPENDENT_CODE=ON"
log_tool_name "zstd $ZSTD_VERSION"
if [ ! -f $PREFIX/include/zstd.h ]; then
if [ -d zstd-$ZSTD_VERSION ]; then
rm -rf zstd-$ZSTD_VERSION
fi
tar -xzf ../archives/zstd-$ZSTD_VERSION.tar.gz
pushd zstd-$ZSTD_VERSION
# build is used by facebook builder
mkdir _build
pushd _build
cmake ../build/cmake $COMMON_CMAKE_FLAGS -DZSTD_BUILD_SHARED=OFF
make -j$CPUS install
popd && popd
fi

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -283,7 +283,7 @@ inline mgp_list *list_all_unique_constraints(mgp_graph *graph, mgp_memory *memor
}
// mgp_graph
inline bool graph_is_transactional(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_transactional, graph); }
inline bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
@ -326,6 +326,20 @@ inline mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_me
return MgInvoke<mgp_vertex *>(mgp_graph_get_vertex_by_id, g, id, memory);
}
inline bool graph_has_text_index(mgp_graph *graph, const char *index_name) {
return MgInvoke<int>(mgp_graph_has_text_index, graph, index_name);
}
inline mgp_map *graph_search_text_index(mgp_graph *graph, const char *index_name, const char *search_query,
mgp_memory *memory) {
return MgInvoke<mgp_map *>(mgp_graph_search_text_index, graph, index_name, search_query, memory);
}
inline mgp_map *graph_regex_search_text_index(mgp_graph *graph, const char *index_name, const char *search_query,
mgp_memory *memory) {
return MgInvoke<mgp_map *>(mgp_graph_regex_search_text_index, graph, index_name, search_query, memory);
}
inline mgp_vertices_iterator *graph_iter_vertices(mgp_graph *g, mgp_memory *memory) {
return MgInvoke<mgp_vertices_iterator *>(mgp_graph_iter_vertices, g, memory);
}

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -891,6 +891,23 @@ enum mgp_error mgp_edge_iter_properties(struct mgp_edge *e, struct mgp_memory *m
enum mgp_error mgp_graph_get_vertex_by_id(struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory,
struct mgp_vertex **result);
/// Result is non-zero if the index with the given name exists.
/// Current implementation always returns without errors.
enum mgp_error mgp_graph_has_text_index(struct mgp_graph *graph, const char *index_name, int *result);
/// Search the named text index for the given query. The result is a list of the vertices whose text properties match
/// the given query.
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate search result vertices.
enum mgp_error mgp_graph_search_text_index(struct mgp_graph *graph, const char *index_name, const char *search_query,
struct mgp_memory *memory, struct mgp_map **result);
/// Search the named text index for the given regex. The result is a list of the vertices whose text properties match
/// the given query.
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate search result vertices.
enum mgp_error mgp_graph_regex_search_text_index(struct mgp_graph *graph, const char *index_name,
const char *search_query, struct mgp_memory *memory,
struct mgp_map **result);
/// Creates label index for given label.
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
/// if label index already exists, result will be 0, otherwise 1.

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -4306,12 +4306,12 @@ inline void AddParamsReturnsToProc(mgp_proc *proc, std::vector<Parameter> &param
}
} // namespace detail
inline bool CreateLabelIndex(mgp_graph *memgaph_graph, const std::string_view label) {
return create_label_index(memgaph_graph, label.data());
inline bool CreateLabelIndex(mgp_graph *memgraph_graph, const std::string_view label) {
return create_label_index(memgraph_graph, label.data());
}
inline bool DropLabelIndex(mgp_graph *memgaph_graph, const std::string_view label) {
return drop_label_index(memgaph_graph, label.data());
inline bool DropLabelIndex(mgp_graph *memgraph_graph, const std::string_view label) {
return drop_label_index(memgraph_graph, label.data());
}
inline List ListAllLabelIndices(mgp_graph *memgraph_graph) {
@ -4322,14 +4322,14 @@ inline List ListAllLabelIndices(mgp_graph *memgraph_graph) {
return List(label_indices);
}
inline bool CreateLabelPropertyIndex(mgp_graph *memgaph_graph, const std::string_view label,
inline bool CreateLabelPropertyIndex(mgp_graph *memgraph_graph, const std::string_view label,
const std::string_view property) {
return create_label_property_index(memgaph_graph, label.data(), property.data());
return create_label_property_index(memgraph_graph, label.data(), property.data());
}
inline bool DropLabelPropertyIndex(mgp_graph *memgaph_graph, const std::string_view label,
inline bool DropLabelPropertyIndex(mgp_graph *memgraph_graph, const std::string_view label,
const std::string_view property) {
return drop_label_property_index(memgaph_graph, label.data(), property.data());
return drop_label_property_index(memgraph_graph, label.data(), property.data());
}
inline List ListAllLabelPropertyIndices(mgp_graph *memgraph_graph) {
@ -4340,6 +4340,29 @@ inline List ListAllLabelPropertyIndices(mgp_graph *memgraph_graph) {
return List(label_property_indices);
}
inline List RunTextSearchQuery(mgp_graph *memgraph_graph, std::string_view index_name, std::string_view search_query) {
auto results_or_error =
Map(mgp::MemHandlerCallback(graph_search_text_index, memgraph_graph, index_name.data(), search_query.data()));
auto maybe_error = results_or_error["error_msg"].ValueString();
if (!maybe_error.empty()) {
throw std::runtime_error{maybe_error.data()};
}
return results_or_error["search_results"].ValueList();
}
inline List RunTextRegexSearchQuery(mgp_graph *memgraph_graph, std::string_view index_name,
std::string_view search_query) {
auto results_or_error = Map(
mgp::MemHandlerCallback(graph_regex_search_text_index, memgraph_graph, index_name.data(), search_query.data()));
auto maybe_error = results_or_error["error_msg"].ValueString();
if (!maybe_error.empty()) {
throw std::runtime_error{maybe_error.data()};
}
return results_or_error["search_results"].ValueList();
}
inline bool CreateExistenceConstraint(mgp_graph *memgraph_graph, const std::string_view label,
const std::string_view property) {
return create_existence_constraint(memgraph_graph, label.data(), property.data());

View File

@ -295,6 +295,40 @@ set_path_external_library(jemalloc STATIC
import_header_library(rangev3 ${CMAKE_CURRENT_SOURCE_DIR}/rangev3/include)
if(NOT DEFINED MGCXX_GIT_TAG)
set(MGCXX_GIT_TAG "v0.0.3" CACHE STRING "mgcxx git tag")
else()
set(MGCXX_GIT_TAG "${MGCXX_GIT_TAG}" CACHE STRING "mgcxx git tag")
endif()
ExternalProject_Add(mgcxx-proj
PREFIX mgcxx-proj
GIT_REPOSITORY https://github.com/memgraph/mgcxx
GIT_TAG "origin/regex-queries"
CMAKE_ARGS
"-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>"
"-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
"-DENABLE_TESTS=OFF"
INSTALL_DIR "${PROJECT_BINARY_DIR}/mgcxx"
)
ExternalProject_Get_Property(mgcxx-proj install_dir)
set(MGCXX_ROOT ${install_dir})
add_library(tantivy_text_search STATIC IMPORTED GLOBAL)
add_dependencies(tantivy_text_search mgcxx-proj)
set_property(TARGET tantivy_text_search PROPERTY IMPORTED_LOCATION ${MGCXX_ROOT}/lib/libtantivy_text_search.a)
add_library(mgcxx_text_search STATIC IMPORTED GLOBAL)
add_dependencies(mgcxx_text_search mgcxx-proj)
set_property(TARGET mgcxx_text_search PROPERTY IMPORTED_LOCATION ${MGCXX_ROOT}/lib/libmgcxx_text_search.a)
# 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
# generated later during the build process.
file(MAKE_DIRECTORY ${MGCXX_ROOT}/include)
set_property(TARGET mgcxx_text_search PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${MGCXX_ROOT}/include)
# Setup NuRaft
import_external_library(nuraft STATIC
${CMAKE_CURRENT_SOURCE_DIR}/nuraft/lib/libnuraft.a

View File

@ -127,6 +127,7 @@ declare -A primary_urls=(
["jemalloc"]="http://$local_cache_host/git/jemalloc.git"
["range-v3"]="http://$local_cache_host/git/range-v3.git"
["nuraft"]="http://$local_cache_host/git/NuRaft.git"
["zstd"]="http://$local_cache_host/git/zstd.git"
)
# The goal of secondary urls is to have links to the "source of truth" of
@ -157,6 +158,7 @@ declare -A secondary_urls=(
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
["range-v3"]="https://github.com/ericniebler/range-v3.git"
["nuraft"]="https://github.com/eBay/NuRaft.git"
["zstd"]="https://github.com/facebook/zstd.git"
)
# antlr
@ -288,3 +290,7 @@ repo_clone_try_double "${primary_urls[nuraft]}" "${secondary_urls[nuraft]}" "nur
pushd nuraft
./prepare.sh
popd
# zstd
zstd_tag="v1.5.5"
repo_clone_try_double "${primary_urls[zstd]}" "${secondary_urls[zstd]}" "zstd" "$zstd_tag" true

View File

@ -58,6 +58,22 @@ install(PROGRAMS $<TARGET_FILE:schema>
# Also install the source of the example, so user can read it.
install(FILES schema.cpp DESTINATION lib/memgraph/query_modules/src)
add_library(text SHARED text_search_module.cpp)
target_include_directories(text PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_options(text PRIVATE -Wall)
target_link_libraries(text PRIVATE -static-libgcc -static-libstdc++)
# Strip C++ example in release build.
if (lower_build_type STREQUAL "release")
add_custom_command(TARGET text POST_BUILD
COMMAND strip -s $<TARGET_FILE:text>
COMMENT "Stripping symbols and sections from the C++ text_search module")
endif()
install(PROGRAMS $<TARGET_FILE:text>
DESTINATION lib/memgraph/query_modules
RENAME text.so)
# Also install the source of the example, so user can read it.
install(FILES text_search_module.cpp DESTINATION lib/memgraph/query_modules/src)
# Install the Python example and modules
install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_example.py)
install(FILES graph_analyzer.py DESTINATION lib/memgraph/query_modules)

View File

@ -0,0 +1,105 @@
// Copyright 2024 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 <string>
#include <string_view>
#include <fmt/format.h>
#include <mgp.hpp>
namespace TextSearch {
constexpr std::string_view kProcedureSearch = "search";
constexpr std::string_view kProcedureRegexSearch = "regex_search";
constexpr std::string_view kParameterIndexName = "index_name";
constexpr std::string_view kParameterSearchString = "search_query";
constexpr std::string_view kReturnNode = "node";
void Search(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
void RegexSearch(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
} // namespace TextSearch
void TextSearch::Search(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
mgp::MemoryDispatcherGuard guard{memory};
const auto record_factory = mgp::RecordFactory(result);
auto arguments = mgp::List(args);
try {
const auto *index_name = arguments[0].ValueString().data();
const auto *search_query = arguments[1].ValueString().data();
// 1. See if the given index_name is text-indexed
if (!mgp::graph_has_text_index(memgraph_graph, index_name)) {
record_factory.SetErrorMessage(fmt::format("Text index \"{}\" doesnt exist.", index_name));
return;
}
// 2. Run a text search of that index and return the search results
for (const auto &node : mgp::RunTextSearchQuery(memgraph_graph, index_name, search_query)) {
auto record = record_factory.NewRecord();
record.Insert(TextSearch::kReturnNode.data(), node.ValueNode());
}
} catch (const std::exception &e) {
record_factory.SetErrorMessage(e.what());
}
}
void TextSearch::RegexSearch(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
mgp::MemoryDispatcherGuard guard{memory};
const auto record_factory = mgp::RecordFactory(result);
auto arguments = mgp::List(args);
try {
const auto *index_name = arguments[0].ValueString().data();
const auto *search_query = arguments[1].ValueString().data();
// 1. See if the given index_name is text-indexed
if (!mgp::graph_has_text_index(memgraph_graph, index_name)) {
record_factory.SetErrorMessage(fmt::format("Text index \"{}\" doesnt exist.", index_name));
return;
}
// 2. Run a text search of that index and return the search results
for (const auto &node : mgp::RunTextRegexSearchQuery(memgraph_graph, index_name, search_query)) {
auto record = record_factory.NewRecord();
record.Insert(TextSearch::kReturnNode.data(), node.ValueNode());
}
} catch (const std::exception &e) {
record_factory.SetErrorMessage(e.what());
}
}
extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
try {
mgp::MemoryDispatcherGuard guard{memory};
AddProcedure(TextSearch::Search, TextSearch::kProcedureSearch, mgp::ProcedureType::Read,
{
mgp::Parameter(TextSearch::kParameterIndexName, mgp::Type::String),
mgp::Parameter(TextSearch::kParameterSearchString, mgp::Type::String),
},
{mgp::Return(TextSearch::kReturnNode, mgp::Type::Node)}, module, memory);
AddProcedure(TextSearch::RegexSearch, TextSearch::kProcedureRegexSearch, mgp::ProcedureType::Read,
{
mgp::Parameter(TextSearch::kParameterIndexName, mgp::Type::String),
mgp::Parameter(TextSearch::kParameterSearchString, mgp::Type::String),
},
{mgp::Return(TextSearch::kReturnNode, mgp::Type::Node)}, module, memory);
} catch (const std::exception &e) {
std::cerr << "Error while initializing query module: " << e.what() << std::endl;
return 1;
}
return 0;
}
extern "C" int mgp_shutdown_module() { return 0; }

View File

@ -45,7 +45,7 @@ set(mg_single_node_v2_sources
add_executable(memgraph ${mg_single_node_v2_sources})
target_include_directories(memgraph PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(memgraph stdc++fs Threads::Threads
mg-telemetry mg-communication mg-communication-metrics mg-memory mg-utils mg-license mg-settings mg-glue mg-flags mg::system mg::replication_handler)
mg-telemetry mgcxx_text_search tantivy_text_search mg-communication mg-communication-metrics mg-memory mg-utils mg-license mg-settings mg-glue mg-flags mg::system mg::replication_handler)
# NOTE: `include/mg_procedure.syms` describes a pattern match for symbols which
# should be dynamically exported, so that `dlopen` can correctly link th

View File

@ -297,7 +297,7 @@ class DbmsHandler {
stats.triggers += info.triggers;
stats.streams += info.streams;
++stats.num_databases;
stats.indices += storage_info.label_indices + storage_info.label_property_indices;
stats.indices += storage_info.label_indices + storage_info.label_property_indices + storage_info.text_indices;
stats.constraints += storage_info.existence_constraints + storage_info.unique_constraints;
++stats.storage_modes[(int)storage_info.storage_mode];
++stats.isolation_levels[(int)storage_info.isolation_level];

View File

@ -534,6 +534,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
if (!vertex)
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
// NOTE: Phase 1 of the text search feature doesn't have replication in scope
auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
if (ret.HasError() || !ret.GetValue())
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
@ -546,6 +547,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
if (!vertex)
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
// NOTE: Phase 1 of the text search feature doesn't have replication in scope
auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
if (ret.HasError() || !ret.GetValue())
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
@ -558,6 +560,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, View::NEW);
if (!vertex)
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
// NOTE: Phase 1 of the text search feature doesn't have replication in scope
auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
delta.vertex_edge_set_property.value);
if (ret.HasError())
@ -758,6 +761,14 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
transaction->DeleteLabelPropertyIndexStats(storage->NameToLabel(info.label));
break;
}
case WalDeltaData::Type::TEXT_INDEX_CREATE: {
// NOTE: Phase 1 of the text search feature doesn't have replication in scope
break;
}
case WalDeltaData::Type::TEXT_INDEX_DROP: {
// NOTE: Phase 1 of the text search feature doesn't have replication in scope
break;
}
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -54,6 +54,9 @@ DEFINE_double(query_execution_timeout_sec, 600,
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(cartesian_product_enabled, true, "Enable cartesian product expansion.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(experimental_text_search_enabled, false, "Enable experimental text search.");
namespace {
// Bolt server name
constexpr auto kServerNameSettingKey = "server.name";
@ -73,11 +76,15 @@ constexpr auto kLogToStderrGFlagsKey = "also_log_to_stderr";
constexpr auto kCartesianProductEnabledSettingKey = "cartesian-product-enabled";
constexpr auto kCartesianProductEnabledGFlagsKey = "cartesian-product-enabled";
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::atomic<double> execution_timeout_sec_; // Local cache-like thing
constexpr auto kExperimentalTextSearchEnabledSettingKey = "experimental-text-search-enabled";
constexpr auto kExperimentalTextSearchEnabledGFlagsKey = "experimental-text-search-enabled";
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::atomic<bool> cartesian_product_enabled_{true}; // Local cache-like thing
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
// Local cache-like thing
std::atomic<double> execution_timeout_sec_;
std::atomic<bool> cartesian_product_enabled_{true};
std::atomic<bool> experimental_text_search_enabled_{true};
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
auto ToLLEnum(std::string_view val) {
const auto ll_enum = memgraph::flags::LogLevelToEnum(val);
@ -186,6 +193,10 @@ void Initialize() {
register_flag(
kCartesianProductEnabledGFlagsKey, kCartesianProductEnabledSettingKey, !kRestore,
[](const std::string &val) { cartesian_product_enabled_ = val == "true"; }, ValidBoolStr);
register_flag(
kExperimentalTextSearchEnabledGFlagsKey, kExperimentalTextSearchEnabledSettingKey, !kRestore,
[](const std::string &val) { experimental_text_search_enabled_ = val == "true"; }, ValidBoolStr);
}
std::string GetServerName() {
@ -199,4 +210,6 @@ double GetExecutionTimeout() { return execution_timeout_sec_; }
bool GetCartesianProductEnabled() { return cartesian_product_enabled_; }
bool GetExperimentalTextSearchEnabled() { return experimental_text_search_enabled_; }
} // namespace memgraph::flags::run_time

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -42,4 +42,11 @@ double GetExecutionTimeout();
*/
bool GetCartesianProductEnabled();
/**
* @brief Get whether text search is enabled
*
* @return bool
*/
bool GetExperimentalTextSearchEnabled();
} // namespace memgraph::flags::run_time

View File

@ -556,6 +556,24 @@ class DbAccessor final {
return accessor_->LabelPropertyIndexExists(label, prop);
}
bool TextIndexExists(const std::string &index_name) const { return accessor_->TextIndexExists(index_name); }
void TextIndexAddVertex(VertexAccessor *vertex) { accessor_->TextIndexAddVertex(&vertex->impl_); }
void TextIndexUpdateVertex(VertexAccessor *vertex) { accessor_->TextIndexUpdateVertex(&vertex->impl_); }
void TextIndexUpdateVertex(VertexAccessor *vertex, const std::vector<storage::LabelId> &removed_labels) {
accessor_->TextIndexUpdateVertex(&vertex->impl_, removed_labels);
}
std::vector<storage::Gid> TextIndexSearch(const std::string &index_name, const std::string &search_query) const {
return accessor_->TextIndexSearch(index_name, search_query);
}
std::vector<storage::Gid> TextIndexRegexSearch(const std::string &index_name, const std::string &search_query) const {
return accessor_->TextIndexRegexSearch(index_name, search_query);
}
std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const {
return accessor_->GetIndexStats(label);
}
@ -631,6 +649,12 @@ class DbAccessor final {
return accessor_->DropIndex(label, property);
}
void CreateTextIndex(const std::string &index_name, storage::LabelId label) {
accessor_->CreateTextIndex(index_name, label, this);
}
void DropTextIndex(const std::string &index_name) { accessor_->DropTextIndex(index_name); }
utils::BasicResult<storage::StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
storage::LabelId label, storage::PropertyId property) {
return accessor_->CreateExistenceConstraint(label, property);

View File

@ -248,6 +248,10 @@ void DumpLabelPropertyIndex(std::ostream *os, query::DbAccessor *dba, storage::L
<< ");";
}
void DumpTextIndex(std::ostream *os, query::DbAccessor *dba, const std::string &index_name, storage::LabelId label) {
*os << "CREATE TEXT INDEX " << EscapeName(index_name) << " ON :" << EscapeName(dba->LabelToName(label)) << ";";
}
void DumpExistenceConstraint(std::ostream *os, query::DbAccessor *dba, storage::LabelId label,
storage::PropertyId property) {
*os << "CREATE CONSTRAINT ON (u:" << EscapeName(dba->LabelToName(label)) << ") ASSERT EXISTS (u."
@ -282,6 +286,8 @@ PullPlanDump::PullPlanDump(DbAccessor *dba, dbms::DatabaseAccess db_acc)
CreateLabelIndicesPullChunk(),
// Dump all label property indices
CreateLabelPropertyIndicesPullChunk(),
// Dump all text indices
CreateTextIndicesPullChunk(),
// Dump all existence constraints
CreateExistenceConstraintsPullChunk(),
// Dump all unique constraints
@ -379,6 +385,34 @@ PullPlanDump::PullChunk PullPlanDump::CreateLabelPropertyIndicesPullChunk() {
};
}
PullPlanDump::PullChunk PullPlanDump::CreateTextIndicesPullChunk() {
// Dump all text indices
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
// Delay the construction of indices vectors
if (!indices_info_) {
indices_info_.emplace(dba_->ListAllIndices());
}
const auto &text = indices_info_->text_indices;
size_t local_counter = 0;
while (global_index < text.size() && (!n || local_counter < *n)) {
std::ostringstream os;
const auto &text_index = text[global_index];
DumpTextIndex(&os, dba_, text_index.first, text_index.second);
stream->Result({TypedValue(os.str())});
++global_index;
++local_counter;
}
if (global_index == text.size()) {
return local_counter;
}
return std::nullopt;
};
}
PullPlanDump::PullChunk PullPlanDump::CreateExistenceConstraintsPullChunk() {
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
// Delay the construction of constraint vectors

View File

@ -55,6 +55,7 @@ struct PullPlanDump {
PullChunk CreateLabelIndicesPullChunk();
PullChunk CreateLabelPropertyIndicesPullChunk();
PullChunk CreateTextIndicesPullChunk();
PullChunk CreateExistenceConstraintsPullChunk();
PullChunk CreateUniqueConstraintsPullChunk();
PullChunk CreateInternalIndexPullChunk();

View File

@ -433,4 +433,15 @@ class MultiDatabaseQueryInMulticommandTxException : public QueryException {
SPECIALIZE_GET_EXCEPTION_NAME(MultiDatabaseQueryInMulticommandTxException)
};
class TextSearchException : public QueryException {
using QueryException::QueryException;
SPECIALIZE_GET_EXCEPTION_NAME(TextSearchException)
};
class TextSearchDisabledException : public TextSearchException {
public:
TextSearchDisabledException() : TextSearchException("To use text indices, enable the text search feature.") {}
SPECIALIZE_GET_EXCEPTION_NAME(TextSearchDisabledException)
};
} // namespace memgraph::query

View File

@ -186,6 +186,9 @@ constexpr utils::TypeInfo query::ProfileQuery::kType{utils::TypeId::AST_PROFILE_
constexpr utils::TypeInfo query::IndexQuery::kType{utils::TypeId::AST_INDEX_QUERY, "IndexQuery", &query::Query::kType};
constexpr utils::TypeInfo query::TextIndexQuery::kType{utils::TypeId::AST_TEXT_INDEX_QUERY, "TextIndexQuery",
&query::Query::kType};
constexpr utils::TypeInfo query::Create::kType{utils::TypeId::AST_CREATE, "Create", &query::Clause::kType};
constexpr utils::TypeInfo query::CallProcedure::kType{utils::TypeId::AST_CALL_PROCEDURE, "CallProcedure",

View File

@ -2223,6 +2223,37 @@ class IndexQuery : public memgraph::query::Query {
friend class AstStorage;
};
class TextIndexQuery : public memgraph::query::Query {
public:
static const utils::TypeInfo kType;
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
enum class Action { CREATE, DROP };
TextIndexQuery() = default;
DEFVISITABLE(QueryVisitor<void>);
memgraph::query::TextIndexQuery::Action action_;
memgraph::query::LabelIx label_;
std::string index_name_;
TextIndexQuery *Clone(AstStorage *storage) const override {
TextIndexQuery *object = storage->Create<TextIndexQuery>();
object->action_ = action_;
object->label_ = storage->GetLabelIx(label_.name);
object->index_name_ = index_name_;
return object;
}
protected:
TextIndexQuery(Action action, LabelIx label, std::string index_name)
: action_(action), label_(std::move(label)), index_name_(index_name) {}
private:
friend class AstStorage;
};
class Create : public memgraph::query::Clause {
public:
static const utils::TypeInfo kType;

View File

@ -82,6 +82,7 @@ class AuthQuery;
class ExplainQuery;
class ProfileQuery;
class IndexQuery;
class TextIndexQuery;
class DatabaseInfoQuery;
class SystemInfoQuery;
class ConstraintQuery;
@ -143,11 +144,11 @@ class ExpressionVisitor
template <class TResult>
class QueryVisitor
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, DatabaseInfoQuery,
SystemInfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery,
SettingQuery, VersionQuery, ShowConfigQuery, TransactionQueueQuery, StorageModeQuery,
AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery,
CoordinatorQuery> {};
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, TextIndexQuery, AuthQuery,
DatabaseInfoQuery, SystemInfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery,
LockPathQuery, FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
StreamQuery, SettingQuery, VersionQuery, ShowConfigQuery, TransactionQueueQuery,
StorageModeQuery, AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery,
EdgeImportModeQuery, CoordinatorQuery> {};
} // namespace memgraph::query

View File

@ -243,6 +243,13 @@ antlrcpp::Any CypherMainVisitor::visitIndexQuery(MemgraphCypher::IndexQueryConte
return index_query;
}
antlrcpp::Any CypherMainVisitor::visitTextIndexQuery(MemgraphCypher::TextIndexQueryContext *ctx) {
MG_ASSERT(ctx->children.size() == 1, "TextIndexQuery should have exactly one child!");
auto *text_index_query = std::any_cast<TextIndexQuery *>(ctx->children[0]->accept(this));
query_ = text_index_query;
return text_index_query;
}
antlrcpp::Any CypherMainVisitor::visitCreateIndex(MemgraphCypher::CreateIndexContext *ctx) {
auto *index_query = storage_->Create<IndexQuery>();
index_query->action_ = IndexQuery::Action::CREATE;
@ -254,6 +261,14 @@ antlrcpp::Any CypherMainVisitor::visitCreateIndex(MemgraphCypher::CreateIndexCon
return index_query;
}
antlrcpp::Any CypherMainVisitor::visitCreateTextIndex(MemgraphCypher::CreateTextIndexContext *ctx) {
auto *index_query = storage_->Create<TextIndexQuery>();
index_query->index_name_ = std::any_cast<std::string>(ctx->indexName()->accept(this));
index_query->action_ = TextIndexQuery::Action::CREATE;
index_query->label_ = AddLabel(std::any_cast<std::string>(ctx->labelName()->accept(this)));
return index_query;
}
antlrcpp::Any CypherMainVisitor::visitDropIndex(MemgraphCypher::DropIndexContext *ctx) {
auto *index_query = storage_->Create<IndexQuery>();
index_query->action_ = IndexQuery::Action::DROP;
@ -265,6 +280,13 @@ antlrcpp::Any CypherMainVisitor::visitDropIndex(MemgraphCypher::DropIndexContext
return index_query;
}
antlrcpp::Any CypherMainVisitor::visitDropTextIndex(MemgraphCypher::DropTextIndexContext *ctx) {
auto *index_query = storage_->Create<TextIndexQuery>();
index_query->index_name_ = std::any_cast<std::string>(ctx->indexName()->accept(this));
index_query->action_ = TextIndexQuery::Action::DROP;
return index_query;
}
antlrcpp::Any CypherMainVisitor::visitAuthQuery(MemgraphCypher::AuthQueryContext *ctx) {
MG_ASSERT(ctx->children.size() == 1, "AuthQuery should have exactly one child!");
auto *auth_query = std::any_cast<AuthQuery *>(ctx->children[0]->accept(this));

View File

@ -148,6 +148,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitIndexQuery(MemgraphCypher::IndexQueryContext *ctx) override;
/**
* @return TextIndexQuery*
*/
antlrcpp::Any visitTextIndexQuery(MemgraphCypher::TextIndexQueryContext *ctx) override;
/**
* @return ExplainQuery*
*/
@ -489,10 +494,20 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
antlrcpp::Any visitCreateIndex(MemgraphCypher::CreateIndexContext *ctx) override;
/**
* @return DropIndex*
* @return TextIndexQuery*
*/
antlrcpp::Any visitCreateTextIndex(MemgraphCypher::CreateTextIndexContext *ctx) override;
/**
* @return IndexQuery*
*/
antlrcpp::Any visitDropIndex(MemgraphCypher::DropIndexContext *ctx) override;
/**
* @return TextIndexQuery*
*/
antlrcpp::Any visitDropTextIndex(MemgraphCypher::DropTextIndexContext *ctx) override;
/**
* @return AuthQuery*
*/

View File

@ -25,6 +25,7 @@ statement : query ;
query : cypherQuery
| indexQuery
| textIndexQuery
| explainQuery
| profileQuery
| databaseInfoQuery
@ -65,6 +66,8 @@ cypherQuery : singleQuery ( cypherUnion )* ( queryMemoryLimit )? ;
indexQuery : createIndex | dropIndex;
textIndexQuery : createTextIndex | dropTextIndex;
singleQuery : clause ( clause )* ;
cypherUnion : ( UNION ALL singleQuery )
@ -339,6 +342,12 @@ createIndex : CREATE INDEX ON ':' labelName ( '(' propertyKeyName ')' )? ;
dropIndex : DROP INDEX ON ':' labelName ( '(' propertyKeyName ')' )? ;
indexName : symbolicName ;
createTextIndex : CREATE TEXT INDEX indexName ON ':' labelName ;
dropTextIndex : DROP TEXT INDEX indexName ;
doubleLiteral : FloatingLiteral ;
cypherKeyword : ALL

View File

@ -131,6 +131,7 @@ SHOW : S H O W ;
SINGLE : S I N G L E ;
STARTS : S T A R T S ;
STORAGE : S T O R A G E ;
TEXT : T E X T ;
THEN : T H E N ;
TRUE : T R U E ;
UNION : U N I O N ;

View File

@ -133,6 +133,7 @@ symbolicName : UnescapedSymbolicName
query : cypherQuery
| indexQuery
| textIndexQuery
| explainQuery
| profileQuery
| databaseInfoQuery

View File

@ -27,6 +27,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
void Visit(IndexQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); }
void Visit(TextIndexQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); }
void Visit(AnalyzeGraphQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); }
void Visit(AuthQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::AUTH); }

View File

@ -2616,6 +2616,76 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
RWType::W};
}
PreparedQuery PrepareTextIndexQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::vector<Notification> *notifications, CurrentDB &current_db) {
if (in_explicit_transaction) {
throw IndexInMulticommandTxException();
}
auto *text_index_query = utils::Downcast<TextIndexQuery>(parsed_query.query);
std::function<void(Notification &)> handler;
// TODO: we will need transaction for replication
MG_ASSERT(current_db.db_acc_, "Text index query expects a current DB");
auto &db_acc = *current_db.db_acc_;
MG_ASSERT(current_db.db_transactional_accessor_, "Text index query expects a current DB transaction");
auto *dba = &*current_db.execution_db_accessor_;
// Creating an index influences computed plan costs.
auto invalidate_plan_cache = [plan_cache = db_acc->plan_cache()] {
plan_cache->WithLock([&](auto &cache) { cache.reset(); });
};
auto *storage = db_acc->storage();
auto label = storage->NameToLabel(text_index_query->label_.name);
auto &index_name = text_index_query->index_name_;
Notification index_notification(SeverityLevel::INFO);
switch (text_index_query->action_) {
case TextIndexQuery::Action::CREATE: {
index_notification.code = NotificationCode::CREATE_INDEX;
index_notification.title = fmt::format("Created text index on label {}.", text_index_query->label_.name);
// TODO: not just storage + invalidate_plan_cache. Need a DB transaction (for replication)
handler = [dba, label, index_name,
invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw TextSearchDisabledException();
}
dba->CreateTextIndex(index_name, label);
utils::OnScopeExit invalidator(invalidate_plan_cache);
};
break;
}
case TextIndexQuery::Action::DROP: {
index_notification.code = NotificationCode::DROP_INDEX;
index_notification.title = fmt::format("Dropped text index on label {}.", text_index_query->label_.name);
// TODO: not just storage + invalidate_plan_cache. Need a DB transaction (for replication)
handler = [dba, index_name,
invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw TextSearchDisabledException();
}
dba->DropTextIndex(index_name);
utils::OnScopeExit invalidator(invalidate_plan_cache);
};
break;
}
}
return PreparedQuery{
{},
std::move(parsed_query.required_privileges),
[handler = std::move(handler), notifications, index_notification = std::move(index_notification)](
AnyStream * /*stream*/, std::optional<int> /*unused*/) mutable {
handler(index_notification);
notifications->push_back(index_notification);
return QueryHandlerResult::COMMIT; // TODO: Will need to become COMMIT when we fix replication
},
RWType::W};
}
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
InterpreterContext *interpreter_context, Interpreter &interpreter) {
if (in_explicit_transaction) {
@ -3408,10 +3478,11 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici
auto *storage = database->storage();
const std::string_view label_index_mark{"label"};
const std::string_view label_property_index_mark{"label+property"};
const std::string_view text_index_mark{"text"};
auto info = dba->ListAllIndices();
auto storage_acc = database->Access();
std::vector<std::vector<TypedValue>> results;
results.reserve(info.label.size() + info.label_property.size());
results.reserve(info.label.size() + info.label_property.size() + info.text_indices.size());
for (const auto &item : info.label) {
results.push_back({TypedValue(label_index_mark), TypedValue(storage->LabelToName(item)), TypedValue(),
TypedValue(static_cast<int>(storage_acc->ApproximateVertexCount(item)))});
@ -3422,6 +3493,9 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici
TypedValue(storage->PropertyToName(item.second)),
TypedValue(static_cast<int>(storage_acc->ApproximateVertexCount(item.first, item.second)))});
}
for (const auto &item : info.text_indices) {
results.push_back({TypedValue(text_index_mark), TypedValue(item.first), TypedValue(), TypedValue()});
}
std::sort(results.begin(), results.end(), [&label_index_mark](const auto &record_1, const auto &record_2) {
const auto type_1 = record_1[0].ValueString();
const auto type_2 = record_2[0].ValueString();
@ -4204,13 +4278,14 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
utils::Downcast<CypherQuery>(parsed_query.query) || utils::Downcast<ExplainQuery>(parsed_query.query) ||
utils::Downcast<ProfileQuery>(parsed_query.query) || utils::Downcast<DumpQuery>(parsed_query.query) ||
utils::Downcast<TriggerQuery>(parsed_query.query) || utils::Downcast<AnalyzeGraphQuery>(parsed_query.query) ||
utils::Downcast<IndexQuery>(parsed_query.query) || utils::Downcast<DatabaseInfoQuery>(parsed_query.query) ||
utils::Downcast<ConstraintQuery>(parsed_query.query);
utils::Downcast<IndexQuery>(parsed_query.query) || utils::Downcast<TextIndexQuery>(parsed_query.query) ||
utils::Downcast<DatabaseInfoQuery>(parsed_query.query) || utils::Downcast<ConstraintQuery>(parsed_query.query);
if (!in_explicit_transaction_ && requires_db_transaction) {
// TODO: ATM only a single database, will change when we have multiple database transactions
bool could_commit = utils::Downcast<CypherQuery>(parsed_query.query) != nullptr;
bool unique = utils::Downcast<IndexQuery>(parsed_query.query) != nullptr ||
utils::Downcast<TextIndexQuery>(parsed_query.query) != nullptr ||
utils::Downcast<ConstraintQuery>(parsed_query.query) != nullptr ||
upper_case_query.find(kSchemaAssert) != std::string::npos;
SetupDatabaseTransaction(could_commit, unique);
@ -4247,6 +4322,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<IndexQuery>(parsed_query.query)) {
prepared_query = PrepareIndexQuery(std::move(parsed_query), in_explicit_transaction_,
&query_execution->notifications, current_db_);
} else if (utils::Downcast<TextIndexQuery>(parsed_query.query)) {
prepared_query = PrepareTextIndexQuery(std::move(parsed_query), in_explicit_transaction_,
&query_execution->notifications, current_db_);
} else if (utils::Downcast<AnalyzeGraphQuery>(parsed_query.query)) {
prepared_query = PrepareAnalyzeGraphQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {

View File

@ -251,6 +251,10 @@ VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *fram
}
MultiPropsInitChecked(&new_node, properties);
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
context.db_accessor->TextIndexAddVertex(&new_node);
}
(*frame)[node_info.symbol] = new_node;
return (*frame)[node_info.symbol].ValueVertex();
}
@ -2881,6 +2885,10 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
context.trigger_context_collector->RegisterSetObjectProperty(lhs.ValueVertex(), self_.property_,
TypedValue{std::move(old_value)}, TypedValue{rhs});
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
auto new_node = lhs.ValueVertex();
context.db_accessor->TextIndexUpdateVertex(&new_node);
}
break;
}
case TypedValue::Type::Edge: {
@ -3037,6 +3045,10 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
case TypedValue::Type::Vertex: {
PropertiesMap new_properties = get_props(rhs.ValueVertex());
update_props(new_properties);
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
auto new_node = rhs.ValueVertex();
context->db_accessor->TextIndexUpdateVertex(&new_node);
}
break;
}
case TypedValue::Type::Map: {
@ -3094,6 +3106,7 @@ bool SetProperties::SetPropertiesCursor::Pull(Frame &frame, ExecutionContext &co
}
#endif
SetPropertiesOnRecord(&lhs.ValueVertex(), rhs, self_.op_, &context, cached_name_id_);
context.db_accessor->TextIndexUpdateVertex(&lhs.ValueVertex());
break;
case TypedValue::Type::Edge:
#ifdef MG_ENTERPRISE
@ -3183,6 +3196,10 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
}
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
context.db_accessor->TextIndexUpdateVertex(&vertex);
}
return true;
}
@ -3254,6 +3271,10 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &
}
#endif
remove_prop(&lhs.ValueVertex());
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
auto &updated_vertex = lhs.ValueVertex();
context.db_accessor->TextIndexUpdateVertex(&updated_vertex);
}
break;
case TypedValue::Type::Edge:
#ifdef MG_ENTERPRISE
@ -3344,6 +3365,10 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
}
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
context.db_accessor->TextIndexUpdateVertex(&vertex, self_.labels_);
}
return true;
}

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -23,6 +23,7 @@
#include <utility>
#include <variant>
#include "flags/run_time_configurable.hpp"
#include "license/license.hpp"
#include "mg_procedure.h"
#include "module.hpp"
@ -1839,7 +1840,10 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
std::visit([property_name](auto *impl) { return impl->NameToProperty(property_name); }, v->graph->impl);
const auto result = std::visit(
[prop_key, property_value](auto &impl) { return impl.SetProperty(prop_key, ToPropertyValue(*property_value)); },
[prop_key, property_value](auto &impl) {
// TODO antepusic also update text index
return impl.SetProperty(prop_key, ToPropertyValue(*property_value));
},
v->impl);
if (result.HasError()) {
switch (result.GetError()) {
@ -1897,6 +1901,7 @@ mgp_error mgp_vertex_set_properties(struct mgp_vertex *v, struct mgp_map *proper
}
const auto result = v->getImpl().UpdateProperties(props);
// TODO antepusic also update text index
if (result.HasError()) {
switch (result.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@ -1953,7 +1958,12 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
}
const auto result = std::visit([label_id](auto &impl) { return impl.AddLabel(label_id); }, v->impl);
const auto result = std::visit(
[label_id](auto &impl) {
// TODO antepusic also update text index
return impl.AddLabel(label_id);
},
v->impl);
if (result.HasError()) {
switch (result.GetError()) {
@ -1995,7 +2005,12 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
}
const auto result = std::visit([label_id](auto &impl) { return impl.RemoveLabel(label_id); }, v->impl);
const auto result = std::visit(
[label_id](auto &impl) {
// todo also remove from text index
return impl.RemoveLabel(label_id);
},
v->impl);
if (result.HasError()) {
switch (result.GetError()) {
@ -2588,7 +2603,7 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) {
return WrapExceptions(
[graph, id, memory]() -> mgp_vertex * {
std::optional<memgraph::query::VertexAccessor> maybe_vertex = std::visit(
auto maybe_vertex = std::visit(
[graph, id](auto *impl) {
return impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
},
@ -3322,6 +3337,135 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
});
}
mgp_error mgp_graph_has_text_index(mgp_graph *graph, const char *index_name, int *result) {
return WrapExceptions([graph, index_name, result]() {
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *impl) { *result = impl->TextIndexExists(index_name); },
[&](memgraph::query::SubgraphDbAccessor *impl) {
*result = impl->GetAccessor()->TextIndexExists(index_name);
}},
graph->impl);
});
}
mgp_vertex *GetVertexByGid(mgp_graph *graph, memgraph::storage::Gid id, mgp_memory *memory) {
std::optional<memgraph::query::VertexAccessor> maybe_vertex =
std::visit([graph, id](auto *impl) { return impl->FindVertex(id, graph->view); }, graph->impl);
if (maybe_vertex) {
return std::visit(memgraph::utils::Overloaded{
[memory, graph, maybe_vertex](memgraph::query::DbAccessor *) {
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
},
[memory, graph, maybe_vertex](memgraph::query::SubgraphDbAccessor *impl) {
return NewRawMgpObject<mgp_vertex>(
memory, memgraph::query::SubgraphVertexAccessor(*maybe_vertex, impl->getGraph()),
graph);
}},
graph->impl);
}
return nullptr;
}
void WrapTextSearch(std::vector<memgraph::storage::Gid> vertex_ids, std::string error_msg, mgp_graph *graph,
mgp_memory *memory, mgp_map **result) {
if (const auto err = mgp_map_make_empty(memory, result); err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Retrieving text search results failed during creation of a mgp_map");
}
mgp_list *search_results{};
if (const auto err = mgp_list_make_empty(vertex_ids.size(), memory, &search_results);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Retrieving text search results failed during creation of a mgp_list");
}
for (const auto &vertex_id : vertex_ids) {
mgp_value *vertex;
if (const auto err = mgp_value_make_vertex(GetVertexByGid(graph, vertex_id, memory), &vertex);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Retrieving text search results failed during creation of a vertex mgp_value");
}
if (const auto err = mgp_list_append(search_results, vertex); err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error(
"Retrieving text search results failed during insertion of the mgp_value into the result list");
}
}
mgp_value *search_results_value;
if (const auto err = mgp_value_make_list(search_results, &search_results_value);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Retrieving text search results failed during creation of a map mgp_value");
}
mgp_value *error_msg_value;
if (const auto err = mgp_value_make_string(error_msg.data(), memory, &error_msg_value);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Retrieving text search results failed during creation of a map mgp_value");
}
if (const auto err = mgp_map_insert(*result, "search_results", search_results_value);
err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Retrieving text search results failed during insertion into mgp_map");
}
if (const auto err = mgp_map_insert(*result, "error_msg", error_msg_value); err != mgp_error::MGP_ERROR_NO_ERROR) {
throw std::logic_error("Retrieving text search results failed during insertion into mgp_map");
}
}
mgp_error mgp_graph_search_text_index(mgp_graph *graph, const char *index_name, const char *search_query,
mgp_memory *memory, mgp_map **result) {
return WrapExceptions([graph, memory, index_name, search_query, result]() {
std::visit(memgraph::utils::Overloaded{[&](memgraph::query::DbAccessor *impl) {
std::vector<memgraph::storage::Gid> search_results;
std::string error_msg;
try {
search_results = impl->TextIndexSearch(index_name, search_query);
} catch (memgraph::query::QueryException &e) {
error_msg = e.what();
}
WrapTextSearch(search_results, error_msg, graph, memory, result);
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
std::vector<memgraph::storage::Gid> search_results;
std::string error_msg;
try {
search_results =
impl->GetAccessor()->TextIndexSearch(index_name, search_query);
} catch (memgraph::query::QueryException &e) {
error_msg = e.what();
}
WrapTextSearch(search_results, error_msg, graph, memory, result);
}},
graph->impl);
});
}
mgp_error mgp_graph_regex_search_text_index(mgp_graph *graph, const char *index_name, const char *search_query,
mgp_memory *memory, mgp_map **result) {
return WrapExceptions([graph, memory, index_name, search_query, result]() {
std::visit(memgraph::utils::Overloaded{[&](memgraph::query::DbAccessor *impl) {
std::vector<memgraph::storage::Gid> search_results;
std::string error_msg;
try {
search_results = impl->TextIndexRegexSearch(index_name, search_query);
} catch (memgraph::query::QueryException &e) {
error_msg = e.what();
}
WrapTextSearch(search_results, error_msg, graph, memory, result);
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
std::vector<memgraph::storage::Gid> search_results;
std::string error_msg;
try {
search_results =
impl->GetAccessor()->TextIndexRegexSearch(index_name, search_query);
} catch (memgraph::query::QueryException &e) {
error_msg = e.what();
}
WrapTextSearch(search_results, error_msg, graph, memory, result);
}},
graph->impl);
});
}
#ifdef MG_ENTERPRISE
namespace {
void NextPermitted(mgp_vertices_iterator &it) {

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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

View File

@ -20,6 +20,7 @@ add_library(mg-storage-v2 STATIC
vertex_info_cache.cpp
storage.cpp
indices/indices.cpp
indices/text_index.cpp
all_vertices_iterable.cpp
vertices_iterable.cpp
inmemory/storage.cpp
@ -42,4 +43,4 @@ add_library(mg-storage-v2 STATIC
inmemory/replication/recovery.cpp
)
target_link_libraries(mg-storage-v2 mg::replication Threads::Threads mg-utils gflags absl::flat_hash_map mg-rpc mg-slk mg-events mg-memory)
target_link_libraries(mg-storage-v2 mg::replication Threads::Threads mg-utils mg-flags gflags absl::flat_hash_map mg-rpc mg-slk mg-events mg-memory mgcxx_text_search tantivy_text_search)

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -26,6 +26,7 @@ constexpr const char *kVertexCountDescr = "vertex_count";
constexpr const char *kEdgeDountDescr = "edge_count";
constexpr const char *kLabelIndexStr = "label_index";
constexpr const char *kLabelPropertyIndexStr = "label_property_index";
constexpr const char *kTextIndexStr = "text_index";
constexpr const char *kExistenceConstraintsStr = "existence_constraints";
constexpr const char *kUniqueConstraintsStr = "unique_constraints";
} // namespace
@ -144,6 +145,29 @@ bool DurableMetadata::PersistLabelPropertyIndexAndExistenceConstraintDeletion(La
return true;
}
bool DurableMetadata::PersistTextIndexCreation(const std::string &index_name) {
if (auto text_index_store = durability_kvstore_.Get(kTextIndexStr); text_index_store.has_value()) {
std::string &value = text_index_store.value();
value += "|";
value += index_name;
return durability_kvstore_.Put(kTextIndexStr, value);
}
return durability_kvstore_.Put(kTextIndexStr, index_name);
}
bool DurableMetadata::PersistTextIndexDeletion(const std::string &index_name) {
if (auto text_index_store = durability_kvstore_.Get(kTextIndexStr); text_index_store.has_value()) {
const std::string &value = text_index_store.value();
std::vector<std::string> text_indices = utils::Split(value, "|");
std::erase(text_indices, index_name);
if (text_indices.empty()) {
return durability_kvstore_.Delete(kTextIndexStr);
}
return durability_kvstore_.Put(kTextIndexStr, utils::Join(text_indices, "|"));
}
return true;
}
bool DurableMetadata::PersistUniqueConstraintCreation(LabelId label, const std::set<PropertyId> &properties) {
const std::string entry = utils::GetKeyForUniqueConstraintsDurability(label, properties);

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -53,6 +53,10 @@ class DurableMetadata {
bool PersistLabelPropertyIndexAndExistenceConstraintDeletion(LabelId label, PropertyId property,
const std::string &key);
bool PersistTextIndexCreation(const std::string &index_name);
bool PersistTextIndexDeletion(const std::string &index_name);
bool PersistUniqueConstraintCreation(LabelId label, const std::set<PropertyId> &properties);
bool PersistUniqueConstraintDeletion(LabelId label, const std::set<PropertyId> &properties);

View File

@ -29,6 +29,7 @@
#include <rocksdb/utilities/transaction.h>
#include <rocksdb/utilities/transaction_db.h>
#include "flags/run_time_configurable.hpp"
#include "kvstore/kvstore.hpp"
#include "spdlog/spdlog.h"
#include "storage/v2/constraints/unique_constraints.hpp"
@ -846,6 +847,7 @@ StorageInfo DiskStorage::GetInfo(bool force_dir,
const auto &lbl = access->ListAllIndices();
info.label_indices = lbl.label.size();
info.label_property_indices = lbl.label_property.size();
info.text_indices = lbl.text_indices.size();
const auto &con = access->ListAllConstraints();
info.existence_constraints = con.existence.size();
info.unique_constraints = con.unique.size();
@ -1654,6 +1656,16 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_STATS_CLEAR: {
throw utils::NotYetImplemented("ClearIndexStats(stats) is not implemented for DiskStorage.");
} break;
case MetadataDelta::Action::TEXT_INDEX_CREATE: {
if (!disk_storage->durable_metadata_.PersistTextIndexCreation(md_delta.text_indices.index_name)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::TEXT_INDEX_DROP: {
if (!disk_storage->durable_metadata_.PersistTextIndexDeletion(md_delta.text_indices.index_name)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::EXISTENCE_CONSTRAINT_CREATE: {
const auto &info = md_delta.label_property;
if (!disk_storage->durable_metadata_.PersistLabelPropertyIndexAndExistenceConstraintCreation(
@ -1752,6 +1764,9 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co
return StorageManipulationError{SerializationError{}};
}
spdlog::trace("rocksdb: Commit successful");
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
disk_storage->indices_.text_index_.Commit();
}
is_transaction_active_ = false;
@ -1870,6 +1885,9 @@ void DiskStorage::DiskAccessor::Abort() {
// query_plan_accumulate_aggregate.cpp
transaction_.disk_transaction_->Rollback();
transaction_.disk_transaction_->ClearSnapshot();
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
storage_->indices_.text_index_.Rollback();
}
delete transaction_.disk_transaction_;
transaction_.disk_transaction_ = nullptr;
is_transaction_active_ = false;
@ -2059,7 +2077,8 @@ IndicesInfo DiskStorage::DiskAccessor::ListAllIndices() const {
auto *disk_label_index = static_cast<DiskLabelIndex *>(on_disk->indices_.label_index_.get());
auto *disk_label_property_index =
static_cast<DiskLabelPropertyIndex *>(on_disk->indices_.label_property_index_.get());
return {disk_label_index->ListIndices(), disk_label_property_index->ListIndices()};
auto &text_index = storage_->indices_.text_index_;
return {disk_label_index->ListIndices(), disk_label_property_index->ListIndices(), text_index.ListIndices()};
}
ConstraintsInfo DiskStorage::DiskAccessor::ListAllConstraints() const {
auto *disk_storage = static_cast<DiskStorage *>(storage_);

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -197,6 +197,22 @@ void RecoverIndicesAndStats(const RecoveredIndicesAndConstraints::IndicesMetadat
}
spdlog::info("Label+property indices statistics are recreated.");
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
// Recover text indices.
spdlog::info("Recreating {} text indices from metadata.", indices_metadata.text_indices.size());
auto &mem_text_index = indices->text_index_;
for (const auto &item : indices_metadata.text_indices) {
try {
mem_text_index.RecoverIndex(item.first, item.second, vertices->access(), name_id_mapper);
} catch (...) {
throw RecoveryFailure("The text index must be created here!");
}
spdlog::info("Text index {} on :{} is recreated from metadata", item.first,
name_id_mapper->IdToName(item.second.AsUint()));
}
}
spdlog::info("Text indices are recreated.");
spdlog::info("Indices are recreated.");
spdlog::info("Recreating constraints from metadata.");

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -60,6 +60,8 @@ enum class Marker : uint8_t {
DELTA_LABEL_INDEX_STATS_CLEAR = 0x62,
DELTA_LABEL_PROPERTY_INDEX_STATS_SET = 0x63,
DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR = 0x64,
DELTA_TEXT_INDEX_CREATE = 0x65,
DELTA_TEXT_INDEX_DROP = 0x66,
VALUE_FALSE = 0x00,
VALUE_TRUE = 0xff,
@ -103,6 +105,8 @@ static const Marker kMarkersAll[] = {
Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR,
Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE,
Marker::DELTA_LABEL_PROPERTY_INDEX_DROP,
Marker::DELTA_TEXT_INDEX_CREATE,
Marker::DELTA_TEXT_INDEX_DROP,
Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE,
Marker::DELTA_EXISTENCE_CONSTRAINT_DROP,
Marker::DELTA_UNIQUE_CONSTRAINT_CREATE,

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -43,6 +43,7 @@ struct RecoveredIndicesAndConstraints {
std::vector<std::pair<LabelId, PropertyId>> label_property;
std::vector<std::pair<LabelId, LabelIndexStats>> label_stats;
std::vector<std::pair<LabelId, std::pair<PropertyId, LabelPropertyIndexStats>>> label_property_stats;
std::vector<std::pair<std::string, LabelId>> text_indices;
} indices;
struct ConstraintsMetadata {

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -350,6 +350,8 @@ std::optional<PropertyValue> Decoder::ReadPropertyValue() {
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR:
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
case Marker::DELTA_TEXT_INDEX_CREATE:
case Marker::DELTA_TEXT_INDEX_DROP:
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
@ -453,6 +455,8 @@ bool Decoder::SkipPropertyValue() {
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR:
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
case Marker::DELTA_TEXT_INDEX_CREATE:
case Marker::DELTA_TEXT_INDEX_DROP:
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:

View File

@ -13,6 +13,7 @@
#include <thread>
#include "flags/run_time_configurable.hpp"
#include "spdlog/spdlog.h"
#include "storage/v2/durability/exceptions.hpp"
#include "storage/v2/durability/paths.hpp"
@ -1629,6 +1630,24 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
spdlog::info("Metadata of label+property indices are recovered.");
}
// Recover text indices.
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Couldn't recover the number of text indices!");
spdlog::info("Recovering metadata of {} text indices.", *size);
for (uint64_t i = 0; i < *size; ++i) {
auto index_name = snapshot.ReadString();
if (!index_name.has_value()) throw RecoveryFailure("Couldn't read text index name!");
auto label = snapshot.ReadUint();
if (!label) throw RecoveryFailure("Couldn't read text index label!");
AddRecoveredIndexConstraint(&indices_constraints.indices.text_indices,
{index_name.value(), get_label_from_id(*label)}, "The text index already exists!");
SPDLOG_TRACE("Recovered metadata of text index {} for :{}", index_name.value(),
name_id_mapper->IdToName(snapshot_id_map.at(*label)));
}
spdlog::info("Metadata of text indices are recovered.");
}
spdlog::info("Metadata of indices are recovered.");
}
@ -2105,6 +2124,16 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files
snapshot.SetPosition(last_pos);
}
}
// Write text indices.
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
auto text = storage->indices_.text_index_.ListIndices();
snapshot.WriteUint(text.size());
for (const auto &item : text) {
snapshot.WriteString(item.first);
write_mapping(item.second);
}
}
}
// Write constraints.

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -23,6 +23,8 @@ enum class StorageMetadataOperation {
LABEL_PROPERTY_INDEX_DROP,
LABEL_PROPERTY_INDEX_STATS_SET,
LABEL_PROPERTY_INDEX_STATS_CLEAR,
TEXT_INDEX_CREATE,
TEXT_INDEX_DROP,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -20,7 +20,7 @@ namespace memgraph::storage::durability {
// The current version of snapshot and WAL encoding / decoding.
// IMPORTANT: Please bump this version for every snapshot and/or WAL format
// change!!!
const uint64_t kVersion{16};
const uint64_t kVersion{17};
const uint64_t kOldestSupportedVersion{14};
const uint64_t kUniqueConstraintVersion{13};

View File

@ -95,6 +95,10 @@ Marker OperationToMarker(StorageMetadataOperation operation) {
return Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET;
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR:
return Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR;
case StorageMetadataOperation::TEXT_INDEX_CREATE:
return Marker::DELTA_TEXT_INDEX_CREATE;
case StorageMetadataOperation::TEXT_INDEX_DROP:
return Marker::DELTA_TEXT_INDEX_DROP;
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE:
return Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE;
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP:
@ -168,6 +172,10 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) {
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE;
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP;
case Marker::DELTA_TEXT_INDEX_CREATE:
return WalDeltaData::Type::TEXT_INDEX_CREATE;
case Marker::DELTA_TEXT_INDEX_DROP:
return WalDeltaData::Type::TEXT_INDEX_DROP;
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET:
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET;
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR:
@ -309,6 +317,8 @@ WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) {
} break;
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
case WalDeltaData::Type::TEXT_INDEX_CREATE:
case WalDeltaData::Type::TEXT_INDEX_DROP:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
if constexpr (read_data) {
@ -508,6 +518,8 @@ bool operator==(const WalDeltaData &a, const WalDeltaData &b) {
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
case WalDeltaData::Type::TEXT_INDEX_CREATE:
case WalDeltaData::Type::TEXT_INDEX_DROP:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
return a.operation_label_property.label == b.operation_label_property.label &&
@ -677,6 +689,8 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage
}
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE:
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP:
case StorageMetadataOperation::TEXT_INDEX_CREATE:
case StorageMetadataOperation::TEXT_INDEX_DROP:
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE:
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP: {
MG_ASSERT(properties.size() == 1, "Invalid function call!");
@ -933,6 +947,20 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst
"The label index stats doesn't exist!");
break;
}
case WalDeltaData::Type::TEXT_INDEX_CREATE: {
auto index_name = delta.operation_text.index_name;
auto label = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_text.label));
AddRecoveredIndexConstraint(&indices_constraints->indices.text_indices, {index_name, label},
"The text index already exists!");
break;
}
case WalDeltaData::Type::TEXT_INDEX_DROP: {
auto index_name = delta.operation_text.index_name;
auto label = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_text.label));
RemoveRecoveredIndexConstraint(&indices_constraints->indices.text_indices, {index_name, label},
"The text index doesn't exist!");
break;
}
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label));
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property));

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -67,6 +67,8 @@ struct WalDeltaData {
LABEL_PROPERTY_INDEX_DROP,
LABEL_PROPERTY_INDEX_STATS_SET,
LABEL_PROPERTY_INDEX_STATS_CLEAR,
TEXT_INDEX_CREATE,
TEXT_INDEX_DROP,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,
@ -121,6 +123,11 @@ struct WalDeltaData {
std::string property;
std::string stats;
} operation_label_property_stats;
struct {
std::string index_name;
std::string label;
} operation_text;
};
bool operator==(const WalDeltaData &a, const WalDeltaData &b);
@ -155,6 +162,8 @@ constexpr bool IsWalDeltaDataTypeTransactionEndVersion15(const WalDeltaData::Typ
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR:
case WalDeltaData::Type::TEXT_INDEX_CREATE:
case WalDeltaData::Type::TEXT_INDEX_DROP:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:

View File

@ -14,6 +14,7 @@
#include "storage/v2/disk/label_property_index.hpp"
#include "storage/v2/inmemory/label_index.hpp"
#include "storage/v2/inmemory/label_property_index.hpp"
#include "storage/v2/storage.hpp"
namespace memgraph::storage {
@ -38,7 +39,7 @@ void Indices::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std:
->RemoveObsoleteEntries(oldest_active_start_timestamp, std::move(token));
}
void Indices::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) const {
void Indices::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx, Storage *storage) const {
label_index_->UpdateOnAddLabel(label, vertex, tx);
label_property_index_->UpdateOnAddLabel(label, vertex, tx);
}
@ -49,7 +50,7 @@ void Indices::UpdateOnRemoveLabel(LabelId label, Vertex *vertex, const Transacti
}
void Indices::UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex,
const Transaction &tx) const {
const Transaction &tx, Storage *storage) const {
label_property_index_->UpdateOnSetProperty(property, value, vertex, tx);
}

View File

@ -17,6 +17,7 @@
#include "storage/v2/id_types.hpp"
#include "storage/v2/indices/label_index.hpp"
#include "storage/v2/indices/label_property_index.hpp"
#include "storage/v2/indices/text_index.hpp"
#include "storage/v2/storage_mode.hpp"
namespace memgraph::storage {
@ -30,12 +31,12 @@ struct Indices {
Indices &operator=(Indices &&) = delete;
~Indices() = default;
/// This function should be called from garbage collection to clean-up the
/// This function should be called from garbage collection to clean up the
/// index.
/// TODO: unused in disk indices
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) const;
/// Surgical removal of entries that was inserted this transaction
/// Surgical removal of entries that were inserted in this transaction
/// TODO: unused in disk indices
void AbortEntries(LabelId labelId, std::span<Vertex *const> vertices, uint64_t exact_start_timestamp) const;
void AbortEntries(PropertyId property, std::span<std::pair<PropertyValue, Vertex *> const> vertices,
@ -55,17 +56,18 @@ struct Indices {
/// This function should be called whenever a label is added to a vertex.
/// @throw std::bad_alloc
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) const;
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx, Storage *storage) const;
void UpdateOnRemoveLabel(LabelId label, Vertex *vertex, const Transaction &tx) const;
/// This function should be called whenever a property is modified on a vertex.
/// @throw std::bad_alloc
void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex,
const Transaction &tx) const;
void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, const Transaction &tx,
Storage *storage) const;
std::unique_ptr<LabelIndex> label_index_;
std::unique_ptr<LabelPropertyIndex> label_property_index_;
mutable TextIndex text_index_;
};
} // namespace memgraph::storage

View File

@ -0,0 +1,357 @@
// Copyright 2024 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 "storage/v2/indices/text_index.hpp"
#include "flags/run_time_configurable.hpp"
#include "query/db_accessor.hpp"
#include "storage/v2/view.hpp"
#include "text_search.hpp"
#include "utils/string.hpp"
namespace memgraph::storage {
std::string GetPropertyName(PropertyId prop_id, memgraph::query::DbAccessor *db) { return db->PropertyToName(prop_id); }
std::string GetPropertyName(PropertyId prop_id, NameIdMapper *name_id_mapper) {
return name_id_mapper->IdToName(prop_id.AsUint());
}
void TextIndex::CreateEmptyIndex(const std::string &index_name, LabelId label) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
try {
nlohmann::json mappings = {};
mappings["properties"] = {};
mappings["properties"]["metadata"] = {{"type", "json"}, {"fast", true}, {"stored", true}, {"text", true}};
mappings["properties"]["all"] = {{"type", "text"}, {"fast", true}, {"stored", true}, {"text", true}};
mappings["properties"]["data"] = {{"type", "json"}, {"fast", true}, {"stored", true}, {"text", true}};
index_.emplace(index_name,
TextIndexData{.context_ = mgcxx::text_search::create_index(
index_name, mgcxx::text_search::IndexConfig{.mappings = mappings.dump()}),
.scope_ = label});
} catch (const std::exception &e) {
throw query::TextSearchException("Tantivy error: {}", e.what());
}
label_to_index_.emplace(label, index_name);
}
std::string TextIndex::PropertiesToString(const std::map<PropertyId, PropertyValue> &properties) {
std::vector<std::string> indexable_properties_as_string;
for (const auto &[_, prop_value] : properties) {
switch (prop_value.type()) {
case PropertyValue::Type::Bool:
case PropertyValue::Type::Int:
case PropertyValue::Type::Double:
case PropertyValue::Type::String:
indexable_properties_as_string.push_back(prop_value.ValueString());
break;
case PropertyValue::Type::Null:
case PropertyValue::Type::List:
case PropertyValue::Type::Map:
case PropertyValue::Type::TemporalData:
default:
continue;
}
}
return utils::Join(indexable_properties_as_string, " ");
}
template <typename T>
nlohmann::json TextIndex::SerializeProperties(const std::map<PropertyId, PropertyValue> &properties, T *name_resolver) {
nlohmann::json serialized_properties = nlohmann::json::value_t::object;
for (const auto &[prop_id, prop_value] : properties) {
switch (prop_value.type()) {
case PropertyValue::Type::Bool:
serialized_properties[GetPropertyName(prop_id, name_resolver)] = prop_value.ValueBool();
break;
case PropertyValue::Type::Int:
serialized_properties[GetPropertyName(prop_id, name_resolver)] = prop_value.ValueInt();
break;
case PropertyValue::Type::Double:
serialized_properties[GetPropertyName(prop_id, name_resolver)] = prop_value.ValueDouble();
break;
case PropertyValue::Type::String:
serialized_properties[GetPropertyName(prop_id, name_resolver)] = prop_value.ValueString();
break;
case PropertyValue::Type::Null:
case PropertyValue::Type::List:
case PropertyValue::Type::Map:
case PropertyValue::Type::TemporalData:
default:
continue;
}
}
return serialized_properties;
}
std::vector<mgcxx::text_search::Context *> TextIndex::GetApplicableTextIndices(const std::vector<LabelId> &labels) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
std::vector<mgcxx::text_search::Context *> applicable_text_indices;
for (const auto &label : labels) {
if (label_to_index_.contains(label)) {
applicable_text_indices.push_back(&index_.at(label_to_index_.at(label)).context_);
}
}
return applicable_text_indices;
}
std::vector<mgcxx::text_search::Context *> TextIndex::GetApplicableTextIndices(Vertex *vertex) {
return GetApplicableTextIndices(vertex->labels);
}
void TextIndex::LoadNodeToTextIndices(const std::int64_t gid, const nlohmann::json &properties,
const std::string &indexable_properties_as_string,
const std::vector<mgcxx::text_search::Context *> &applicable_text_indices) {
// NOTE: Text indexes are presently all-property indices. If we allow text indexes restricted to specific properties,
// an indexable document should be created for each applicable index.
nlohmann::json document = {};
document["data"] = properties;
document["all"] = indexable_properties_as_string;
document["metadata"] = {};
document["metadata"]["gid"] = gid;
document["metadata"]["deleted"] = false;
document["metadata"]["is_node"] = true;
for (auto *index_context : applicable_text_indices) {
try {
mgcxx::text_search::add_document(
*index_context,
mgcxx::text_search::DocumentInput{
.data = document.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace)},
kDoSkipCommit);
} catch (const std::exception &e) {
throw query::TextSearchException("Tantivy error: {}", e.what());
}
}
}
void TextIndex::CommitLoadedNodes(mgcxx::text_search::Context &index_context) {
// As CREATE TEXT INDEX (...) queries dont accumulate deltas, db_transactional_accessor_->Commit() does not reach
// the code area where changes to indices are committed. To get around that without needing to commit text indices
// after every such query, we commit here.
try {
mgcxx::text_search::commit(index_context);
} catch (const std::exception &e) {
throw query::TextSearchException("Tantivy error: {}", e.what());
}
}
void TextIndex::AddNode(Vertex *vertex_after_update, NameIdMapper *name_id_mapper,
const std::vector<mgcxx::text_search::Context *> &applicable_text_indices) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
auto vertex_properties = vertex_after_update->properties.Properties();
LoadNodeToTextIndices(vertex_after_update->gid.AsInt(), SerializeProperties(vertex_properties, name_id_mapper),
PropertiesToString(vertex_properties), applicable_text_indices);
}
void TextIndex::AddNode(Vertex *vertex_after_update, NameIdMapper *name_id_mapper) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
auto applicable_text_indices = GetApplicableTextIndices(vertex_after_update);
if (applicable_text_indices.empty()) return;
AddNode(vertex_after_update, name_id_mapper, applicable_text_indices);
}
void TextIndex::UpdateNode(Vertex *vertex_after_update, NameIdMapper *name_id_mapper,
const std::vector<LabelId> &removed_labels) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
if (!removed_labels.empty()) {
RemoveNode(vertex_after_update, GetApplicableTextIndices(removed_labels));
}
auto applicable_text_indices = GetApplicableTextIndices(vertex_after_update);
if (applicable_text_indices.empty()) return;
RemoveNode(vertex_after_update, applicable_text_indices);
AddNode(vertex_after_update, name_id_mapper, applicable_text_indices);
}
void TextIndex::RemoveNode(Vertex *vertex_after_update,
const std::vector<mgcxx::text_search::Context *> &applicable_text_indices) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
auto search_node_to_be_deleted =
mgcxx::text_search::SearchInput{.search_query = fmt::format("metadata.gid:{}", vertex_after_update->gid.AsInt())};
for (auto *index_context : applicable_text_indices) {
try {
mgcxx::text_search::delete_document(*index_context, search_node_to_be_deleted, kDoSkipCommit);
} catch (const std::exception &e) {
throw query::TextSearchException("Tantivy error: {}", e.what());
}
}
}
void TextIndex::RemoveNode(Vertex *vertex_after_update) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
auto applicable_text_indices = GetApplicableTextIndices(vertex_after_update);
if (applicable_text_indices.empty()) return;
RemoveNode(vertex_after_update, applicable_text_indices);
}
void TextIndex::CreateIndex(const std::string &index_name, LabelId label, memgraph::query::DbAccessor *db) {
CreateEmptyIndex(index_name, label);
for (const auto &v : db->Vertices(View::NEW)) {
if (!v.HasLabel(View::NEW, label).GetValue()) {
continue;
}
auto vertex_properties = v.Properties(View::NEW).GetValue();
LoadNodeToTextIndices(v.Gid().AsInt(), SerializeProperties(vertex_properties, db),
PropertiesToString(vertex_properties), {&index_.at(index_name).context_});
}
CommitLoadedNodes(index_.at(index_name).context_);
}
void TextIndex::RecoverIndex(const std::string &index_name, LabelId label,
memgraph::utils::SkipList<Vertex>::Accessor vertices, NameIdMapper *name_id_mapper) {
CreateEmptyIndex(index_name, label);
for (const auto &v : vertices) {
if (std::find(v.labels.begin(), v.labels.end(), label) == v.labels.end()) {
continue;
}
nlohmann::json document = {};
auto vertex_properties = v.properties.Properties();
LoadNodeToTextIndices(v.gid.AsInt(), SerializeProperties(vertex_properties, name_id_mapper),
PropertiesToString(vertex_properties), {&index_.at(index_name).context_});
}
CommitLoadedNodes(index_.at(index_name).context_);
}
void TextIndex::DropIndex(const std::string &index_name) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
try {
mgcxx::text_search::drop_index(index_name);
} catch (const std::exception &e) {
throw query::TextSearchException("Tantivy error: {}", e.what());
}
index_.erase(index_name);
std::erase_if(label_to_index_, [index_name](const auto &item) { return item.second == index_name; });
}
bool TextIndex::IndexExists(const std::string &index_name) const { return index_.contains(index_name); }
std::vector<Gid> TextIndex::Search(const std::string &index_name, const std::string &search_query) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
if (!index_.contains(index_name)) {
throw query::TextSearchException("Text index \"{}\" doesnt exist.", index_name);
}
auto input = mgcxx::text_search::SearchInput{.search_query = search_query, .return_fields = {"data", "metadata"}};
// Basic check for search fields in the query (Tantivy syntax delimits them with a `:` to the right)
if (search_query.find(":") == std::string::npos) {
input.search_fields = {"data"};
}
std::vector<Gid> found_nodes;
mgcxx::text_search::SearchOutput search_results;
try {
search_results = mgcxx::text_search::search(index_.at(index_name).context_, input);
} catch (const std::exception &e) {
throw query::TextSearchException("Tantivy error: {}", e.what());
}
for (const auto &doc : search_results.docs) {
// The CXX .data() method (https://cxx.rs/binding/string.html) may overestimate string length, causing JSON parsing
// errors downstream. We prevent this by resizing the converted string with the correctly-working .length() method.
std::string doc_string = doc.data.data();
doc_string.resize(doc.data.length());
auto doc_json = nlohmann::json::parse(doc_string);
found_nodes.push_back(storage::Gid::FromString(doc_json["metadata"]["gid"].dump()));
}
return found_nodes;
}
std::vector<Gid> TextIndex::RegexSearch(const std::string &index_name, const std::string &search_query) {
if (!flags::run_time::GetExperimentalTextSearchEnabled()) {
throw query::TextSearchDisabledException();
}
if (!index_.contains(index_name)) {
throw query::TextSearchException("Text index \"{}\" doesnt exist.", index_name);
}
auto input = mgcxx::text_search::SearchInput{
.search_fields = {"all"}, .search_query = search_query, .return_fields = {"data", "metadata"}};
std::vector<Gid> found_nodes;
mgcxx::text_search::SearchOutput search_results;
try {
search_results = mgcxx::text_search::regex_search(index_.at(index_name).context_, input);
} catch (const std::exception &e) {
throw query::TextSearchException("Tantivy error: {}", e.what());
}
for (const auto &doc : search_results.docs) {
// The CXX .data() method (https://cxx.rs/binding/string.html) may overestimate string length, causing JSON parsing
// errors downstream. We prevent this by resizing the converted string with the correctly-working .length() method.
std::string doc_string = doc.data.data();
doc_string.resize(doc.data.length());
auto doc_json = nlohmann::json::parse(doc_string);
found_nodes.push_back(storage::Gid::FromString(doc_json["metadata"]["gid"].dump()));
}
return found_nodes;
}
void TextIndex::Commit() {
for (auto &[_, index_data] : index_) {
mgcxx::text_search::commit(index_data.context_);
}
}
void TextIndex::Rollback() {
for (auto &[_, index_data] : index_) {
mgcxx::text_search::rollback(index_data.context_);
}
}
std::vector<std::pair<std::string, LabelId>> TextIndex::ListIndices() const {
std::vector<std::pair<std::string, LabelId>> ret;
ret.reserve(index_.size());
for (const auto &[index_name, index_data] : index_) {
ret.emplace_back(index_name, index_data.scope_);
}
return ret;
}
std::uint64_t TextIndex::ApproximateVertexCount(const std::string &index_name) const { return 10; }
} // namespace memgraph::storage

View File

@ -0,0 +1,100 @@
// Copyright 2024 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.
#pragma once
#include <json/json.hpp>
#include "storage/v2/id_types.hpp"
#include "storage/v2/name_id_mapper.hpp"
#include "storage/v2/transaction.hpp"
#include "storage/v2/vertex.hpp"
#include "text_search.hpp"
namespace memgraph::query {
class DbAccessor;
}
namespace memgraph::storage {
class Storage;
constexpr bool kDoSkipCommit = true;
struct TextIndexData {
mgcxx::text_search::Context context_;
LabelId scope_;
};
class TextIndex {
private:
void CreateEmptyIndex(const std::string &index_name, LabelId label);
std::string PropertiesToString(const std::map<PropertyId, PropertyValue> &properties);
template <typename T>
nlohmann::json SerializeProperties(const std::map<PropertyId, PropertyValue> &properties, T *name_resolver);
std::vector<mgcxx::text_search::Context *> GetApplicableTextIndices(const std::vector<LabelId> &labels);
std::vector<mgcxx::text_search::Context *> GetApplicableTextIndices(Vertex *vertex);
void LoadNodeToTextIndices(const std::int64_t gid, const nlohmann::json &properties,
const std::string &indexable_properties_as_string,
const std::vector<mgcxx::text_search::Context *> &applicable_text_indices);
void CommitLoadedNodes(mgcxx::text_search::Context &index_context);
void AddNode(Vertex *vertex, NameIdMapper *name_id_mapper,
const std::vector<mgcxx::text_search::Context *> &applicable_text_indices);
void RemoveNode(Vertex *vertex, const std::vector<mgcxx::text_search::Context *> &applicable_text_indices);
public:
TextIndex() = default;
TextIndex(const TextIndex &) = delete;
TextIndex(TextIndex &&) = delete;
TextIndex &operator=(const TextIndex &) = delete;
TextIndex &operator=(TextIndex &&) = delete;
~TextIndex() = default;
std::map<std::string, TextIndexData> index_;
std::map<LabelId, std::string> label_to_index_;
void AddNode(Vertex *vertex, NameIdMapper *name_id_mapper);
void UpdateNode(Vertex *vertex, NameIdMapper *name_id_mapper, const std::vector<LabelId> &removed_labels = {});
void RemoveNode(Vertex *vertex);
void CreateIndex(const std::string &index_name, LabelId label, memgraph::query::DbAccessor *db);
void RecoverIndex(const std::string &index_name, LabelId label, memgraph::utils::SkipList<Vertex>::Accessor vertices,
NameIdMapper *name_id_mapper);
void DropIndex(const std::string &index_name);
bool IndexExists(const std::string &index_name) const;
std::vector<Gid> Search(const std::string &index_name, const std::string &search_query);
std::vector<Gid> RegexSearch(const std::string &index_name, const std::string &search_query);
void Commit();
void Rollback();
std::vector<std::pair<std::string, LabelId>> ListIndices() const;
std::uint64_t ApproximateVertexCount(const std::string &index_name) const;
};
} // namespace memgraph::storage

View File

@ -14,6 +14,7 @@
#include <functional>
#include <optional>
#include "dbms/constants.hpp"
#include "flags/run_time_configurable.hpp"
#include "memory/global_memory_control.hpp"
#include "storage/v2/durability/durability.hpp"
#include "storage/v2/durability/snapshot.hpp"
@ -874,6 +875,10 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
commit_timestamp_.reset(); // We have aborted, hence we have not committed
return StorageManipulationError{*unique_constraint_violation};
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
mem_storage->indices_.text_index_.Commit();
}
}
is_transaction_active_ = false;
@ -1196,6 +1201,9 @@ void InMemoryStorage::InMemoryAccessor::Abort() {
for (auto const &[property, prop_vertices] : property_cleanup) {
storage_->indices_.AbortEntries(property, prop_vertices, transaction_.start_timestamp);
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
storage_->indices_.text_index_.Rollback();
}
// VERTICES
{
@ -1795,6 +1803,7 @@ StorageInfo InMemoryStorage::GetInfo(bool force_directory,
const auto &lbl = access->ListAllIndices();
info.label_indices = lbl.label.size();
info.label_property_indices = lbl.label_property.size();
info.text_indices = lbl.text_indices.size();
const auto &con = access->ListAllConstraints();
info.existence_constraints = con.existence.size();
info.unique_constraints = con.unique.size();
@ -2048,6 +2057,16 @@ bool InMemoryStorage::AppendToWal(const Transaction &transaction, uint64_t final
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR, info.label,
final_commit_timestamp);
} break;
case MetadataDelta::Action::TEXT_INDEX_CREATE: {
const auto &info = md_delta.text_indices;
AppendToWalDataDefinition(durability::StorageMetadataOperation::TEXT_INDEX_CREATE, info.index_name, info.label,
final_commit_timestamp);
} break;
case MetadataDelta::Action::TEXT_INDEX_DROP: {
const auto &info = md_delta.text_indices;
AppendToWalDataDefinition(durability::StorageMetadataOperation::TEXT_INDEX_DROP, info.index_name, info.label,
final_commit_timestamp);
} break;
case MetadataDelta::Action::EXISTENCE_CONSTRAINT_CREATE: {
const auto &info = md_delta.label_property;
AppendToWalDataDefinition(durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE, info.label,
@ -2109,6 +2128,11 @@ void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOpera
return AppendToWalDataDefinition(operation, label, {}, {}, final_commit_timestamp);
}
void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, std::string index_name,
LabelId label, uint64_t final_commit_timestamp) {
return AppendToWalDataDefinition(operation, label, {}, {}, final_commit_timestamp);
}
utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot(
memgraph::replication_coordination_glue::ReplicationRole replication_role) {
if (replication_role == memgraph::replication_coordination_glue::ReplicationRole::REPLICA) {
@ -2250,7 +2274,8 @@ IndicesInfo InMemoryStorage::InMemoryAccessor::ListAllIndices() const {
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(in_memory->indices_.label_index_.get());
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(in_memory->indices_.label_property_index_.get());
return {mem_label_index->ListIndices(), mem_label_property_index->ListIndices()};
auto &text_index = storage_->indices_.text_index_;
return {mem_label_index->ListIndices(), mem_label_property_index->ListIndices(), text_index.ListIndices()};
}
ConstraintsInfo InMemoryStorage::InMemoryAccessor::ListAllConstraints() const {
const auto *mem_storage = static_cast<InMemoryStorage *>(storage_);

View File

@ -378,20 +378,23 @@ class InMemoryStorage final : public Storage {
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
/// Return true in all cases except if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
/// Return true in all cases except if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label, LabelIndexStats stats,
uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
/// Return true in all cases except if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, LabelPropertyIndexStats property_stats,
uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
/// Return true in all cases except if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, LabelIndexStats stats,
LabelPropertyIndexStats property_stats, uint64_t final_commit_timestamp);
/// Return true in all cases except if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, std::string index_name, LabelId label,
uint64_t final_commit_timestamp);
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -35,6 +35,8 @@ struct MetadataDelta {
LABEL_PROPERTY_INDEX_DROP,
LABEL_PROPERTY_INDEX_STATS_SET,
LABEL_PROPERTY_INDEX_STATS_CLEAR,
TEXT_INDEX_CREATE,
TEXT_INDEX_DROP,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,
@ -57,6 +59,10 @@ struct MetadataDelta {
} label_property_index_stats_set;
static constexpr struct LabelPropertyIndexStatsClear {
} label_property_index_stats_clear;
static constexpr struct TextIndexCreate {
} text_index_create;
static constexpr struct TextIndexDrop {
} text_index_drop;
static constexpr struct ExistenceConstraintCreate {
} existence_constraint_create;
static constexpr struct ExistenceConstraintDrop {
@ -87,6 +93,12 @@ struct MetadataDelta {
MetadataDelta(LabelPropertyIndexStatsClear /*tag*/, LabelId label)
: action(Action::LABEL_PROPERTY_INDEX_STATS_CLEAR), label{label} {}
MetadataDelta(TextIndexCreate /*tag*/, std::string index_name, LabelId label)
: action(Action::TEXT_INDEX_CREATE), text_indices{index_name, label} {}
MetadataDelta(TextIndexDrop /*tag*/, std::string index_name, LabelId label)
: action(Action::TEXT_INDEX_DROP), text_indices{index_name, label} {}
MetadataDelta(ExistenceConstraintCreate /*tag*/, LabelId label, PropertyId property)
: action(Action::EXISTENCE_CONSTRAINT_CREATE), label_property{label, property} {}
@ -114,6 +126,8 @@ struct MetadataDelta {
case Action::LABEL_PROPERTY_INDEX_DROP:
case Action::LABEL_PROPERTY_INDEX_STATS_SET:
case Action::LABEL_PROPERTY_INDEX_STATS_CLEAR:
case Action::TEXT_INDEX_CREATE:
case Action::TEXT_INDEX_DROP:
case Action::EXISTENCE_CONSTRAINT_CREATE:
case Action::EXISTENCE_CONSTRAINT_DROP:
break;
@ -149,6 +163,11 @@ struct MetadataDelta {
PropertyId property;
LabelPropertyIndexStats stats;
} label_property_stats;
struct {
std::string index_name;
LabelId label;
} text_indices;
};
};

View File

@ -105,7 +105,7 @@ enum class Type : uint8_t {
STRING = 0x50,
LIST = 0x60,
MAP = 0x70,
TEMPORAL_DATA = 0x80
TEMPORAL_DATA = 0x80,
};
const uint8_t kMaskType = 0xf0;

View File

@ -13,6 +13,7 @@
#include "absl/container/flat_hash_set.h"
#include "spdlog/spdlog.h"
#include "flags/run_time_configurable.hpp"
#include "storage/v2/disk/name_id_mapper.hpp"
#include "storage/v2/storage.hpp"
#include "storage/v2/transaction.hpp"
@ -148,6 +149,10 @@ Result<std::optional<VertexAccessor>> Storage::Accessor::DeleteVertex(VertexAcce
return res.GetError();
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
storage_->indices_.text_index_.RemoveNode(vertex->vertex_);
}
const auto &value = res.GetValue();
if (!value) {
return std::optional<VertexAccessor>{};
@ -185,6 +190,10 @@ Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> Stor
return res.GetError();
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
storage_->indices_.text_index_.RemoveNode(vertex->vertex_);
}
auto &value = res.GetValue();
if (!value) {
return std::optional<ReturnType>{};
@ -273,6 +282,12 @@ Storage::Accessor::DetachDelete(std::vector<VertexAccessor *> nodes, std::vector
return maybe_deleted_vertices.GetError();
}
if (flags::run_time::GetExperimentalTextSearchEnabled()) {
for (auto *node : nodes_to_delete) {
storage_->indices_.text_index_.RemoveNode(node);
}
}
auto deleted_vertices = maybe_deleted_vertices.GetValue();
return std::make_optional<ReturnType>(std::move(deleted_vertices), std::move(deleted_edges));

View File

@ -61,6 +61,7 @@ class EdgeAccessor;
struct IndicesInfo {
std::vector<LabelId> label;
std::vector<std::pair<LabelId, PropertyId>> label_property;
std::vector<std::pair<std::string, LabelId>> text_indices;
};
struct ConstraintsInfo {
@ -76,6 +77,7 @@ struct StorageInfo {
uint64_t disk_usage;
uint64_t label_indices;
uint64_t label_property_indices;
uint64_t text_indices;
uint64_t existence_constraints;
uint64_t unique_constraints;
StorageMode storage_mode;
@ -93,6 +95,7 @@ static inline nlohmann::json ToJson(const StorageInfo &info) {
res["disk"] = info.disk_usage;
res["label_indices"] = info.label_indices;
res["label_prop_indices"] = info.label_property_indices;
res["text_indices"] = info.text_indices;
res["existence_constraints"] = info.existence_constraints;
res["unique_constraints"] = info.unique_constraints;
res["storage_mode"] = storage::StorageModeToString(info.storage_mode);
@ -224,6 +227,30 @@ class Storage {
virtual bool LabelPropertyIndexExists(LabelId label, PropertyId property) const = 0;
bool TextIndexExists(const std::string &index_name) const {
return storage_->indices_.text_index_.IndexExists(index_name);
}
void TextIndexAddVertex(VertexAccessor *vertex) {
storage_->indices_.text_index_.AddNode(vertex->vertex_, storage_->name_id_mapper_.get());
}
void TextIndexUpdateVertex(VertexAccessor *vertex) {
storage_->indices_.text_index_.UpdateNode(vertex->vertex_, storage_->name_id_mapper_.get());
}
void TextIndexUpdateVertex(VertexAccessor *vertex, std::vector<LabelId> removed_labels) {
storage_->indices_.text_index_.UpdateNode(vertex->vertex_, storage_->name_id_mapper_.get(), removed_labels);
}
std::vector<Gid> TextIndexSearch(const std::string &index_name, const std::string &search_query) const {
return storage_->indices_.text_index_.Search(index_name, search_query);
}
std::vector<Gid> TextIndexRegexSearch(const std::string &index_name, const std::string &search_query) const {
return storage_->indices_.text_index_.RegexSearch(index_name, search_query);
}
virtual IndicesInfo ListAllIndices() const = 0;
virtual ConstraintsInfo ListAllConstraints() const = 0;
@ -268,6 +295,12 @@ class Storage {
virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) = 0;
void CreateTextIndex(const std::string &index_name, LabelId label, query::DbAccessor *db) {
storage_->indices_.text_index_.CreateIndex(index_name, label, db);
}
void DropTextIndex(const std::string &index_name) { storage_->indices_.text_index_.DropIndex(index_name); }
virtual utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property) = 0;

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -15,6 +15,8 @@
#include <tuple>
#include <utility>
#include <fmt/format.h>
#include "query/exceptions.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/edge_accessor.hpp"
@ -26,6 +28,7 @@
#include "storage/v2/storage.hpp"
#include "storage/v2/vertex_info_cache.hpp"
#include "storage/v2/vertex_info_helpers.hpp"
#include "text_search.hpp"
#include "utils/atomic_memory_block.hpp"
#include "utils/logging.hpp"
#include "utils/memory_tracker.hpp"
@ -121,7 +124,7 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) {
/// TODO: some by pointers, some by reference => not good, make it better
storage_->constraints_.unique_constraints_->UpdateOnAddLabel(label, *vertex_, transaction_->start_timestamp);
transaction_->constraint_verification_info.AddedLabel(vertex_);
storage_->indices_.UpdateOnAddLabel(label, vertex_, *transaction_);
storage_->indices_.UpdateOnAddLabel(label, vertex_, *transaction_, storage_);
transaction_->manyDeltasCache.Invalidate(vertex_, label);
return true;
@ -281,7 +284,7 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
} else {
transaction_->constraint_verification_info.RemovedProperty(vertex_);
}
storage_->indices_.UpdateOnSetProperty(property, value, vertex_, *transaction_);
storage_->indices_.UpdateOnSetProperty(property, value, vertex_, *transaction_, storage_);
transaction_->manyDeltasCache.Invalidate(vertex_, property);
return std::move(current_value);
@ -307,7 +310,7 @@ Result<bool> VertexAccessor::InitProperties(const std::map<storage::PropertyId,
}
for (const auto &[property, value] : properties) {
CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, PropertyValue());
storage->indices_.UpdateOnSetProperty(property, value, vertex, *transaction);
storage->indices_.UpdateOnSetProperty(property, value, vertex, *transaction, storage);
transaction->manyDeltasCache.Invalidate(vertex, property);
if (!value.IsNull()) {
transaction->constraint_verification_info.AddedProperty(vertex);
@ -340,11 +343,12 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> Vertex
utils::AtomicMemoryBlock atomic_memory_block{
[storage = storage_, transaction = transaction_, vertex = vertex_, &properties, &id_old_new_change]() {
id_old_new_change.emplace(vertex->properties.UpdateProperties(properties));
if (!id_old_new_change.has_value()) {
return;
}
for (auto &[id, old_value, new_value] : *id_old_new_change) {
storage->indices_.UpdateOnSetProperty(id, new_value, vertex, *transaction);
storage->indices_.UpdateOnSetProperty(id, new_value, vertex, *transaction, storage);
CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), id, std::move(old_value));
transaction->manyDeltasCache.Invalidate(vertex, id);
if (!new_value.IsNull()) {
@ -379,10 +383,11 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
}
for (const auto &[property, value] : *properties) {
CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, value);
storage->indices_.UpdateOnSetProperty(property, PropertyValue(), vertex, *transaction);
storage->indices_.UpdateOnSetProperty(property, PropertyValue(), vertex, *transaction, storage);
transaction->constraint_verification_info.RemovedProperty(vertex);
transaction->manyDeltasCache.Invalidate(vertex, property);
}
vertex->properties.ClearProperties();
}};
std::invoke(atomic_memory_block);

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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

View File

@ -173,6 +173,7 @@ enum class TypeId : uint64_t {
AST_EXPLAIN_QUERY,
AST_PROFILE_QUERY,
AST_INDEX_QUERY,
AST_TEXT_INDEX_QUERY,
AST_CREATE,
AST_CALL_PROCEDURE,
AST_MATCH,

View File

@ -0,0 +1,6 @@
function(copy_text_search_e2e_python_files FILE_NAME)
copy_e2e_python_files(text_search ${FILE_NAME})
endfunction()
copy_text_search_e2e_python_files(common.py)
copy_text_search_e2e_python_files(test_text_search.py)

View File

@ -0,0 +1,84 @@
# 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.
import typing
import mgclient
import pytest
from gqlalchemy import Memgraph
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
cursor.execute(query, params)
return cursor.fetchall()
@pytest.fixture
def connect(**kwargs) -> mgclient.Connection:
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
cursor = connection.cursor()
execute_and_fetch_all(cursor, """USE DATABASE memgraph""")
try:
execute_and_fetch_all(cursor, """DROP DATABASE clean""")
except:
pass
execute_and_fetch_all(cursor, """MATCH (n) DETACH DELETE n""")
yield connection
@pytest.fixture
def memgraph(**kwargs) -> Memgraph:
memgraph = Memgraph()
yield memgraph
memgraph.drop_database()
memgraph.drop_indexes()
@pytest.fixture
def memgraph_with_text_indexed_data(**kwargs) -> Memgraph:
memgraph = Memgraph()
memgraph.execute(
"""CREATE (:Document {title: "Rules2024", version: 1, fulltext: "random fulltext", date: date("2023-11-14")});"""
)
memgraph.execute(
"""CREATE (:Document:Revision {title: "Rules2024", version: 2, fulltext: "random words", date: date("2023-12-15")});"""
)
memgraph.execute("""CREATE (:Revision {title: "OperationSchema", version: 3, date: date("2023-10-01")});""")
memgraph.execute("""CREATE TEXT INDEX complianceDocuments ON :Document;""")
yield memgraph
memgraph.execute("""DROP TEXT INDEX complianceDocuments;""")
memgraph.drop_database()
memgraph.drop_indexes()
@pytest.fixture
def memgraph_with_mixed_data(**kwargs) -> Memgraph:
memgraph = Memgraph()
memgraph.execute(
"""CREATE (:Document:Revision {title: "Rules2024", version: 1, date: date("2023-11-14"), contents: "Lorem ipsum dolor sit amet"});"""
)
memgraph.execute(
"""CREATE (:Revision {title: "Rules2024", version: 2, date: date("2023-12-15"), contents: "consectetur adipiscing elit"});"""
)
memgraph.execute("""CREATE TEXT INDEX complianceDocuments ON :Document;""")
yield memgraph
memgraph.execute("""DROP TEXT INDEX complianceDocuments;""")
memgraph.drop_database()
memgraph.drop_indexes()

View File

@ -0,0 +1,131 @@
# Copyright 2024 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.
import sys
import mgclient
import pytest
from common import memgraph, memgraph_with_mixed_data, memgraph_with_text_indexed_data
GET_RULES_2024_DOCUMENT = """CALL text_search.search("complianceDocuments", "data.title:Rules2024") YIELD node
RETURN node.title AS title, node.version AS version
ORDER BY version ASC, title ASC;"""
def test_create_index(memgraph):
memgraph.execute("""CREATE TEXT INDEX exampleIndex ON :Document;""")
index_info = memgraph.execute_and_fetch("""SHOW INDEX INFO""")
assert list(index_info) == [{"index type": "text", "label": "exampleIndex", "property": None, "count": None}]
def test_drop_index(memgraph):
memgraph.execute("""DROP TEXT INDEX exampleIndex;""")
index_info = memgraph.execute_and_fetch("""SHOW INDEX INFO""")
assert list(index_info) == []
def test_text_search_given_property(memgraph_with_text_indexed_data):
result = list(memgraph_with_text_indexed_data.execute_and_fetch(GET_RULES_2024_DOCUMENT))
assert len(result) == 2 and result == [{"title": "Rules2024", "version": 1}, {"title": "Rules2024", "version": 2}]
def test_text_search_query_boolean(memgraph_with_text_indexed_data):
BOOLEAN_QUERY = """CALL text_search.search("complianceDocuments", "(data.title:Rules2023 OR data.title:Rules2024) AND data.fulltext:words") YIELD node
RETURN node.title AS title, node.version AS version
ORDER BY version ASC, title ASC;"""
result = list(memgraph_with_text_indexed_data.execute_and_fetch(BOOLEAN_QUERY))
assert len(result) == 1 and result == [{"title": "Rules2024", "version": 2}]
def test_create_indexed_node(memgraph_with_text_indexed_data):
memgraph_with_text_indexed_data.execute("""CREATE (:Document {title: "Rules2024", version: 3});""")
result = list(memgraph_with_text_indexed_data.execute_and_fetch(GET_RULES_2024_DOCUMENT))
assert len(result) == 3 and result == [
{"title": "Rules2024", "version": 1},
{"title": "Rules2024", "version": 2},
{"title": "Rules2024", "version": 3},
]
def test_delete_indexed_node(memgraph_with_text_indexed_data):
memgraph_with_text_indexed_data.execute("""MATCH (n:Document {title: "Rules2024", version: 2}) DETACH DELETE n;""")
result = list(memgraph_with_text_indexed_data.execute_and_fetch(GET_RULES_2024_DOCUMENT))
assert len(result) == 1 and result == [{"title": "Rules2024", "version": 1}]
def test_add_indexed_label(memgraph_with_mixed_data):
memgraph_with_mixed_data.execute("""MATCH (n:Revision {version:2}) SET n:Document;""")
result = list(memgraph_with_mixed_data.execute_and_fetch(GET_RULES_2024_DOCUMENT))
assert len(result) == 2 and result == [{"title": "Rules2024", "version": 1}, {"title": "Rules2024", "version": 2}]
def test_remove_indexed_label(memgraph_with_mixed_data):
memgraph_with_mixed_data.execute("""MATCH (n:Document {version: 1}) REMOVE n:Document;""")
result = list(memgraph_with_mixed_data.execute_and_fetch(GET_RULES_2024_DOCUMENT))
assert len(result) == 0
def test_update_text_property_of_indexed_node(memgraph_with_text_indexed_data):
memgraph_with_text_indexed_data.execute("""MATCH (n:Document {version:1}) SET n.title = "Rules2030";""")
result = list(
memgraph_with_text_indexed_data.execute_and_fetch(
"""CALL text_search.search("complianceDocuments", "data.title:Rules2030") YIELD node
RETURN node.title AS title, node.version AS version
ORDER BY version ASC, title ASC;"""
)
)
assert len(result) == 1 and result == [{"title": "Rules2030", "version": 1}]
def test_add_non_text_property_to_indexed_node(memgraph_with_text_indexed_data):
memgraph_with_text_indexed_data.execute("""MATCH (n:Document {version:1}) SET n.randomList = [2, 3, 4, 5];""")
def test_remove_indexable_property_from_indexed_node(memgraph_with_text_indexed_data):
memgraph_with_text_indexed_data.execute(
"""MATCH (n:Document {version:1}) REMOVE n.title, n.version, n.fulltext, n.date;"""
)
def test_remove_non_text_property_from_indexed_node(memgraph_with_text_indexed_data):
memgraph_with_text_indexed_data.execute_and_fetch(
"""MATCH (n:Document {date: date("2023-12-15")}) REMOVE n.date;"""
)
def test_text_search_nonexistent_index(memgraph_with_text_indexed_data):
NONEXISTENT_INDEX_QUERY = """CALL text_search.search("noSuchIndex", "data.fulltext:words") YIELD node
RETURN node.title AS title, node.version AS version
ORDER BY version ASC, title ASC;"""
with pytest.raises(mgclient.DatabaseError, match='Text index "noSuchIndex" doesnt exist.') as _:
list(memgraph_with_text_indexed_data.execute_and_fetch(NONEXISTENT_INDEX_QUERY))
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,14 @@
text_search_cluster: &text_search_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE", "--experimental-text-search-enabled=true"]
log_file: "text_search.log"
setup_queries: []
validation_queries: []
workloads:
- name: "Test text search behavior in Memgraph"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/text_search/query_modules/"
args: ["text_search/test_text_search.py"]
<<: *text_search_cluster

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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

View File

@ -69,6 +69,11 @@ struct DatabaseState {
std::string property;
};
struct TextItem {
std::string index_name;
std::string label;
};
struct LabelPropertiesItem {
std::string label;
std::set<std::string, std::less<>> properties;
@ -78,6 +83,7 @@ struct DatabaseState {
std::set<Edge> edges;
std::set<LabelItem> label_indices;
std::set<LabelPropertyItem> label_property_indices;
std::set<TextItem> text_indices;
std::set<LabelPropertyItem> existence_constraints;
std::set<LabelPropertiesItem> unique_constraints;
};
@ -183,6 +189,7 @@ DatabaseState GetState(memgraph::storage::Storage *db) {
// Capture all indices
std::set<DatabaseState::LabelItem> label_indices;
std::set<DatabaseState::LabelPropertyItem> label_property_indices;
std::set<DatabaseState::TextItem> text_indices;
{
auto info = dba->ListAllIndices();
for (const auto &item : info.label) {
@ -191,6 +198,9 @@ DatabaseState GetState(memgraph::storage::Storage *db) {
for (const auto &item : info.label_property) {
label_property_indices.insert({dba->LabelToName(item.first), dba->PropertyToName(item.second)});
}
for (const auto &item : info.text_indices) {
text_indices.insert({item.first, dba->LabelToName(item.second)});
}
}
// Capture all constraints
@ -210,7 +220,8 @@ DatabaseState GetState(memgraph::storage::Storage *db) {
}
}
return {vertices, edges, label_indices, label_property_indices, existence_constraints, unique_constraints};
return {vertices, edges, label_indices, label_property_indices, text_indices, existence_constraints,
unique_constraints};
}
auto Execute(memgraph::query::InterpreterContext *context, memgraph::dbms::DatabaseAccess db,

View File

@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@ -355,6 +355,8 @@ TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) {
case memgraph::storage::durability::Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
case memgraph::storage::durability::Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET:
case memgraph::storage::durability::Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR:
case memgraph::storage::durability::Marker::DELTA_TEXT_INDEX_CREATE:
case memgraph::storage::durability::Marker::DELTA_TEXT_INDEX_DROP:
case memgraph::storage::durability::Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
case memgraph::storage::durability::Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
case memgraph::storage::durability::Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:

View File

@ -146,6 +146,7 @@ TYPED_TEST(InfoTest, InfoCheck) {
ASSERT_LT(info.disk_usage, 1000'000);
ASSERT_EQ(info.label_indices, 1);
ASSERT_EQ(info.label_property_indices, 1);
ASSERT_EQ(info.text_indices, 0);
ASSERT_EQ(info.existence_constraints, 0);
ASSERT_EQ(info.unique_constraints, 2);
ASSERT_EQ(info.storage_mode, this->mode);

View File

@ -49,6 +49,10 @@ memgraph::storage::durability::WalDeltaData::Type StorageMetadataOperationToWalD
return memgraph::storage::durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET;
case memgraph::storage::durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR:
return memgraph::storage::durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR;
case memgraph::storage::durability::StorageMetadataOperation::TEXT_INDEX_CREATE:
return memgraph::storage::durability::WalDeltaData::Type::TEXT_INDEX_CREATE;
case memgraph::storage::durability::StorageMetadataOperation::TEXT_INDEX_DROP:
return memgraph::storage::durability::WalDeltaData::Type::TEXT_INDEX_DROP;
case memgraph::storage::durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE:
return memgraph::storage::durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE;
case memgraph::storage::durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP:
@ -267,6 +271,8 @@ class DeltaGenerator final {
break;
case memgraph::storage::durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE:
case memgraph::storage::durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP:
case memgraph::storage::durability::StorageMetadataOperation::TEXT_INDEX_CREATE:
case memgraph::storage::durability::StorageMetadataOperation::TEXT_INDEX_DROP:
case memgraph::storage::durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE:
case memgraph::storage::durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP:
data.operation_label_property.label = label;