From 619b01f3f81072ef6621bbae6e1192ca76b91fea Mon Sep 17 00:00:00 2001 From: gvolfing <107616712+gvolfing@users.noreply.github.com> Date: Fri, 8 Mar 2024 08:44:48 +0100 Subject: [PATCH] Implement edge type indices (#1542) Implement edge type indices (#1542 ) --- src/communication/result_stream_faker.hpp | 2 +- src/dbms/database_handler.hpp | 2 +- src/dbms/inmemory/replication_handlers.cpp | 14 + src/glue/communication.hpp | 2 +- src/query/db_accessor.hpp | 70 +++ src/query/dump.cpp | 35 +- src/query/dump.hpp | 1 + src/query/frontend/ast/ast.cpp | 3 + src/query/frontend/ast/ast.hpp | 28 + src/query/frontend/ast/ast_visitor.hpp | 13 +- .../frontend/ast/cypher_main_visitor.cpp | 21 + .../frontend/ast/cypher_main_visitor.hpp | 15 + .../opencypher/grammar/MemgraphCypher.g4 | 7 + .../frontend/semantic/required_privileges.cpp | 2 + src/query/frontend/semantic/symbol.hpp | 2 + src/query/interpreter.cpp | 82 ++- src/query/plan/hint_provider.hpp | 3 + src/query/plan/operator.cpp | 82 +++ src/query/plan/operator.hpp | 45 +- src/query/plan/operator_type_info.cpp | 2 + src/query/plan/planner.hpp | 8 +- src/query/plan/pretty_print.cpp | 20 + src/query/plan/pretty_print.hpp | 2 + .../plan/rewrite/edge_type_index_lookup.hpp | 534 ++++++++++++++++++ src/query/plan/vertex_count_cache.hpp | 2 + src/query/procedure/module.hpp | 2 +- src/query/procedure/py_module.hpp | 2 +- src/storage/v2/CMakeLists.txt | 3 + src/storage/v2/disk/edge_type_index.cpp | 49 ++ src/storage/v2/disk/edge_type_index.hpp | 35 ++ src/storage/v2/disk/storage.cpp | 33 ++ src/storage/v2/disk/storage.hpp | 10 + src/storage/v2/durability/durability.cpp | 14 +- src/storage/v2/durability/marker.hpp | 9 +- src/storage/v2/durability/metadata.hpp | 3 +- src/storage/v2/durability/serialization.cpp | 8 +- src/storage/v2/durability/snapshot.cpp | 410 +++++++++++++- src/storage/v2/durability/snapshot.hpp | 3 +- .../durability/storage_global_operation.hpp | 4 +- src/storage/v2/durability/version.hpp | 4 +- src/storage/v2/durability/wal.cpp | 72 +++ src/storage/v2/durability/wal.hpp | 17 +- src/storage/v2/edges_iterable.cpp | 149 +++++ src/storage/v2/edges_iterable.hpp | 73 +++ src/storage/v2/indices/edge_type_index.hpp | 46 ++ src/storage/v2/indices/indices.cpp | 11 + src/storage/v2/indices/indices.hpp | 5 + src/storage/v2/inmemory/edge_type_index.cpp | 318 +++++++++++ src/storage/v2/inmemory/edge_type_index.hpp | 113 ++++ src/storage/v2/inmemory/storage.cpp | 60 +- src/storage/v2/inmemory/storage.hpp | 34 +- src/storage/v2/metadata_delta.hpp | 17 +- .../v2/replication/replication_client.cpp | 6 + .../v2/replication/replication_client.hpp | 3 + .../replication/replication_storage_state.cpp | 10 + .../replication/replication_storage_state.hpp | 2 + src/storage/v2/storage.hpp | 12 + src/storage/v2/vertices_iterable.cpp | 3 +- src/storage/v2/vertices_iterable.hpp | 2 +- src/utils/atomic_memory_block.hpp | 2 +- src/utils/event_counter.cpp | 1 + src/utils/settings.cpp | 2 +- src/utils/typeinfo.hpp | 2 + .../tests/v17/test_all/create_dataset.cypher | 22 + .../v17/test_all/expected_snapshot.cypher | 19 + .../tests/v17/test_all/expected_wal.cypher | 19 + .../tests/v17/test_all/snapshot.bin | Bin 0 -> 2067 bytes .../durability/tests/v17/test_all/wal.bin | Bin 0 -> 3582 bytes .../test_constraints/create_dataset.cypher | 6 + .../test_constraints/expected_snapshot.cypher | 6 + .../v17/test_constraints/expected_wal.cypher | 6 + .../tests/v17/test_constraints/snapshot.bin | Bin 0 -> 625 bytes .../tests/v17/test_constraints/wal.bin | Bin 0 -> 460 bytes .../v17/test_edges/create_dataset.cypher | 60 ++ .../v17/test_edges/expected_snapshot.cypher | 58 ++ .../tests/v17/test_edges/expected_wal.cypher | 58 ++ .../tests/v17/test_edges/snapshot.bin | Bin 0 -> 4297 bytes .../durability/tests/v17/test_edges/wal.bin | Bin 0 -> 6616 bytes .../v17/test_indices/create_dataset.cypher | 6 + .../v17/test_indices/expected_snapshot.cypher | 5 + .../v17/test_indices/expected_wal.cypher | 5 + .../tests/v17/test_indices/snapshot.bin | Bin 0 -> 731 bytes .../durability/tests/v17/test_indices/wal.bin | Bin 0 -> 847 bytes .../v17/test_vertices/create_dataset.cypher | 18 + .../test_vertices/expected_snapshot.cypher | 16 + .../v17/test_vertices/expected_wal.cypher | 16 + .../tests/v17/test_vertices/snapshot.bin | Bin 0 -> 1739 bytes .../tests/v17/test_vertices/wal.bin | Bin 0 -> 4355 bytes tests/manual/interactive_planning.cpp | 2 + tests/unit/dbms_database.cpp | 2 +- tests/unit/query_plan.cpp | 59 +- tests/unit/query_plan_checker.hpp | 19 +- tests/unit/storage_v2_decoder_encoder.cpp | 5 +- tests/unit/storage_v2_durability_inmemory.cpp | 75 ++- tests/unit/storage_v2_indices.cpp | 385 ++++++++++++- tests/unit/storage_v2_wal_file.cpp | 39 ++ 96 files changed, 3390 insertions(+), 62 deletions(-) create mode 100644 src/query/plan/rewrite/edge_type_index_lookup.hpp create mode 100644 src/storage/v2/disk/edge_type_index.cpp create mode 100644 src/storage/v2/disk/edge_type_index.hpp create mode 100644 src/storage/v2/edges_iterable.cpp create mode 100644 src/storage/v2/edges_iterable.hpp create mode 100644 src/storage/v2/indices/edge_type_index.hpp create mode 100644 src/storage/v2/inmemory/edge_type_index.cpp create mode 100644 src/storage/v2/inmemory/edge_type_index.hpp create mode 100644 tests/integration/durability/tests/v17/test_all/create_dataset.cypher create mode 100644 tests/integration/durability/tests/v17/test_all/expected_snapshot.cypher create mode 100644 tests/integration/durability/tests/v17/test_all/expected_wal.cypher create mode 100644 tests/integration/durability/tests/v17/test_all/snapshot.bin create mode 100644 tests/integration/durability/tests/v17/test_all/wal.bin create mode 100644 tests/integration/durability/tests/v17/test_constraints/create_dataset.cypher create mode 100644 tests/integration/durability/tests/v17/test_constraints/expected_snapshot.cypher create mode 100644 tests/integration/durability/tests/v17/test_constraints/expected_wal.cypher create mode 100644 tests/integration/durability/tests/v17/test_constraints/snapshot.bin create mode 100644 tests/integration/durability/tests/v17/test_constraints/wal.bin create mode 100644 tests/integration/durability/tests/v17/test_edges/create_dataset.cypher create mode 100644 tests/integration/durability/tests/v17/test_edges/expected_snapshot.cypher create mode 100644 tests/integration/durability/tests/v17/test_edges/expected_wal.cypher create mode 100644 tests/integration/durability/tests/v17/test_edges/snapshot.bin create mode 100644 tests/integration/durability/tests/v17/test_edges/wal.bin create mode 100644 tests/integration/durability/tests/v17/test_indices/create_dataset.cypher create mode 100644 tests/integration/durability/tests/v17/test_indices/expected_snapshot.cypher create mode 100644 tests/integration/durability/tests/v17/test_indices/expected_wal.cypher create mode 100644 tests/integration/durability/tests/v17/test_indices/snapshot.bin create mode 100644 tests/integration/durability/tests/v17/test_indices/wal.bin create mode 100644 tests/integration/durability/tests/v17/test_vertices/create_dataset.cypher create mode 100644 tests/integration/durability/tests/v17/test_vertices/expected_snapshot.cypher create mode 100644 tests/integration/durability/tests/v17/test_vertices/expected_wal.cypher create mode 100644 tests/integration/durability/tests/v17/test_vertices/snapshot.bin create mode 100644 tests/integration/durability/tests/v17/test_vertices/wal.bin diff --git a/src/communication/result_stream_faker.hpp b/src/communication/result_stream_faker.hpp index 779d039cc..c0a40cecf 100644 --- a/src/communication/result_stream_faker.hpp +++ b/src/communication/result_stream_faker.hpp @@ -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 diff --git a/src/dbms/database_handler.hpp b/src/dbms/database_handler.hpp index de5f813ba..cae54088e 100644 --- a/src/dbms/database_handler.hpp +++ b/src/dbms/database_handler.hpp @@ -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 diff --git a/src/dbms/inmemory/replication_handlers.cpp b/src/dbms/inmemory/replication_handlers.cpp index 6a78977bb..3e4a31884 100644 --- a/src/dbms/inmemory/replication_handlers.cpp +++ b/src/dbms/inmemory/replication_handlers.cpp @@ -840,6 +840,20 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage transaction->DeleteLabelPropertyIndexStats(storage->NameToLabel(info.label)); break; } + case WalDeltaData::Type::EDGE_INDEX_CREATE: { + spdlog::trace(" Create edge index on :{}", delta.operation_edge_type.edge_type); + auto *transaction = get_transaction(timestamp, kUniqueAccess); + if (transaction->CreateIndex(storage->NameToEdgeType(delta.operation_label.label)).HasError()) + throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__); + break; + } + case WalDeltaData::Type::EDGE_INDEX_DROP: { + spdlog::trace(" Drop edge index on :{}", delta.operation_edge_type.edge_type); + auto *transaction = get_transaction(timestamp, kUniqueAccess); + if (transaction->DropIndex(storage->NameToEdgeType(delta.operation_label.label)).HasError()) + throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__); + break; + } case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: { spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label, delta.operation_label_property.property); diff --git a/src/glue/communication.hpp b/src/glue/communication.hpp index 737f32db2..a448b05fc 100644 --- a/src/glue/communication.hpp +++ b/src/glue/communication.hpp @@ -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 diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index e10102ee5..915ea9936 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -371,6 +371,62 @@ class VerticesIterable final { } }; +class EdgesIterable final { + std::variant<storage::EdgesIterable, std::unordered_set<EdgeAccessor, std::hash<EdgeAccessor>, std::equal_to<void>, + utils::Allocator<EdgeAccessor>> *> + iterable_; + + public: + class Iterator final { + std::variant<storage::EdgesIterable::Iterator, + std::unordered_set<EdgeAccessor, std::hash<EdgeAccessor>, std::equal_to<void>, + utils::Allocator<EdgeAccessor>>::iterator> + it_; + + public: + explicit Iterator(storage::EdgesIterable::Iterator it) : it_(std::move(it)) {} + explicit Iterator(std::unordered_set<EdgeAccessor, std::hash<EdgeAccessor>, std::equal_to<void>, + utils::Allocator<EdgeAccessor>>::iterator it) + : it_(it) {} + + EdgeAccessor operator*() const { + return std::visit([](auto &it_) { return EdgeAccessor(*it_); }, it_); + } + + Iterator &operator++() { + std::visit([](auto &it_) { ++it_; }, it_); + return *this; + } + + bool operator==(const Iterator &other) const { return it_ == other.it_; } + + bool operator!=(const Iterator &other) const { return !(other == *this); } + }; + + explicit EdgesIterable(storage::EdgesIterable iterable) : iterable_(std::move(iterable)) {} + explicit EdgesIterable(std::unordered_set<EdgeAccessor, std::hash<EdgeAccessor>, std::equal_to<void>, + utils::Allocator<EdgeAccessor>> *edges) + : iterable_(edges) {} + + Iterator begin() { + return std::visit( + memgraph::utils::Overloaded{ + [](storage::EdgesIterable &iterable_) { return Iterator(iterable_.begin()); }, + [](std::unordered_set<EdgeAccessor, std::hash<EdgeAccessor>, std::equal_to<void>, + utils::Allocator<EdgeAccessor>> *iterable_) { return Iterator(iterable_->begin()); }}, + iterable_); + } + + Iterator end() { + return std::visit( + memgraph::utils::Overloaded{ + [](storage::EdgesIterable &iterable_) { return Iterator(iterable_.end()); }, + [](std::unordered_set<EdgeAccessor, std::hash<EdgeAccessor>, std::equal_to<void>, + utils::Allocator<EdgeAccessor>> *iterable_) { return Iterator(iterable_->end()); }}, + iterable_); + } +}; + class DbAccessor final { storage::Storage::Accessor *accessor_; @@ -416,6 +472,10 @@ class DbAccessor final { return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view)); } + EdgesIterable Edges(storage::View view, storage::EdgeTypeId edge_type) { + return EdgesIterable(accessor_->Edges(edge_type, view)); + } + VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); } storage::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to, @@ -572,6 +632,8 @@ class DbAccessor final { return accessor_->LabelPropertyIndexExists(label, prop); } + bool EdgeTypeIndexExists(storage::EdgeTypeId edge_type) const { return accessor_->EdgeTypeIndexExists(edge_type); } + std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const { return accessor_->GetIndexStats(label); } @@ -638,6 +700,10 @@ class DbAccessor final { return accessor_->CreateIndex(label, property); } + utils::BasicResult<storage::StorageIndexDefinitionError, void> CreateIndex(storage::EdgeTypeId edge_type) { + return accessor_->CreateIndex(edge_type); + } + utils::BasicResult<storage::StorageIndexDefinitionError, void> DropIndex(storage::LabelId label) { return accessor_->DropIndex(label); } @@ -647,6 +713,10 @@ class DbAccessor final { return accessor_->DropIndex(label, property); } + utils::BasicResult<storage::StorageIndexDefinitionError, void> DropIndex(storage::EdgeTypeId edge_type) { + return accessor_->DropIndex(edge_type); + } + utils::BasicResult<storage::StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint( storage::LabelId label, storage::PropertyId property) { return accessor_->CreateExistenceConstraint(label, property); diff --git a/src/query/dump.cpp b/src/query/dump.cpp index 2925023fb..f1dd08c8d 100644 --- a/src/query/dump.cpp +++ b/src/query/dump.cpp @@ -242,6 +242,10 @@ void DumpLabelIndex(std::ostream *os, query::DbAccessor *dba, const storage::Lab *os << "CREATE INDEX ON :" << EscapeName(dba->LabelToName(label)) << ";"; } +void DumpEdgeTypeIndex(std::ostream *os, query::DbAccessor *dba, const storage::EdgeTypeId edge_type) { + *os << "CREATE EDGE INDEX ON :" << EscapeName(dba->EdgeTypeToName(edge_type)) << ";"; +} + void DumpLabelPropertyIndex(std::ostream *os, query::DbAccessor *dba, storage::LabelId label, storage::PropertyId property) { *os << "CREATE INDEX ON :" << EscapeName(dba->LabelToName(label)) << "(" << EscapeName(dba->PropertyToName(property)) @@ -297,7 +301,9 @@ PullPlanDump::PullPlanDump(DbAccessor *dba, dbms::DatabaseAccess db_acc) // Internal index cleanup CreateInternalIndexCleanupPullChunk(), // Dump all triggers - CreateTriggersPullChunk()} {} + CreateTriggersPullChunk(), + // Dump all edge-type indices + CreateEdgeTypeIndicesPullChunk()} {} bool PullPlanDump::Pull(AnyStream *stream, std::optional<int> n) { // Iterate all functions that stream some results. @@ -352,6 +358,33 @@ PullPlanDump::PullChunk PullPlanDump::CreateLabelIndicesPullChunk() { }; } +PullPlanDump::PullChunk PullPlanDump::CreateEdgeTypeIndicesPullChunk() { + // Dump all label 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 &edge_type = indices_info_->edge_type; + + size_t local_counter = 0; + while (global_index < edge_type.size() && (!n || local_counter < *n)) { + std::ostringstream os; + DumpEdgeTypeIndex(&os, dba_, edge_type[global_index]); + stream->Result({TypedValue(os.str())}); + + ++global_index; + ++local_counter; + } + + if (global_index == edge_type.size()) { + return local_counter; + } + + return std::nullopt; + }; +} + PullPlanDump::PullChunk PullPlanDump::CreateLabelPropertyIndicesPullChunk() { return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> { // Delay the construction of indices vectors diff --git a/src/query/dump.hpp b/src/query/dump.hpp index a9d68d45c..05bd42967 100644 --- a/src/query/dump.hpp +++ b/src/query/dump.hpp @@ -63,5 +63,6 @@ struct PullPlanDump { PullChunk CreateDropInternalIndexPullChunk(); PullChunk CreateInternalIndexCleanupPullChunk(); PullChunk CreateTriggersPullChunk(); + PullChunk CreateEdgeTypeIndicesPullChunk(); }; } // namespace memgraph::query diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp index 57d5398ab..7da5c09a0 100644 --- a/src/query/frontend/ast/ast.cpp +++ b/src/query/frontend/ast/ast.cpp @@ -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::EdgeIndexQuery::kType{utils::TypeId::AST_EDGE_INDEX_QUERY, "EdgeIndexQuery", + &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", diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index ed354f6ca..b8d8c9e1a 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -2224,6 +2224,34 @@ class IndexQuery : public memgraph::query::Query { friend class AstStorage; }; +class EdgeIndexQuery : public memgraph::query::Query { + public: + static const utils::TypeInfo kType; + const utils::TypeInfo &GetTypeInfo() const override { return kType; } + + enum class Action { CREATE, DROP }; + + EdgeIndexQuery() = default; + + DEFVISITABLE(QueryVisitor<void>); + + memgraph::query::EdgeIndexQuery::Action action_; + memgraph::query::EdgeTypeIx edge_type_; + + EdgeIndexQuery *Clone(AstStorage *storage) const override { + EdgeIndexQuery *object = storage->Create<EdgeIndexQuery>(); + object->action_ = action_; + object->edge_type_ = storage->GetEdgeTypeIx(edge_type_.name); + return object; + } + + protected: + EdgeIndexQuery(Action action, EdgeTypeIx edge_type) : action_(action), edge_type_(edge_type) {} + + private: + friend class AstStorage; +}; + class Create : public memgraph::query::Clause { public: static const utils::TypeInfo kType; diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp index 5d463d3ee..bf11878da 100644 --- a/src/query/frontend/ast/ast_visitor.hpp +++ b/src/query/frontend/ast/ast_visitor.hpp @@ -82,6 +82,7 @@ class AuthQuery; class ExplainQuery; class ProfileQuery; class IndexQuery; +class EdgeIndexQuery; 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, EdgeIndexQuery, 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 diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index d3747bc3f..467c73125 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -265,6 +265,27 @@ antlrcpp::Any CypherMainVisitor::visitDropIndex(MemgraphCypher::DropIndexContext return index_query; } +antlrcpp::Any CypherMainVisitor::visitEdgeIndexQuery(MemgraphCypher::EdgeIndexQueryContext *ctx) { + MG_ASSERT(ctx->children.size() == 1, "EdgeIndexQuery should have exactly one child!"); + auto *index_query = std::any_cast<EdgeIndexQuery *>(ctx->children[0]->accept(this)); + query_ = index_query; + return index_query; +} + +antlrcpp::Any CypherMainVisitor::visitCreateEdgeIndex(MemgraphCypher::CreateEdgeIndexContext *ctx) { + auto *index_query = storage_->Create<EdgeIndexQuery>(); + index_query->action_ = EdgeIndexQuery::Action::CREATE; + index_query->edge_type_ = AddEdgeType(std::any_cast<std::string>(ctx->labelName()->accept(this))); + return index_query; +} + +antlrcpp::Any CypherMainVisitor::visitDropEdgeIndex(MemgraphCypher::DropEdgeIndexContext *ctx) { + auto *index_query = storage_->Create<EdgeIndexQuery>(); + index_query->action_ = EdgeIndexQuery::Action::DROP; + index_query->edge_type_ = AddEdgeType(std::any_cast<std::string>(ctx->labelName()->accept(this))); + 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)); diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp index 6d66e6d7e..8c65345c8 100644 --- a/src/query/frontend/ast/cypher_main_visitor.hpp +++ b/src/query/frontend/ast/cypher_main_visitor.hpp @@ -148,6 +148,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { */ antlrcpp::Any visitIndexQuery(MemgraphCypher::IndexQueryContext *ctx) override; + /** + * @return IndexQuery* + */ + antlrcpp::Any visitEdgeIndexQuery(MemgraphCypher::EdgeIndexQueryContext *ctx) override; + /** * @return ExplainQuery* */ @@ -499,6 +504,16 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { */ antlrcpp::Any visitDropIndex(MemgraphCypher::DropIndexContext *ctx) override; + /** + * @return EdgeIndexQuery* + */ + antlrcpp::Any visitCreateEdgeIndex(MemgraphCypher::CreateEdgeIndexContext *ctx) override; + + /** + * @return DropEdgeIndex* + */ + antlrcpp::Any visitDropEdgeIndex(MemgraphCypher::DropEdgeIndexContext *ctx) override; + /** * @return AuthQuery* */ diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index d24480b0a..0147bba04 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -133,6 +133,7 @@ symbolicName : UnescapedSymbolicName query : cypherQuery | indexQuery + | edgeIndexQuery | explainQuery | profileQuery | databaseInfoQuery @@ -527,3 +528,9 @@ showDatabase : SHOW DATABASE ; showDatabases : SHOW DATABASES ; edgeImportModeQuery : EDGE IMPORT MODE ( ACTIVE | INACTIVE ) ; + +createEdgeIndex : CREATE EDGE INDEX ON ':' labelName ; + +dropEdgeIndex : DROP EDGE INDEX ON ':' labelName ; + +edgeIndexQuery : createEdgeIndex | dropEdgeIndex ; diff --git a/src/query/frontend/semantic/required_privileges.cpp b/src/query/frontend/semantic/required_privileges.cpp index ef66a75ac..15726e3e2 100644 --- a/src/query/frontend/semantic/required_privileges.cpp +++ b/src/query/frontend/semantic/required_privileges.cpp @@ -27,6 +27,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis void Visit(IndexQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); } + void Visit(EdgeIndexQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); } + void Visit(AnalyzeGraphQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); } void Visit(AuthQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::AUTH); } diff --git a/src/query/frontend/semantic/symbol.hpp b/src/query/frontend/semantic/symbol.hpp index 77557b6fe..0cfb86608 100644 --- a/src/query/frontend/semantic/symbol.hpp +++ b/src/query/frontend/semantic/symbol.hpp @@ -53,6 +53,8 @@ class Symbol { bool user_declared() const { return user_declared_; } int token_position() const { return token_position_; } + bool IsSymbolAnonym() const { return name_.substr(0U, 4U) == "anon"; } + std::string name_; int64_t position_; bool user_declared_{true}; diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index ecec4fccb..ce74586d3 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -2679,6 +2679,75 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans RWType::W}; } +PreparedQuery PrepareEdgeIndexQuery(ParsedQuery parsed_query, bool in_explicit_transaction, + std::vector<Notification> *notifications, CurrentDB ¤t_db) { + if (in_explicit_transaction) { + throw IndexInMulticommandTxException(); + } + + auto *index_query = utils::Downcast<EdgeIndexQuery>(parsed_query.query); + std::function<void(Notification &)> handler; + + MG_ASSERT(current_db.db_acc_, "Index query expects a current DB"); + auto &db_acc = *current_db.db_acc_; + + MG_ASSERT(current_db.db_transactional_accessor_, "Index query expects a current DB transaction"); + auto *dba = &*current_db.execution_db_accessor_; + + auto invalidate_plan_cache = [plan_cache = db_acc->plan_cache()] { + plan_cache->WithLock([&](auto &cache) { cache.reset(); }); + }; + + auto *storage = db_acc->storage(); + auto edge_type = storage->NameToEdgeType(index_query->edge_type_.name); + + Notification index_notification(SeverityLevel::INFO); + switch (index_query->action_) { + case EdgeIndexQuery::Action::CREATE: { + index_notification.code = NotificationCode::CREATE_INDEX; + index_notification.title = fmt::format("Created index on edge-type {}.", index_query->edge_type_.name); + + handler = [dba, edge_type, label_name = index_query->edge_type_.name, + invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) { + auto maybe_index_error = dba->CreateIndex(edge_type); + utils::OnScopeExit invalidator(invalidate_plan_cache); + + if (maybe_index_error.HasError()) { + index_notification.code = NotificationCode::EXISTENT_INDEX; + index_notification.title = fmt::format("Index on edge-type {} already exists.", label_name); + } + }; + break; + } + case EdgeIndexQuery::Action::DROP: { + index_notification.code = NotificationCode::DROP_INDEX; + index_notification.title = fmt::format("Dropped index on edge-type {}.", index_query->edge_type_.name); + handler = [dba, edge_type, label_name = index_query->edge_type_.name, + invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) { + auto maybe_index_error = dba->DropIndex(edge_type); + utils::OnScopeExit invalidator(invalidate_plan_cache); + + if (maybe_index_error.HasError()) { + index_notification.code = NotificationCode::NONEXISTENT_INDEX; + index_notification.title = fmt::format("Index on edge-type {} doesn't exist.", label_name); + } + }; + 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; + }, + RWType::W}; +} + PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction, InterpreterContext *interpreter_context, Interpreter &interpreter) { if (in_explicit_transaction) { @@ -3483,6 +3552,7 @@ 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 edge_type_index_mark{"edge-type"}; auto info = dba->ListAllIndices(); auto storage_acc = database->Access(); std::vector<std::vector<TypedValue>> results; @@ -3497,6 +3567,10 @@ 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.edge_type) { + results.push_back({TypedValue(edge_type_index_mark), TypedValue(storage->EdgeTypeToName(item)), TypedValue(), + TypedValue(static_cast<int>(storage_acc->ApproximateEdgeCount(item)))}); + } 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(); @@ -4283,13 +4357,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<EdgeIndexQuery>(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<EdgeIndexQuery>(parsed_query.query) != nullptr || utils::Downcast<ConstraintQuery>(parsed_query.query) != nullptr || upper_case_query.find(kSchemaAssert) != std::string::npos; SetupDatabaseTransaction(could_commit, unique); @@ -4326,6 +4401,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<EdgeIndexQuery>(parsed_query.query)) { + prepared_query = PrepareEdgeIndexQuery(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)) { diff --git a/src/query/plan/hint_provider.hpp b/src/query/plan/hint_provider.hpp index b70de9aaf..3c8510561 100644 --- a/src/query/plan/hint_provider.hpp +++ b/src/query/plan/hint_provider.hpp @@ -114,6 +114,9 @@ class PlanHintsProvider final : public HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllById & /*unused*/) override { return true; } bool PostVisit(ScanAllById & /*unused*/) override { return true; } + bool PreVisit(ScanAllByEdgeType & /*unused*/) override { return true; } + bool PostVisit(ScanAllByEdgeType & /*unused*/) override { return true; } + bool PreVisit(ConstructNamedPath & /*unused*/) override { return true; } bool PostVisit(ConstructNamedPath & /*unused*/) override { return true; } diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index ba421b653..7cd506050 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -105,6 +105,7 @@ extern const Event ScanAllByLabelPropertyRangeOperator; extern const Event ScanAllByLabelPropertyValueOperator; extern const Event ScanAllByLabelPropertyOperator; extern const Event ScanAllByIdOperator; +extern const Event ScanAllByEdgeTypeOperator; extern const Event ExpandOperator; extern const Event ExpandVariableOperator; extern const Event ConstructNamedPathOperator; @@ -517,6 +518,60 @@ class ScanAllCursor : public Cursor { const char *op_name_; }; +template <typename TEdgesFun> +class ScanAllByEdgeTypeCursor : public Cursor { + public: + explicit ScanAllByEdgeTypeCursor(const ScanAllByEdgeType &self, Symbol output_symbol, UniqueCursorPtr input_cursor, + storage::View view, TEdgesFun get_edges, const char *op_name) + : self_(self), + output_symbol_(std::move(output_symbol)), + input_cursor_(std::move(input_cursor)), + view_(view), + get_edges_(std::move(get_edges)), + op_name_(op_name) {} + + bool Pull(Frame &frame, ExecutionContext &context) override { + OOMExceptionEnabler oom_exception; + SCOPED_PROFILE_OP_BY_REF(self_); + + AbortCheck(context); + + while (!vertices_ || vertices_it_.value() == vertices_end_it_.value()) { + if (!input_cursor_->Pull(frame, context)) return false; + auto next_vertices = get_edges_(frame, context); + if (!next_vertices) continue; + + vertices_.emplace(std::move(next_vertices.value())); + vertices_it_.emplace(vertices_.value().begin()); + vertices_end_it_.emplace(vertices_.value().end()); + } + + frame[output_symbol_] = *vertices_it_.value(); + ++vertices_it_.value(); + return true; + } + + void Shutdown() override { input_cursor_->Shutdown(); } + + void Reset() override { + input_cursor_->Reset(); + vertices_ = std::nullopt; + vertices_it_ = std::nullopt; + vertices_end_it_ = std::nullopt; + } + + private: + const ScanAllByEdgeType &self_; + const Symbol output_symbol_; + const UniqueCursorPtr input_cursor_; + storage::View view_; + TEdgesFun get_edges_; + std::optional<typename std::result_of<TEdgesFun(Frame &, ExecutionContext &)>::type::value_type> vertices_; + std::optional<decltype(vertices_.value().begin())> vertices_it_; + std::optional<decltype(vertices_.value().end())> vertices_end_it_; + const char *op_name_; +}; + ScanAll::ScanAll(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::View view) : input_(input ? input : std::make_shared<Once>()), output_symbol_(std::move(output_symbol)), view_(view) {} @@ -556,6 +611,33 @@ UniqueCursorPtr ScanAllByLabel::MakeCursor(utils::MemoryResource *mem) const { view_, std::move(vertices), "ScanAllByLabel"); } +ScanAllByEdgeType::ScanAllByEdgeType(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, + storage::EdgeTypeId edge_type, storage::View view) + : input_(input ? input : std::make_shared<Once>()), + output_symbol_(std::move(output_symbol)), + view_(view), + edge_type_(edge_type) {} + +ACCEPT_WITH_INPUT(ScanAllByEdgeType) + +UniqueCursorPtr ScanAllByEdgeType::MakeCursor(utils::MemoryResource *mem) const { + memgraph::metrics::IncrementCounter(memgraph::metrics::ScanAllByEdgeTypeOperator); + + auto edges = [this](Frame &, ExecutionContext &context) { + auto *db = context.db_accessor; + return std::make_optional(db->Edges(view_, edge_type_)); + }; + + return MakeUniqueCursorPtr<ScanAllByEdgeTypeCursor<decltype(edges)>>( + mem, *this, output_symbol_, input_->MakeCursor(mem), view_, std::move(edges), "ScanAllByEdgeType"); +} + +std::vector<Symbol> ScanAllByEdgeType::ModifiedSymbols(const SymbolTable &table) const { + auto symbols = input_->ModifiedSymbols(table); + symbols.emplace_back(output_symbol_); + return symbols; +} + // TODO(buda): Implement ScanAllByLabelProperty operator to iterate over // vertices that have the label and some value for the given property. diff --git a/src/query/plan/operator.hpp b/src/query/plan/operator.hpp index cdaca2875..6563c2bb0 100644 --- a/src/query/plan/operator.hpp +++ b/src/query/plan/operator.hpp @@ -99,6 +99,7 @@ class ScanAllByLabelPropertyRange; class ScanAllByLabelPropertyValue; class ScanAllByLabelProperty; class ScanAllById; +class ScanAllByEdgeType; class Expand; class ExpandVariable; class ConstructNamedPath; @@ -134,10 +135,10 @@ class RollUpApply; using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, ScanAllByLabelPropertyRange, - ScanAllByLabelPropertyValue, ScanAllByLabelProperty, ScanAllById, Expand, ExpandVariable, - ConstructNamedPath, Filter, Produce, Delete, SetProperty, SetProperties, SetLabels, - RemoveProperty, RemoveLabels, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, - OrderBy, Merge, Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, + ScanAllByLabelPropertyValue, ScanAllByLabelProperty, ScanAllById, ScanAllByEdgeType, Expand, + ExpandVariable, ConstructNamedPath, Filter, Produce, Delete, SetProperty, SetProperties, + SetLabels, RemoveProperty, RemoveLabels, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, + Limit, OrderBy, Merge, Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach, EmptyResult, EvaluatePatternFilter, Apply, IndexedJoin, HashJoin, RollUpApply>; using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>; @@ -592,6 +593,42 @@ class ScanAllByLabel : public memgraph::query::plan::ScanAll { } }; +class ScanAllByEdgeType : public memgraph::query::plan::LogicalOperator { + public: + static const utils::TypeInfo kType; + const utils::TypeInfo &GetTypeInfo() const override { return kType; } + + ScanAllByEdgeType() = default; + ScanAllByEdgeType(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, storage::EdgeTypeId edge_type, + storage::View view = storage::View::OLD); + bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; + UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; + std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override; + + bool HasSingleInput() const override { return true; } + std::shared_ptr<LogicalOperator> input() const override { return input_; } + void set_input(std::shared_ptr<LogicalOperator> input) override { input_ = input; } + + std::string ToString() const override { + return fmt::format("ScanAllByEdgeType ({} :{})", output_symbol_.name(), dba_->EdgeTypeToName(edge_type_)); + } + + std::shared_ptr<memgraph::query::plan::LogicalOperator> input_; + Symbol output_symbol_; + storage::View view_; + + storage::EdgeTypeId edge_type_; + + std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override { + auto object = std::make_unique<ScanAllByEdgeType>(); + object->input_ = input_ ? input_->Clone(storage) : nullptr; + object->output_symbol_ = output_symbol_; + object->view_ = view_; + object->edge_type_ = edge_type_; + return object; + } +}; + /// Behaves like @c ScanAll, but produces only vertices with given label and /// property value which is inside a range (inclusive or exlusive). /// diff --git a/src/query/plan/operator_type_info.cpp b/src/query/plan/operator_type_info.cpp index 168137552..6b0a28313 100644 --- a/src/query/plan/operator_type_info.cpp +++ b/src/query/plan/operator_type_info.cpp @@ -49,6 +49,8 @@ constexpr utils::TypeInfo query::plan::ScanAllByLabelProperty::kType{ constexpr utils::TypeInfo query::plan::ScanAllById::kType{utils::TypeId::SCAN_ALL_BY_ID, "ScanAllById", &query::plan::ScanAll::kType}; +constexpr utils::TypeInfo query::plan::ScanAllByEdgeType::kType{utils::TypeId::SCAN_ALL_BY_EDGE_TYPE, + "ScanAllByEdgeType", &query::plan::ScanAll::kType}; constexpr utils::TypeInfo query::plan::ExpandCommon::kType{utils::TypeId::EXPAND_COMMON, "ExpandCommon", nullptr}; diff --git a/src/query/plan/planner.hpp b/src/query/plan/planner.hpp index e8ca80e39..3136e7271 100644 --- a/src/query/plan/planner.hpp +++ b/src/query/plan/planner.hpp @@ -23,6 +23,7 @@ #include "query/plan/operator.hpp" #include "query/plan/preprocess.hpp" #include "query/plan/pretty_print.hpp" +#include "query/plan/rewrite/edge_type_index_lookup.hpp" #include "query/plan/rewrite/index_lookup.hpp" #include "query/plan/rewrite/join.hpp" #include "query/plan/rule_based_planner.hpp" @@ -54,8 +55,11 @@ class PostProcessor final { std::unique_ptr<LogicalOperator> Rewrite(std::unique_ptr<LogicalOperator> plan, TPlanningContext *context) { auto index_lookup_plan = RewriteWithIndexLookup(std::move(plan), context->symbol_table, context->ast_storage, context->db, index_hints_); - return RewriteWithJoinRewriter(std::move(index_lookup_plan), context->symbol_table, context->ast_storage, - context->db); + auto join_plan = + RewriteWithJoinRewriter(std::move(index_lookup_plan), context->symbol_table, context->ast_storage, context->db); + auto edge_index_plan = RewriteWithEdgeTypeIndexRewriter(std::move(join_plan), context->symbol_table, + context->ast_storage, context->db); + return edge_index_plan; } template <class TVertexCounts> diff --git a/src/query/plan/pretty_print.cpp b/src/query/plan/pretty_print.cpp index 7938f9c73..eeb0c15b5 100644 --- a/src/query/plan/pretty_print.cpp +++ b/src/query/plan/pretty_print.cpp @@ -76,6 +76,13 @@ bool PlanPrinter::PreVisit(ScanAllById &op) { return true; } +bool PlanPrinter::PreVisit(query::plan::ScanAllByEdgeType &op) { + op.dba_ = dba_; + WithPrintLn([&op](auto &out) { out << "* " << op.ToString(); }); + op.dba_ = nullptr; + return true; +} + bool PlanPrinter::PreVisit(query::plan::Expand &op) { op.dba_ = dba_; WithPrintLn([&op](auto &out) { out << "* " << op.ToString(); }); @@ -464,6 +471,19 @@ bool PlanToJsonVisitor::PreVisit(ScanAllById &op) { return false; } +bool PlanToJsonVisitor::PreVisit(ScanAllByEdgeType &op) { + json self; + self["name"] = "ScanAllByEdgeType"; + self["edge_type"] = ToJson(op.edge_type_, *dba_); + self["output_symbol"] = ToJson(op.output_symbol_); + + op.input_->Accept(*this); + self["input"] = PopOutput(); + + output_ = std::move(self); + return false; +} + bool PlanToJsonVisitor::PreVisit(CreateNode &op) { json self; self["name"] = "CreateNode"; diff --git a/src/query/plan/pretty_print.hpp b/src/query/plan/pretty_print.hpp index af8429b85..d62ae6bf2 100644 --- a/src/query/plan/pretty_print.hpp +++ b/src/query/plan/pretty_print.hpp @@ -67,6 +67,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyRange &) override; bool PreVisit(ScanAllByLabelProperty &) override; bool PreVisit(ScanAllById &) override; + bool PreVisit(ScanAllByEdgeType &) override; bool PreVisit(Expand &) override; bool PreVisit(ExpandVariable &) override; @@ -204,6 +205,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor { bool PreVisit(ScanAllByLabelPropertyValue &) override; bool PreVisit(ScanAllByLabelProperty &) override; bool PreVisit(ScanAllById &) override; + bool PreVisit(ScanAllByEdgeType &) override; bool PreVisit(EmptyResult &) override; bool PreVisit(Produce &) override; diff --git a/src/query/plan/rewrite/edge_type_index_lookup.hpp b/src/query/plan/rewrite/edge_type_index_lookup.hpp new file mode 100644 index 000000000..ed8666513 --- /dev/null +++ b/src/query/plan/rewrite/edge_type_index_lookup.hpp @@ -0,0 +1,534 @@ +// 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. + +/// @file +/// This file provides a plan rewriter which replaces `ScanAll` and `Expand` +/// operations with `ScanAllByEdgeType` if possible. The public entrypoint is +/// `RewriteWithEdgeTypeIndexRewriter`. + +#pragma once + +#include <algorithm> +#include <memory> +#include <optional> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include <gflags/gflags.h> + +#include "query/plan/operator.hpp" +#include "query/plan/preprocess.hpp" +#include "query/plan/rewrite/index_lookup.hpp" +#include "utils/algorithm.hpp" + +namespace memgraph::query::plan { + +namespace impl { + +template <class TDbAccessor> +class EdgeTypeIndexRewriter final : public HierarchicalLogicalOperatorVisitor { + public: + EdgeTypeIndexRewriter(SymbolTable *symbol_table, AstStorage *ast_storage, TDbAccessor *db) + : symbol_table_(symbol_table), ast_storage_(ast_storage), db_(db) {} + + using HierarchicalLogicalOperatorVisitor::PostVisit; + using HierarchicalLogicalOperatorVisitor::PreVisit; + using HierarchicalLogicalOperatorVisitor::Visit; + + bool Visit(Once &) override { return true; } + + bool PreVisit(Filter &op) override { + prev_ops_.push_back(&op); + return true; + } + + bool PostVisit(Filter & /*op*/) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ScanAll &op) override { + prev_ops_.push_back(&op); + + if (op.input()->GetTypeInfo() == Once::kType) { + const bool is_node_anon = op.output_symbol_.IsSymbolAnonym(); + once_under_scanall_ = is_node_anon; + } + + return true; + } + + bool PostVisit(ScanAll &op) override { + prev_ops_.pop_back(); + + if (EdgeTypeIndexingPossible()) { + SetOnParent(op.input()); + } + + return true; + } + + bool PreVisit(Expand &op) override { + prev_ops_.push_back(&op); + + if (op.input()->GetTypeInfo() == ScanAll::kType) { + const bool only_one_edge_type = (op.common_.edge_types.size() == 1U); + const bool expansion_is_named = !(op.common_.edge_symbol.IsSymbolAnonym()); + const bool expdanded_node_not_named = op.common_.node_symbol.IsSymbolAnonym(); + + edge_type_index_exist = only_one_edge_type ? db_->EdgeTypeIndexExists(op.common_.edge_types.front()) : false; + + scanall_under_expand_ = only_one_edge_type && expansion_is_named && expdanded_node_not_named; + } + + return true; + } + + bool PostVisit(Expand &op) override { + prev_ops_.pop_back(); + + if (EdgeTypeIndexingPossible()) { + auto indexed_scan = GenEdgeTypeScan(op); + SetOnParent(std::move(indexed_scan)); + } + + return true; + } + + bool PreVisit(ExpandVariable &op) override { + prev_ops_.push_back(&op); + return true; + } + + bool PostVisit(ExpandVariable &expand) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Merge &op) override { + prev_ops_.push_back(&op); + op.input()->Accept(*this); + RewriteBranch(&op.merge_match_); + return false; + } + + bool PostVisit(Merge &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Optional &op) override { + prev_ops_.push_back(&op); + op.input()->Accept(*this); + RewriteBranch(&op.optional_); + return false; + } + + bool PostVisit(Optional &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Cartesian &op) override { + prev_ops_.push_back(&op); + return true; + } + + bool PostVisit(Cartesian &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(IndexedJoin &op) override { + prev_ops_.push_back(&op); + RewriteBranch(&op.main_branch_); + RewriteBranch(&op.sub_branch_); + return false; + } + + bool PostVisit(IndexedJoin &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(HashJoin &op) override { + prev_ops_.push_back(&op); + return true; + } + + bool PostVisit(HashJoin &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Union &op) override { + prev_ops_.push_back(&op); + RewriteBranch(&op.left_op_); + RewriteBranch(&op.right_op_); + return false; + } + + bool PostVisit(Union &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(CreateNode &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(CreateNode &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(CreateExpand &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(CreateExpand &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ScanAllByLabel &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(ScanAllByLabel &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ScanAllByLabelPropertyRange &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(ScanAllByLabelPropertyRange &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ScanAllByLabelPropertyValue &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(ScanAllByLabelPropertyValue &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ScanAllByLabelProperty &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(ScanAllByLabelProperty &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ScanAllById &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(ScanAllById &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ScanAllByEdgeType &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(ScanAllByEdgeType &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(ConstructNamedPath &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(ConstructNamedPath &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Produce &op) override { + prev_ops_.push_back(&op); + + if (op.input()->GetTypeInfo() == Expand::kType) { + expand_under_produce_ = true; + } + + return true; + } + bool PostVisit(Produce &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(EmptyResult &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(EmptyResult &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Delete &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(Delete &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(SetProperty &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(SetProperty &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(SetProperties &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(SetProperties &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(SetLabels &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(SetLabels &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(RemoveProperty &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(RemoveProperty &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(RemoveLabels &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(RemoveLabels &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(EdgeUniquenessFilter &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(EdgeUniquenessFilter &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Accumulate &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(Accumulate &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Aggregate &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(Aggregate &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Skip &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(Skip &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Limit &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(Limit &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(OrderBy &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(OrderBy &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Unwind &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(Unwind &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Distinct &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(Distinct &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(CallProcedure &op) override { + prev_ops_.push_back(&op); + return true; + } + bool PostVisit(CallProcedure &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Foreach &op) override { + prev_ops_.push_back(&op); + op.input()->Accept(*this); + RewriteBranch(&op.update_clauses_); + return false; + } + + bool PostVisit(Foreach &) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(EvaluatePatternFilter &op) override { + prev_ops_.push_back(&op); + return true; + } + + bool PostVisit(EvaluatePatternFilter & /*op*/) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(Apply &op) override { + prev_ops_.push_back(&op); + op.input()->Accept(*this); + RewriteBranch(&op.subquery_); + return false; + } + + bool PostVisit(Apply & /*op*/) override { + prev_ops_.pop_back(); + return true; + } + + bool PreVisit(LoadCsv &op) override { + prev_ops_.push_back(&op); + return true; + } + + bool PostVisit(LoadCsv & /*op*/) override { + prev_ops_.pop_back(); + return true; + } + + std::shared_ptr<LogicalOperator> new_root_; + + private: + SymbolTable *symbol_table_; + AstStorage *ast_storage_; + TDbAccessor *db_; + // Collected filters, pending for examination if they can be used for advanced + // lookup operations (by index, node ID, ...). + Filters filters_; + // Expressions which no longer need a plain Filter operator. + std::unordered_set<Expression *> filter_exprs_for_removal_; + std::vector<LogicalOperator *> prev_ops_; + std::unordered_set<Symbol> cartesian_symbols_; + + bool EdgeTypeIndexingPossible() const { + return expand_under_produce_ && scanall_under_expand_ && once_under_scanall_ && edge_type_index_exist; + } + bool expand_under_produce_ = false; + bool scanall_under_expand_ = false; + bool once_under_scanall_ = false; + bool edge_type_index_exist = false; + + bool DefaultPreVisit() override { + throw utils::NotYetImplemented("Operator not yet covered by EdgeTypeIndexRewriter"); + } + + std::unique_ptr<ScanAllByEdgeType> GenEdgeTypeScan(const Expand &expand) { + const auto &input = expand.input(); + const auto &output_symbol = expand.common_.edge_symbol; + const auto &view = expand.view_; + + // Extract edge_type from symbol + auto edge_type = expand.common_.edge_types.front(); + return std::make_unique<ScanAllByEdgeType>(input, output_symbol, edge_type, view); + } + + void SetOnParent(const std::shared_ptr<LogicalOperator> &input) { + MG_ASSERT(input); + if (prev_ops_.empty()) { + MG_ASSERT(!new_root_); + new_root_ = input; + return; + } + prev_ops_.back()->set_input(input); + } + + void RewriteBranch(std::shared_ptr<LogicalOperator> *branch) { + EdgeTypeIndexRewriter<TDbAccessor> rewriter(symbol_table_, ast_storage_, db_); + (*branch)->Accept(rewriter); + if (rewriter.new_root_) { + *branch = rewriter.new_root_; + } + } +}; + +} // namespace impl + +template <class TDbAccessor> +std::unique_ptr<LogicalOperator> RewriteWithEdgeTypeIndexRewriter(std::unique_ptr<LogicalOperator> root_op, + SymbolTable *symbol_table, AstStorage *ast_storage, + TDbAccessor *db) { + impl::EdgeTypeIndexRewriter<TDbAccessor> rewriter(symbol_table, ast_storage, db); + root_op->Accept(rewriter); + return root_op; +} + +} // namespace memgraph::query::plan diff --git a/src/query/plan/vertex_count_cache.hpp b/src/query/plan/vertex_count_cache.hpp index 4cfb2486b..802f4e09f 100644 --- a/src/query/plan/vertex_count_cache.hpp +++ b/src/query/plan/vertex_count_cache.hpp @@ -78,6 +78,8 @@ class VertexCountCache { return db_->LabelPropertyIndexExists(label, property); } + bool EdgeTypeIndexExists(storage::EdgeTypeId edge_type) { return db_->EdgeTypeIndexExists(edge_type); } + std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const { return db_->GetIndexStats(label); } diff --git a/src/query/procedure/module.hpp b/src/query/procedure/module.hpp index 41cda0ca6..f5027dafa 100644 --- a/src/query/procedure/module.hpp +++ b/src/query/procedure/module.hpp @@ -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 diff --git a/src/query/procedure/py_module.hpp b/src/query/procedure/py_module.hpp index 9cb22fe2c..fe93b5c51 100644 --- a/src/query/procedure/py_module.hpp +++ b/src/query/procedure/py_module.hpp @@ -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 diff --git a/src/storage/v2/CMakeLists.txt b/src/storage/v2/CMakeLists.txt index 150a02cc7..ec5108d63 100644 --- a/src/storage/v2/CMakeLists.txt +++ b/src/storage/v2/CMakeLists.txt @@ -21,8 +21,10 @@ add_library(mg-storage-v2 STATIC storage.cpp indices/indices.cpp all_vertices_iterable.cpp + edges_iterable.cpp vertices_iterable.cpp inmemory/storage.cpp + inmemory/edge_type_index.cpp inmemory/label_index.cpp inmemory/label_property_index.cpp inmemory/unique_constraints.cpp @@ -30,6 +32,7 @@ add_library(mg-storage-v2 STATIC disk/edge_import_mode_cache.cpp disk/storage.cpp disk/rocksdb_storage.cpp + disk/edge_type_index.cpp disk/label_index.cpp disk/label_property_index.cpp disk/unique_constraints.cpp diff --git a/src/storage/v2/disk/edge_type_index.cpp b/src/storage/v2/disk/edge_type_index.cpp new file mode 100644 index 000000000..d11eb6caf --- /dev/null +++ b/src/storage/v2/disk/edge_type_index.cpp @@ -0,0 +1,49 @@ +// 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 "edge_type_index.hpp" + +#include "utils/exceptions.hpp" + +namespace memgraph::storage { + +bool DiskEdgeTypeIndex::DropIndex(EdgeTypeId /*edge_type*/) { + spdlog::warn("Edge-type index related operations are not yet supported using on-disk storage mode."); + return true; +} + +bool DiskEdgeTypeIndex::IndexExists(EdgeTypeId /*edge_type*/) const { + spdlog::warn("Edge-type index related operations are not yet supported using on-disk storage mode."); + return false; +} + +std::vector<EdgeTypeId> DiskEdgeTypeIndex::ListIndices() const { + spdlog::warn("Edge-type index related operations are not yet supported using on-disk storage mode."); + return {}; +} + +uint64_t DiskEdgeTypeIndex::ApproximateEdgeCount(EdgeTypeId /*edge_type*/) const { + spdlog::warn("Edge-type index related operations are not yet supported using on-disk storage mode."); + return 0U; +} + +void DiskEdgeTypeIndex::UpdateOnEdgeCreation(Vertex * /*from*/, Vertex * /*to*/, EdgeRef /*edge_ref*/, + EdgeTypeId /*edge_type*/, const Transaction & /*tx*/) { + spdlog::warn("Edge-type index related operations are not yet supported using on-disk storage mode."); +} + +void DiskEdgeTypeIndex::UpdateOnEdgeModification(Vertex * /*old_from*/, Vertex * /*old_to*/, Vertex * /*new_from*/, + Vertex * /*new_to*/, EdgeRef /*edge_ref*/, EdgeTypeId /*edge_type*/, + const Transaction & /*tx*/) { + spdlog::warn("Edge-type index related operations are not yet supported using on-disk storage mode."); +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/edge_type_index.hpp b/src/storage/v2/disk/edge_type_index.hpp new file mode 100644 index 000000000..fe79b2690 --- /dev/null +++ b/src/storage/v2/disk/edge_type_index.hpp @@ -0,0 +1,35 @@ +// 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 "storage/v2/indices/edge_type_index.hpp" + +namespace memgraph::storage { + +class DiskEdgeTypeIndex : public storage::EdgeTypeIndex { + public: + bool DropIndex(EdgeTypeId edge_type) override; + + bool IndexExists(EdgeTypeId edge_type) const override; + + std::vector<EdgeTypeId> ListIndices() const override; + + uint64_t ApproximateEdgeCount(EdgeTypeId edge_type) const override; + + void UpdateOnEdgeCreation(Vertex *from, Vertex *to, EdgeRef edge_ref, EdgeTypeId edge_type, + const Transaction &tx) override; + + void UpdateOnEdgeModification(Vertex *old_from, Vertex *old_to, Vertex *new_from, Vertex *new_to, EdgeRef edge_ref, + EdgeTypeId edge_type, const Transaction &tx) override; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/storage.cpp b/src/storage/v2/disk/storage.cpp index f9cd2ac13..21ae7755e 100644 --- a/src/storage/v2/disk/storage.cpp +++ b/src/storage/v2/disk/storage.cpp @@ -41,6 +41,7 @@ #include "storage/v2/edge_accessor.hpp" #include "storage/v2/edge_import_mode.hpp" #include "storage/v2/edge_ref.hpp" +#include "storage/v2/edges_iterable.hpp" #include "storage/v2/id_types.hpp" #include "storage/v2/modified_edge.hpp" #include "storage/v2/mvcc.hpp" @@ -807,11 +808,21 @@ void DiskStorage::LoadVerticesFromDiskLabelPropertyIndexForIntervalSearch( } } +EdgesIterable DiskStorage::DiskAccessor::Edges(EdgeTypeId /*edge_type*/, View /*view*/) { + throw utils::NotYetImplemented( + "Edge-type index related operations are not yet supported using on-disk storage mode."); +} + uint64_t DiskStorage::DiskAccessor::ApproximateVertexCount() const { auto *disk_storage = static_cast<DiskStorage *>(storage_); return disk_storage->vertex_count_.load(std::memory_order_acquire); } +uint64_t DiskStorage::DiskAccessor::ApproximateEdgeCount(EdgeTypeId /*edge_type*/) const { + spdlog::info("Edge-type index related operations are not yet supported using on-disk storage mode."); + return 0U; +} + uint64_t DiskStorage::GetDiskSpaceUsage() const { uint64_t main_disk_storage_size = utils::GetDirDiskUsage(config_.disk.main_storage_directory); uint64_t index_disk_storage_size = utils::GetDirDiskUsage(config_.disk.label_index_directory) + @@ -1629,6 +1640,9 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co return StorageManipulationError{PersistenceError{}}; } } break; + case MetadataDelta::Action::EDGE_INDEX_CREATE: { + throw utils::NotYetImplemented("Edge-type indexing is not yet implemented on on-disk storage mode."); + } case MetadataDelta::Action::LABEL_INDEX_DROP: { if (!disk_storage->durable_metadata_.PersistLabelIndexDeletion(md_delta.label)) { return StorageManipulationError{PersistenceError{}}; @@ -1641,6 +1655,9 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co return StorageManipulationError{PersistenceError{}}; } } break; + case MetadataDelta::Action::EDGE_INDEX_DROP: { + throw utils::NotYetImplemented("Edge-type indexing is not yet implemented on on-disk storage mode."); + } case MetadataDelta::Action::LABEL_INDEX_STATS_SET: { throw utils::NotYetImplemented("SetIndexStats(stats) is not implemented for DiskStorage."); } break; @@ -1917,6 +1934,11 @@ utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor: return {}; } +utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::CreateIndex(EdgeTypeId /*edge_type*/) { + throw utils::NotYetImplemented( + "Edge-type index related operations are not yet supported using on-disk storage mode."); +} + utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::DropIndex(LabelId label) { MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!"); auto *on_disk = static_cast<DiskStorage *>(storage_); @@ -1945,6 +1967,11 @@ utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor: return {}; } +utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::DropIndex(EdgeTypeId /*edge_type*/) { + throw utils::NotYetImplemented( + "Edge-type index related operations are not yet supported using on-disk storage mode."); +} + utils::BasicResult<StorageExistenceConstraintDefinitionError, void> DiskStorage::DiskAccessor::CreateExistenceConstraint(LabelId label, PropertyId property) { MG_ASSERT(unique_guard_.owns_lock(), "Create existence constraint requires a unique access to the storage!"); @@ -2053,6 +2080,12 @@ std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess( return std::unique_ptr<DiskAccessor>( new DiskAccessor{Storage::Accessor::unique_access, this, isolation_level, storage_mode_}); } + +bool DiskStorage::DiskAccessor::EdgeTypeIndexExists(EdgeTypeId /*edge_type*/) const { + spdlog::info("Edge-type index related operations are not yet supported using on-disk storage mode."); + return false; +} + IndicesInfo DiskStorage::DiskAccessor::ListAllIndices() const { auto *on_disk = static_cast<DiskStorage *>(storage_); auto *disk_label_index = static_cast<DiskLabelIndex *>(on_disk->indices_.label_index_.get()); diff --git a/src/storage/v2/disk/storage.hpp b/src/storage/v2/disk/storage.hpp index 4d71fd10b..349a7454a 100644 --- a/src/storage/v2/disk/storage.hpp +++ b/src/storage/v2/disk/storage.hpp @@ -72,6 +72,8 @@ class DiskStorage final : public Storage { const std::optional<utils::Bound<PropertyValue>> &lower_bound, const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) override; + EdgesIterable Edges(EdgeTypeId edge_type, View view) override; + uint64_t ApproximateVertexCount() const override; uint64_t ApproximateVertexCount(LabelId /*label*/) const override { return 10; } @@ -89,6 +91,8 @@ class DiskStorage final : public Storage { return 10; } + uint64_t ApproximateEdgeCount(EdgeTypeId edge_type) const override; + std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId & /*label*/) const override { return {}; } @@ -140,6 +144,8 @@ class DiskStorage final : public Storage { return disk_storage->indices_.label_property_index_->IndexExists(label, property); } + bool EdgeTypeIndexExists(EdgeTypeId edge_type) const override; + IndicesInfo ListAllIndices() const override; ConstraintsInfo ListAllConstraints() const override; @@ -158,10 +164,14 @@ class DiskStorage final : public Storage { utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) override; + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type) override; + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) override; utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) override; + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(EdgeTypeId edge_type) override; + utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint( LabelId label, PropertyId property) override; diff --git a/src/storage/v2/durability/durability.cpp b/src/storage/v2/durability/durability.cpp index a83313820..fbbedbee5 100644 --- a/src/storage/v2/durability/durability.cpp +++ b/src/storage/v2/durability/durability.cpp @@ -31,6 +31,7 @@ #include "storage/v2/durability/paths.hpp" #include "storage/v2/durability/snapshot.hpp" #include "storage/v2/durability/wal.hpp" +#include "storage/v2/inmemory/edge_type_index.hpp" #include "storage/v2/inmemory/label_index.hpp" #include "storage/v2/inmemory/label_property_index.hpp" #include "storage/v2/inmemory/unique_constraints.hpp" @@ -199,9 +200,18 @@ void RecoverIndicesAndStats(const RecoveredIndicesAndConstraints::IndicesMetadat } spdlog::info("Label+property indices statistics are recreated."); - spdlog::info("Indices are recreated."); + // Recover edge-type indices. + spdlog::info("Recreating {} edge-type indices from metadata.", indices_metadata.edge.size()); + auto *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(indices->edge_type_index_.get()); + for (const auto &item : indices_metadata.edge) { + if (!mem_edge_type_index->CreateIndex(item, vertices->access())) { + throw RecoveryFailure("The edge-type index must be created here!"); + } + spdlog::info("Index on :{} is recreated from metadata", name_id_mapper->IdToName(item.AsUint())); + } + spdlog::info("Edge-type indices are recreated."); - spdlog::info("Recreating constraints from metadata."); + spdlog::info("Indices are recreated."); } void RecoverExistenceConstraints(const RecoveredIndicesAndConstraints::ConstraintsMetadata &constraints_metadata, diff --git a/src/storage/v2/durability/marker.hpp b/src/storage/v2/durability/marker.hpp index 8f00d435d..ac0cc074d 100644 --- a/src/storage/v2/durability/marker.hpp +++ b/src/storage/v2/durability/marker.hpp @@ -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 @@ -37,6 +37,8 @@ enum class Marker : uint8_t { SECTION_CONSTRAINTS = 0x25, SECTION_DELTA = 0x26, SECTION_EPOCH_HISTORY = 0x27, + SECTION_EDGE_INDICES = 0x28, + SECTION_OFFSETS = 0x42, DELTA_VERTEX_CREATE = 0x50, @@ -60,6 +62,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_EDGE_TYPE_INDEX_CREATE = 0x65, + DELTA_EDGE_TYPE_INDEX_DROP = 0x66, VALUE_FALSE = 0x00, VALUE_TRUE = 0xff, @@ -85,6 +89,7 @@ static const Marker kMarkersAll[] = { Marker::SECTION_CONSTRAINTS, Marker::SECTION_DELTA, Marker::SECTION_EPOCH_HISTORY, + Marker::SECTION_EDGE_INDICES, Marker::SECTION_OFFSETS, Marker::DELTA_VERTEX_CREATE, Marker::DELTA_VERTEX_DELETE, @@ -103,6 +108,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_EDGE_TYPE_INDEX_CREATE, + Marker::DELTA_EDGE_TYPE_INDEX_DROP, Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE, Marker::DELTA_EXISTENCE_CONSTRAINT_DROP, Marker::DELTA_UNIQUE_CONSTRAINT_CREATE, diff --git a/src/storage/v2/durability/metadata.hpp b/src/storage/v2/durability/metadata.hpp index 42e24e723..c8ee27b2f 100644 --- a/src/storage/v2/durability/metadata.hpp +++ b/src/storage/v2/durability/metadata.hpp @@ -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<EdgeTypeId> edge; } indices; struct ConstraintsMetadata { diff --git a/src/storage/v2/durability/serialization.cpp b/src/storage/v2/durability/serialization.cpp index 6b13d9d00..28ba64943 100644 --- a/src/storage/v2/durability/serialization.cpp +++ b/src/storage/v2/durability/serialization.cpp @@ -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 @@ -332,6 +332,7 @@ std::optional<PropertyValue> Decoder::ReadPropertyValue() { case Marker::SECTION_CONSTRAINTS: case Marker::SECTION_DELTA: case Marker::SECTION_EPOCH_HISTORY: + case Marker::SECTION_EDGE_INDICES: case Marker::SECTION_OFFSETS: case Marker::DELTA_VERTEX_CREATE: case Marker::DELTA_VERTEX_DELETE: @@ -350,6 +351,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_EDGE_TYPE_INDEX_CREATE: + case Marker::DELTA_EDGE_TYPE_INDEX_DROP: case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE: case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP: case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE: @@ -435,6 +438,7 @@ bool Decoder::SkipPropertyValue() { case Marker::SECTION_CONSTRAINTS: case Marker::SECTION_DELTA: case Marker::SECTION_EPOCH_HISTORY: + case Marker::SECTION_EDGE_INDICES: case Marker::SECTION_OFFSETS: case Marker::DELTA_VERTEX_CREATE: case Marker::DELTA_VERTEX_DELETE: @@ -453,6 +457,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_EDGE_TYPE_INDEX_CREATE: + case Marker::DELTA_EDGE_TYPE_INDEX_DROP: case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE: case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP: case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE: diff --git a/src/storage/v2/durability/snapshot.cpp b/src/storage/v2/durability/snapshot.cpp index eee099870..5fea3dfa5 100644 --- a/src/storage/v2/durability/snapshot.cpp +++ b/src/storage/v2/durability/snapshot.cpp @@ -153,6 +153,11 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { info.offset_edges = read_offset(); info.offset_vertices = read_offset(); info.offset_indices = read_offset(); + if (*version >= 17) { + info.offset_edge_indices = read_offset(); + } else { + info.offset_edge_indices = 0U; + } info.offset_constraints = read_offset(); info.offset_mapper = read_offset(); info.offset_epoch_history = read_offset(); @@ -1379,10 +1384,11 @@ RecoveredSnapshot LoadSnapshotVersion15(const std::filesystem::path &path, utils return {info, recovery_info, std::move(indices_constraints)}; } -RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices, - utils::SkipList<Edge> *edges, - std::deque<std::pair<std::string, uint64_t>> *epoch_history, - NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, const Config &config) { +RecoveredSnapshot LoadSnapshotVersion16(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices, + utils::SkipList<Edge> *edges, + std::deque<std::pair<std::string, uint64_t>> *epoch_history, + NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, + const Config &config) { RecoveryInfo recovery_info; RecoveredIndicesAndConstraints indices_constraints; @@ -1391,13 +1397,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis if (!version) throw RecoveryFailure("Couldn't read snapshot magic and/or version!"); if (!IsVersionSupported(*version)) throw RecoveryFailure(fmt::format("Invalid snapshot version {}", *version)); - if (*version == 14U) { - return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count, - config.salient.items); - } - if (*version == 15U) { - return LoadSnapshotVersion15(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config); - } + if (*version != 16U) throw RecoveryFailure(fmt::format("Expected snapshot version is 16, but got {}", *version)); // Cleanup of loaded data in case of failure. bool success = false; @@ -1727,6 +1727,380 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis return {info, recovery_info, std::move(indices_constraints)}; } +RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices, + utils::SkipList<Edge> *edges, + std::deque<std::pair<std::string, uint64_t>> *epoch_history, + NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, const Config &config) { + RecoveryInfo recovery_info; + RecoveredIndicesAndConstraints indices_constraints; + + Decoder snapshot; + const auto version = snapshot.Initialize(path, kSnapshotMagic); + if (!version) throw RecoveryFailure("Couldn't read snapshot magic and/or version!"); + + if (!IsVersionSupported(*version)) throw RecoveryFailure(fmt::format("Invalid snapshot version {}", *version)); + if (*version == 14U) { + return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count, + config.salient.items); + } + if (*version == 15U) { + return LoadSnapshotVersion15(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config); + } + if (*version == 16U) { + return LoadSnapshotVersion16(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config); + } + + // Cleanup of loaded data in case of failure. + bool success = false; + utils::OnScopeExit cleanup([&] { + if (!success) { + edges->clear(); + vertices->clear(); + epoch_history->clear(); + } + }); + + // Read snapshot info. + const auto info = ReadSnapshotInfo(path); + spdlog::info("Recovering {} vertices and {} edges.", info.vertices_count, info.edges_count); + // Check for edges. + bool snapshot_has_edges = info.offset_edges != 0; + + // Recover mapper. + std::unordered_map<uint64_t, uint64_t> snapshot_id_map; + { + spdlog::info("Recovering mapper metadata."); + if (!snapshot.SetPosition(info.offset_mapper)) throw RecoveryFailure("Couldn't read data from snapshot!"); + + auto marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_MAPPER) throw RecoveryFailure("Failed to read section mapper!"); + + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Failed to read name-id mapper size!"); + + for (uint64_t i = 0; i < *size; ++i) { + auto id = snapshot.ReadUint(); + if (!id) throw RecoveryFailure("Failed to read id for name-id mapper!"); + auto name = snapshot.ReadString(); + if (!name) throw RecoveryFailure("Failed to read name for name-id mapper!"); + auto my_id = name_id_mapper->NameToId(*name); + snapshot_id_map.emplace(*id, my_id); + SPDLOG_TRACE("Mapping \"{}\"from snapshot id {} to actual id {}.", *name, *id, my_id); + } + } + auto get_label_from_id = [&snapshot_id_map](uint64_t label_id) { + auto it = snapshot_id_map.find(label_id); + if (it == snapshot_id_map.end()) throw RecoveryFailure("Couldn't find label id in snapshot_id_map!"); + return LabelId::FromUint(it->second); + }; + auto get_property_from_id = [&snapshot_id_map](uint64_t property_id) { + auto it = snapshot_id_map.find(property_id); + if (it == snapshot_id_map.end()) throw RecoveryFailure("Couldn't find property id in snapshot_id_map!"); + return PropertyId::FromUint(it->second); + }; + auto get_edge_type_from_id = [&snapshot_id_map](uint64_t edge_type_id) { + auto it = snapshot_id_map.find(edge_type_id); + if (it == snapshot_id_map.end()) throw RecoveryFailure("Couldn't find edge type id in snapshot_id_map!"); + return EdgeTypeId::FromUint(it->second); + }; + + // Reset current edge count. + edge_count->store(0, std::memory_order_release); + + { + spdlog::info("Recovering edges."); + // Recover edges. + if (snapshot_has_edges) { + // We don't need to check whether we store properties on edge or not, because `LoadPartialEdges` will always + // iterate over the edges in the snapshot (if they exist) and the current configuration of properties on edge only + // affect what it does: + // 1. If properties are allowed on edges, then it loads the edges. + // 2. If properties are not allowed on edges, then it checks that none of the edges have any properties. + if (!snapshot.SetPosition(info.offset_edge_batches)) { + throw RecoveryFailure("Couldn't read data from snapshot!"); + } + const auto edge_batches = ReadBatchInfos(snapshot); + + RecoverOnMultipleThreads( + config.durability.recovery_thread_count, + [path, edges, items = config.salient.items, &get_property_from_id](const size_t /*batch_index*/, + const BatchInfo &batch) { + LoadPartialEdges(path, *edges, batch.offset, batch.count, items, get_property_from_id); + }, + edge_batches); + } + spdlog::info("Edges are recovered."); + + // Recover vertices (labels and properties). + spdlog::info("Recovering vertices.", info.vertices_count); + uint64_t last_vertex_gid{0}; + + if (!snapshot.SetPosition(info.offset_vertex_batches)) { + throw RecoveryFailure("Couldn't read data from snapshot!"); + } + + const auto vertex_batches = ReadBatchInfos(snapshot); + RecoverOnMultipleThreads( + config.durability.recovery_thread_count, + [path, vertices, &vertex_batches, &get_label_from_id, &get_property_from_id, &last_vertex_gid]( + const size_t batch_index, const BatchInfo &batch) { + const auto last_vertex_gid_in_batch = + LoadPartialVertices(path, *vertices, batch.offset, batch.count, get_label_from_id, get_property_from_id); + if (batch_index == vertex_batches.size() - 1) { + last_vertex_gid = last_vertex_gid_in_batch; + } + }, + vertex_batches); + + spdlog::info("Vertices are recovered."); + + // Recover vertices (in/out edges). + spdlog::info("Recover connectivity."); + recovery_info.vertex_batches.reserve(vertex_batches.size()); + for (const auto batch : vertex_batches) { + recovery_info.vertex_batches.emplace_back(Gid::FromUint(0), batch.count); + } + std::atomic<uint64_t> highest_edge_gid{0}; + + RecoverOnMultipleThreads( + config.durability.recovery_thread_count, + [path, vertices, edges, edge_count, items = config.salient.items, snapshot_has_edges, &get_edge_type_from_id, + &highest_edge_gid, &recovery_info](const size_t batch_index, const BatchInfo &batch) { + const auto result = LoadPartialConnectivity(path, *vertices, *edges, batch.offset, batch.count, items, + snapshot_has_edges, get_edge_type_from_id); + edge_count->fetch_add(result.edge_count); + auto known_highest_edge_gid = highest_edge_gid.load(); + while (known_highest_edge_gid < result.highest_edge_id) { + highest_edge_gid.compare_exchange_weak(known_highest_edge_gid, result.highest_edge_id); + } + recovery_info.vertex_batches[batch_index].first = result.first_vertex_gid; + }, + vertex_batches); + + spdlog::info("Connectivity is recovered."); + + // Set initial values for edge/vertex ID generators. + recovery_info.next_edge_id = highest_edge_gid + 1; + recovery_info.next_vertex_id = last_vertex_gid + 1; + } + + // Recover indices. + { + spdlog::info("Recovering metadata of indices."); + if (!snapshot.SetPosition(info.offset_indices)) throw RecoveryFailure("Couldn't read data from snapshot!"); + + auto marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_INDICES) throw RecoveryFailure("Couldn't read section indices!"); + + // Recover label indices. + { + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Couldn't read the number of label indices"); + spdlog::info("Recovering metadata of {} label indices.", *size); + for (uint64_t i = 0; i < *size; ++i) { + auto label = snapshot.ReadUint(); + if (!label) throw RecoveryFailure("Couldn't read label of label index!"); + AddRecoveredIndexConstraint(&indices_constraints.indices.label, get_label_from_id(*label), + "The label index already exists!"); + SPDLOG_TRACE("Recovered metadata of label index for :{}", name_id_mapper->IdToName(snapshot_id_map.at(*label))); + } + spdlog::info("Metadata of label indices are recovered."); + } + + // Recover label indices statistics. + { + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Couldn't read the number of entries for label index statistics!"); + spdlog::info("Recovering metadata of {} label indices statistics.", *size); + for (uint64_t i = 0; i < *size; ++i) { + const auto label = snapshot.ReadUint(); + if (!label) throw RecoveryFailure("Couldn't read label while recovering label index statistics!"); + const auto count = snapshot.ReadUint(); + if (!count) throw RecoveryFailure("Couldn't read count for label index statistics!"); + const auto avg_degree = snapshot.ReadDouble(); + if (!avg_degree) throw RecoveryFailure("Couldn't read average degree for label index statistics"); + const auto label_id = get_label_from_id(*label); + indices_constraints.indices.label_stats.emplace_back(label_id, LabelIndexStats{*count, *avg_degree}); + SPDLOG_TRACE("Recovered metadata of label index statistics for :{}", + name_id_mapper->IdToName(snapshot_id_map.at(*label))); + } + spdlog::info("Metadata of label indices are recovered."); + } + + // Recover label+property indices. + { + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Couldn't recover the number of label property indices!"); + spdlog::info("Recovering metadata of {} label+property indices.", *size); + for (uint64_t i = 0; i < *size; ++i) { + auto label = snapshot.ReadUint(); + if (!label) throw RecoveryFailure("Couldn't read label for label property index!"); + auto property = snapshot.ReadUint(); + if (!property) throw RecoveryFailure("Couldn't read property for label property index"); + AddRecoveredIndexConstraint(&indices_constraints.indices.label_property, + {get_label_from_id(*label), get_property_from_id(*property)}, + "The label+property index already exists!"); + SPDLOG_TRACE("Recovered metadata of label+property index for :{}({})", + name_id_mapper->IdToName(snapshot_id_map.at(*label)), + name_id_mapper->IdToName(snapshot_id_map.at(*property))); + } + spdlog::info("Metadata of label+property indices are recovered."); + } + + // Recover label+property indices statistics. + { + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Couldn't recover the number of entries for label property statistics!"); + spdlog::info("Recovering metadata of {} label+property indices statistics.", *size); + for (uint64_t i = 0; i < *size; ++i) { + const auto label = snapshot.ReadUint(); + if (!label) throw RecoveryFailure("Couldn't read label for label property index statistics!"); + const auto property = snapshot.ReadUint(); + if (!property) throw RecoveryFailure("Couldn't read property for label property index statistics!"); + const auto count = snapshot.ReadUint(); + if (!count) throw RecoveryFailure("Couldn't read count for label property index statistics!!"); + const auto distinct_values_count = snapshot.ReadUint(); + if (!distinct_values_count) + throw RecoveryFailure("Couldn't read distinct values count for label property index statistics!"); + const auto statistic = snapshot.ReadDouble(); + if (!statistic) throw RecoveryFailure("Couldn't read statistics value for label-property index statistics!"); + const auto avg_group_size = snapshot.ReadDouble(); + if (!avg_group_size) + throw RecoveryFailure("Couldn't read average group size for label property index statistics!"); + const auto avg_degree = snapshot.ReadDouble(); + if (!avg_degree) throw RecoveryFailure("Couldn't read average degree for label property index statistics!"); + const auto label_id = get_label_from_id(*label); + const auto property_id = get_property_from_id(*property); + indices_constraints.indices.label_property_stats.emplace_back( + label_id, std::make_pair(property_id, LabelPropertyIndexStats{*count, *distinct_values_count, *statistic, + *avg_group_size, *avg_degree})); + SPDLOG_TRACE("Recovered metadata of label+property index statistics for :{}({})", + name_id_mapper->IdToName(snapshot_id_map.at(*label)), + name_id_mapper->IdToName(snapshot_id_map.at(*property))); + } + spdlog::info("Metadata of label+property indices are recovered."); + } + + // Recover edge-type indices. + spdlog::info("Recovering metadata of indices."); + if (!snapshot.SetPosition(info.offset_edge_indices)) throw RecoveryFailure("Couldn't read data from snapshot!"); + + marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_EDGE_INDICES) + throw RecoveryFailure("Couldn't read section edge-indices!"); + + { + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Couldn't read the number of edge-type indices"); + spdlog::info("Recovering metadata of {} edge-type indices.", *size); + for (uint64_t i = 0; i < *size; ++i) { + auto edge_type = snapshot.ReadUint(); + if (!edge_type) throw RecoveryFailure("Couldn't read edge-type of edge-type index!"); + AddRecoveredIndexConstraint(&indices_constraints.indices.edge, get_edge_type_from_id(*edge_type), + "The edge-type index already exists!"); + SPDLOG_TRACE("Recovered metadata of edge-type index for :{}", + name_id_mapper->IdToName(snapshot_id_map.at(*edge_type))); + } + spdlog::info("Metadata of edge-type indices are recovered."); + } + + spdlog::info("Metadata of indices are recovered."); + } + + // Recover constraints. + { + spdlog::info("Recovering metadata of constraints."); + if (!snapshot.SetPosition(info.offset_constraints)) throw RecoveryFailure("Couldn't read data from snapshot!"); + + auto marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_CONSTRAINTS) + throw RecoveryFailure("Couldn't read section constraints marker!"); + + // Recover existence constraints. + { + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Couldn't read the number of existence constraints!"); + spdlog::info("Recovering metadata of {} existence constraints.", *size); + for (uint64_t i = 0; i < *size; ++i) { + auto label = snapshot.ReadUint(); + if (!label) throw RecoveryFailure("Couldn't read label of existence constraints!"); + auto property = snapshot.ReadUint(); + if (!property) throw RecoveryFailure("Couldn't read property of existence constraints!"); + AddRecoveredIndexConstraint(&indices_constraints.constraints.existence, + {get_label_from_id(*label), get_property_from_id(*property)}, + "The existence constraint already exists!"); + SPDLOG_TRACE("Recovered metadata of existence constraint for :{}({})", + name_id_mapper->IdToName(snapshot_id_map.at(*label)), + name_id_mapper->IdToName(snapshot_id_map.at(*property))); + } + spdlog::info("Metadata of existence constraints are recovered."); + } + + // Recover unique constraints. + // Snapshot version should be checked since unique constraints were + // implemented in later versions of snapshot. + if (*version >= kUniqueConstraintVersion) { + auto size = snapshot.ReadUint(); + if (!size) throw RecoveryFailure("Couldn't read the number of unique constraints!"); + spdlog::info("Recovering metadata of {} unique constraints.", *size); + for (uint64_t i = 0; i < *size; ++i) { + auto label = snapshot.ReadUint(); + if (!label) throw RecoveryFailure("Couldn't read label of unique constraints!"); + auto properties_count = snapshot.ReadUint(); + if (!properties_count) throw RecoveryFailure("Couldn't read the number of properties in unique constraint!"); + std::set<PropertyId> properties; + for (uint64_t j = 0; j < *properties_count; ++j) { + auto property = snapshot.ReadUint(); + if (!property) throw RecoveryFailure("Couldn't read property of unique constraint!"); + properties.insert(get_property_from_id(*property)); + } + AddRecoveredIndexConstraint(&indices_constraints.constraints.unique, {get_label_from_id(*label), properties}, + "The unique constraint already exists!"); + SPDLOG_TRACE("Recovered metadata of unique constraints for :{}", + name_id_mapper->IdToName(snapshot_id_map.at(*label))); + } + spdlog::info("Metadata of unique constraints are recovered."); + } + spdlog::info("Metadata of constraints are recovered."); + } + + spdlog::info("Recovering metadata."); + // Recover epoch history + { + if (!snapshot.SetPosition(info.offset_epoch_history)) throw RecoveryFailure("Couldn't read data from snapshot!"); + + const auto marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_EPOCH_HISTORY) + throw RecoveryFailure("Couldn't read section epoch history marker!"); + + const auto history_size = snapshot.ReadUint(); + if (!history_size) { + throw RecoveryFailure("Couldn't read history size!"); + } + + for (int i = 0; i < *history_size; ++i) { + auto maybe_epoch_id = snapshot.ReadString(); + if (!maybe_epoch_id) { + throw RecoveryFailure("Couldn't read maybe epoch id!"); + } + const auto maybe_last_commit_timestamp = snapshot.ReadUint(); + if (!maybe_last_commit_timestamp) { + throw RecoveryFailure("Couldn't read maybe last commit timestamp!"); + } + epoch_history->emplace_back(std::move(*maybe_epoch_id), *maybe_last_commit_timestamp); + } + } + + spdlog::info("Metadata recovered."); + // Recover timestamp. + recovery_info.next_timestamp = info.start_timestamp + 1; + + // Set success flag (to disable cleanup). + success = true; + + return {info, recovery_info, std::move(indices_constraints)}; +} + using OldSnapshotFiles = std::vector<std::pair<uint64_t, std::filesystem::path>>; void EnsureNecessaryWalFilesExist(const std::filesystem::path &wal_directory, const std::string &uuid, OldSnapshotFiles old_snapshot_files, Transaction *transaction, @@ -1835,6 +2209,7 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files uint64_t offset_edges = 0; uint64_t offset_vertices = 0; uint64_t offset_indices = 0; + uint64_t offset_edge_indices = 0; uint64_t offset_constraints = 0; uint64_t offset_mapper = 0; uint64_t offset_metadata = 0; @@ -1847,6 +2222,7 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files snapshot.WriteUint(offset_edges); snapshot.WriteUint(offset_vertices); snapshot.WriteUint(offset_indices); + snapshot.WriteUint(offset_edge_indices); snapshot.WriteUint(offset_constraints); snapshot.WriteUint(offset_mapper); snapshot.WriteUint(offset_epoch_history); @@ -2106,6 +2482,17 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files snapshot.SetPosition(last_pos); } } + + // Write edge-type indices. + offset_edge_indices = snapshot.GetPosition(); + snapshot.WriteMarker(Marker::SECTION_EDGE_INDICES); + { + auto edge_type = storage->indices_.edge_type_index_->ListIndices(); + snapshot.WriteUint(edge_type.size()); + for (const auto &item : edge_type) { + write_mapping(item); + } + } } // Write constraints. @@ -2196,6 +2583,7 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files snapshot.WriteUint(offset_edges); snapshot.WriteUint(offset_vertices); snapshot.WriteUint(offset_indices); + snapshot.WriteUint(offset_edge_indices); snapshot.WriteUint(offset_constraints); snapshot.WriteUint(offset_mapper); snapshot.WriteUint(offset_epoch_history); diff --git a/src/storage/v2/durability/snapshot.hpp b/src/storage/v2/durability/snapshot.hpp index 4c1aee1ce..b8c224b3f 100644 --- a/src/storage/v2/durability/snapshot.hpp +++ b/src/storage/v2/durability/snapshot.hpp @@ -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 @@ -34,6 +34,7 @@ struct SnapshotInfo { uint64_t offset_edges; uint64_t offset_vertices; uint64_t offset_indices; + uint64_t offset_edge_indices; uint64_t offset_constraints; uint64_t offset_mapper; uint64_t offset_epoch_history; diff --git a/src/storage/v2/durability/storage_global_operation.hpp b/src/storage/v2/durability/storage_global_operation.hpp index a4f1b043a..7dd635e9d 100644 --- a/src/storage/v2/durability/storage_global_operation.hpp +++ b/src/storage/v2/durability/storage_global_operation.hpp @@ -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, + EDGE_TYPE_INDEX_CREATE, + EDGE_TYPE_INDEX_DROP, EXISTENCE_CONSTRAINT_CREATE, EXISTENCE_CONSTRAINT_DROP, UNIQUE_CONSTRAINT_CREATE, diff --git a/src/storage/v2/durability/version.hpp b/src/storage/v2/durability/version.hpp index 25eb30904..58ca0364a 100644 --- a/src/storage/v2/durability/version.hpp +++ b/src/storage/v2/durability/version.hpp @@ -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}; diff --git a/src/storage/v2/durability/wal.cpp b/src/storage/v2/durability/wal.cpp index 52e916052..5c40ab1c5 100644 --- a/src/storage/v2/durability/wal.cpp +++ b/src/storage/v2/durability/wal.cpp @@ -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::EDGE_TYPE_INDEX_CREATE: + return Marker::DELTA_EDGE_TYPE_INDEX_CREATE; + case StorageMetadataOperation::EDGE_TYPE_INDEX_DROP: + return Marker::DELTA_EDGE_TYPE_INDEX_DROP; case StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE: return Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE; case StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP: @@ -172,6 +176,10 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) { return WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET; case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR: return WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR; + case Marker::DELTA_EDGE_TYPE_INDEX_CREATE: + return WalDeltaData::Type::EDGE_INDEX_CREATE; + case Marker::DELTA_EDGE_TYPE_INDEX_DROP: + return WalDeltaData::Type::EDGE_INDEX_DROP; case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE: return WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE; case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP: @@ -198,6 +206,7 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) { case Marker::SECTION_CONSTRAINTS: case Marker::SECTION_DELTA: case Marker::SECTION_EPOCH_HISTORY: + case Marker::SECTION_EDGE_INDICES: case Marker::SECTION_OFFSETS: case Marker::VALUE_FALSE: case Marker::VALUE_TRUE: @@ -280,6 +289,7 @@ WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) { } case WalDeltaData::Type::TRANSACTION_END: break; + // NOLINTNEXTLINE(bugprone-branch-clone) case WalDeltaData::Type::LABEL_INDEX_CREATE: case WalDeltaData::Type::LABEL_INDEX_DROP: case WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR: @@ -295,6 +305,17 @@ WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) { } break; } + case WalDeltaData::Type::EDGE_INDEX_CREATE: + case WalDeltaData::Type::EDGE_INDEX_DROP: { + if constexpr (read_data) { + auto edge_type = decoder->ReadString(); + if (!edge_type) throw RecoveryFailure("Invalid WAL data!"); + delta.operation_edge_type.edge_type = std::move(*edge_type); + } else { + if (!decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!"); + } + break; + } case WalDeltaData::Type::LABEL_INDEX_STATS_SET: { if constexpr (read_data) { auto label = decoder->ReadString(); @@ -522,6 +543,9 @@ bool operator==(const WalDeltaData &a, const WalDeltaData &b) { case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: return a.operation_label_properties.label == b.operation_label_properties.label && a.operation_label_properties.properties == b.operation_label_properties.properties; + case WalDeltaData::Type::EDGE_INDEX_CREATE: + case WalDeltaData::Type::EDGE_INDEX_DROP: + return a.operation_edge_type.edge_type == b.operation_edge_type.edge_type; } } bool operator!=(const WalDeltaData &a, const WalDeltaData &b) { return !(a == b); } @@ -703,6 +727,37 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage } break; } + case StorageMetadataOperation::EDGE_TYPE_INDEX_CREATE: + case StorageMetadataOperation::EDGE_TYPE_INDEX_DROP: { + MG_ASSERT(false, "Invalid function call!"); + } + } +} + +void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageMetadataOperation operation, + EdgeTypeId edge_type, uint64_t timestamp) { + encoder->WriteMarker(Marker::SECTION_DELTA); + encoder->WriteUint(timestamp); + switch (operation) { + case StorageMetadataOperation::EDGE_TYPE_INDEX_CREATE: + case StorageMetadataOperation::EDGE_TYPE_INDEX_DROP: { + encoder->WriteMarker(OperationToMarker(operation)); + encoder->WriteString(name_id_mapper->IdToName(edge_type.AsUint())); + break; + } + case StorageMetadataOperation::LABEL_INDEX_CREATE: + case StorageMetadataOperation::LABEL_INDEX_DROP: + case StorageMetadataOperation::LABEL_INDEX_STATS_CLEAR: + case StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR: + case StorageMetadataOperation::LABEL_INDEX_STATS_SET: + case StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE: + case StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP: + case StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE: + case StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP: + case StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_SET: + case StorageMetadataOperation::UNIQUE_CONSTRAINT_CREATE: + case StorageMetadataOperation::UNIQUE_CONSTRAINT_DROP: + MG_ASSERT(false, "Invalid function call!"); } } @@ -887,6 +942,18 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst "The label index doesn't exist!"); break; } + case WalDeltaData::Type::EDGE_INDEX_CREATE: { + auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.operation_edge_type.edge_type)); + AddRecoveredIndexConstraint(&indices_constraints->indices.edge, edge_type_id, + "The edge-type index already exists!"); + break; + } + case WalDeltaData::Type::EDGE_INDEX_DROP: { + auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.operation_edge_type.edge_type)); + RemoveRecoveredIndexConstraint(&indices_constraints->indices.edge, edge_type_id, + "The edge-type index doesn't exist!"); + break; + } case WalDeltaData::Type::LABEL_INDEX_STATS_SET: { auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_stats.label)); LabelIndexStats stats{}; @@ -1088,6 +1155,11 @@ void WalFile::AppendOperation(StorageMetadataOperation operation, LabelId label, UpdateStats(timestamp); } +void WalFile::AppendOperation(StorageMetadataOperation operation, EdgeTypeId edge_type, uint64_t timestamp) { + EncodeOperation(&wal_, name_id_mapper_, operation, edge_type, timestamp); + UpdateStats(timestamp); +} + void WalFile::Sync() { wal_.Sync(); } uint64_t WalFile::GetSize() { return wal_.GetSize(); } diff --git a/src/storage/v2/durability/wal.hpp b/src/storage/v2/durability/wal.hpp index 20d88b040..516487e0d 100644 --- a/src/storage/v2/durability/wal.hpp +++ b/src/storage/v2/durability/wal.hpp @@ -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, + EDGE_INDEX_CREATE, + EDGE_INDEX_DROP, EXISTENCE_CONSTRAINT_CREATE, EXISTENCE_CONSTRAINT_DROP, UNIQUE_CONSTRAINT_CREATE, @@ -111,6 +113,10 @@ struct WalDeltaData { std::set<std::string, std::less<>> properties; } operation_label_properties; + struct { + std::string edge_type; + } operation_edge_type; + struct { std::string label; std::string stats; @@ -155,6 +161,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::EDGE_INDEX_CREATE: + case WalDeltaData::Type::EDGE_INDEX_DROP: case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: @@ -164,7 +172,7 @@ constexpr bool IsWalDeltaDataTypeTransactionEndVersion15(const WalDeltaData::Typ } constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type, const uint64_t version = kVersion) { - if (version < 16U) { + if (version < 17U) { return IsWalDeltaDataTypeTransactionEndVersion15(type); } // All deltas are now handled in a transactional scope @@ -208,6 +216,9 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage LabelId label, const std::set<PropertyId> &properties, const LabelIndexStats &stats, const LabelPropertyIndexStats &property_stats, uint64_t timestamp); +void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageMetadataOperation operation, + EdgeTypeId edge_type, uint64_t timestamp); + /// Function used to load the WAL data into the storage. /// @throw RecoveryFailure RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints, @@ -240,6 +251,8 @@ class WalFile { void AppendOperation(StorageMetadataOperation operation, LabelId label, const std::set<PropertyId> &properties, const LabelIndexStats &stats, const LabelPropertyIndexStats &property_stats, uint64_t timestamp); + void AppendOperation(StorageMetadataOperation operation, EdgeTypeId edge_type, uint64_t timestamp); + void Sync(); uint64_t GetSize(); diff --git a/src/storage/v2/edges_iterable.cpp b/src/storage/v2/edges_iterable.cpp new file mode 100644 index 000000000..6acae34e3 --- /dev/null +++ b/src/storage/v2/edges_iterable.cpp @@ -0,0 +1,149 @@ +// 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/edges_iterable.hpp" + +namespace memgraph::storage { + +EdgesIterable::EdgesIterable(InMemoryEdgeTypeIndex::Iterable edges) : type_(Type::BY_EDGE_TYPE_IN_MEMORY) { + new (&in_memory_edges_by_edge_type_) InMemoryEdgeTypeIndex::Iterable(std::move(edges)); +} + +EdgesIterable::EdgesIterable(EdgesIterable &&other) noexcept : type_(other.type_) { + switch (other.type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + new (&in_memory_edges_by_edge_type_) + InMemoryEdgeTypeIndex::Iterable(std::move(other.in_memory_edges_by_edge_type_)); + break; + } +} + +EdgesIterable &EdgesIterable::operator=(EdgesIterable &&other) noexcept { + Destroy(); + type_ = other.type_; + switch (other.type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + new (&in_memory_edges_by_edge_type_) + InMemoryEdgeTypeIndex::Iterable(std::move(other.in_memory_edges_by_edge_type_)); + break; + } + return *this; +} + +EdgesIterable::~EdgesIterable() { Destroy(); } + +void EdgesIterable::Destroy() noexcept { + switch (type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + in_memory_edges_by_edge_type_.InMemoryEdgeTypeIndex::Iterable::~Iterable(); + break; + } +} + +EdgesIterable::Iterator EdgesIterable::begin() { + switch (type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + return Iterator(in_memory_edges_by_edge_type_.begin()); + } +} + +EdgesIterable::Iterator EdgesIterable::end() { + switch (type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + return Iterator(in_memory_edges_by_edge_type_.end()); + } +} + +EdgesIterable::Iterator::Iterator(InMemoryEdgeTypeIndex::Iterable::Iterator it) : type_(Type::BY_EDGE_TYPE_IN_MEMORY) { + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&in_memory_edges_by_edge_type_) InMemoryEdgeTypeIndex::Iterable::Iterator(std::move(it)); +} + +EdgesIterable::Iterator::Iterator(const EdgesIterable::Iterator &other) : type_(other.type_) { + switch (other.type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + new (&in_memory_edges_by_edge_type_) + InMemoryEdgeTypeIndex::Iterable::Iterator(other.in_memory_edges_by_edge_type_); + break; + } +} + +// NOLINTNEXTLINE(cert-oop54-cpp) +EdgesIterable::Iterator &EdgesIterable::Iterator::operator=(const EdgesIterable::Iterator &other) { + Destroy(); + type_ = other.type_; + switch (other.type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + new (&in_memory_edges_by_edge_type_) + InMemoryEdgeTypeIndex::Iterable::Iterator(other.in_memory_edges_by_edge_type_); + break; + } + return *this; +} + +EdgesIterable::Iterator::Iterator(EdgesIterable::Iterator &&other) noexcept : type_(other.type_) { + switch (other.type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + new (&in_memory_edges_by_edge_type_) + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + InMemoryEdgeTypeIndex::Iterable::Iterator(std::move(other.in_memory_edges_by_edge_type_)); + break; + } +} + +EdgesIterable::Iterator &EdgesIterable::Iterator::operator=(EdgesIterable::Iterator &&other) noexcept { + Destroy(); + type_ = other.type_; + switch (other.type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + new (&in_memory_edges_by_edge_type_) + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + InMemoryEdgeTypeIndex::Iterable::Iterator(std::move(other.in_memory_edges_by_edge_type_)); + break; + } + return *this; +} + +EdgesIterable::Iterator::~Iterator() { Destroy(); } + +void EdgesIterable::Iterator::Destroy() noexcept { + switch (type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + in_memory_edges_by_edge_type_.InMemoryEdgeTypeIndex::Iterable::Iterator::~Iterator(); + break; + } +} + +EdgeAccessor const &EdgesIterable::Iterator::operator*() const { + switch (type_) { + ; + case Type::BY_EDGE_TYPE_IN_MEMORY: + return *in_memory_edges_by_edge_type_; + } +} + +EdgesIterable::Iterator &EdgesIterable::Iterator::operator++() { + switch (type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + ++in_memory_edges_by_edge_type_; + break; + } + return *this; +} + +bool EdgesIterable::Iterator::operator==(const Iterator &other) const { + switch (type_) { + case Type::BY_EDGE_TYPE_IN_MEMORY: + return in_memory_edges_by_edge_type_ == other.in_memory_edges_by_edge_type_; + } +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/edges_iterable.hpp b/src/storage/v2/edges_iterable.hpp new file mode 100644 index 000000000..9c9326705 --- /dev/null +++ b/src/storage/v2/edges_iterable.hpp @@ -0,0 +1,73 @@ +// 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 "storage/v2/all_vertices_iterable.hpp" +#include "storage/v2/inmemory/edge_type_index.hpp" + +namespace memgraph::storage { + +class InMemoryEdgeTypeIndex; + +class EdgesIterable final { + enum class Type { BY_EDGE_TYPE_IN_MEMORY }; + + Type type_; + union { + InMemoryEdgeTypeIndex::Iterable in_memory_edges_by_edge_type_; + }; + + void Destroy() noexcept; + + public: + explicit EdgesIterable(InMemoryEdgeTypeIndex::Iterable); + + EdgesIterable(const EdgesIterable &) = delete; + EdgesIterable &operator=(const EdgesIterable &) = delete; + + EdgesIterable(EdgesIterable &&) noexcept; + EdgesIterable &operator=(EdgesIterable &&) noexcept; + + ~EdgesIterable(); + + class Iterator final { + Type type_; + union { + InMemoryEdgeTypeIndex::Iterable::Iterator in_memory_edges_by_edge_type_; + }; + + void Destroy() noexcept; + + public: + explicit Iterator(InMemoryEdgeTypeIndex::Iterable::Iterator); + + Iterator(const Iterator &); + Iterator &operator=(const Iterator &); + + Iterator(Iterator &&) noexcept; + Iterator &operator=(Iterator &&) noexcept; + + ~Iterator(); + + EdgeAccessor const &operator*() const; + + Iterator &operator++(); + + bool operator==(const Iterator &other) const; + bool operator!=(const Iterator &other) const { return !(*this == other); } + }; + + Iterator begin(); + Iterator end(); +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/indices/edge_type_index.hpp b/src/storage/v2/indices/edge_type_index.hpp new file mode 100644 index 000000000..788ccb225 --- /dev/null +++ b/src/storage/v2/indices/edge_type_index.hpp @@ -0,0 +1,46 @@ +// 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 <vector> + +#include "storage/v2/transaction.hpp" + +namespace memgraph::storage { + +class EdgeTypeIndex { + public: + EdgeTypeIndex() = default; + + EdgeTypeIndex(const EdgeTypeIndex &) = delete; + EdgeTypeIndex(EdgeTypeIndex &&) = delete; + EdgeTypeIndex &operator=(const EdgeTypeIndex &) = delete; + EdgeTypeIndex &operator=(EdgeTypeIndex &&) = delete; + + virtual ~EdgeTypeIndex() = default; + + virtual bool DropIndex(EdgeTypeId edge_type) = 0; + + virtual bool IndexExists(EdgeTypeId edge_type) const = 0; + + virtual std::vector<EdgeTypeId> ListIndices() const = 0; + + virtual uint64_t ApproximateEdgeCount(EdgeTypeId edge_type) const = 0; + + virtual void UpdateOnEdgeCreation(Vertex *from, Vertex *to, EdgeRef edge_ref, EdgeTypeId edge_type, + const Transaction &tx) = 0; + + virtual void UpdateOnEdgeModification(Vertex *old_from, Vertex *old_to, Vertex *new_from, Vertex *new_to, + EdgeRef edge_ref, EdgeTypeId edge_type, const Transaction &tx) = 0; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/indices/indices.cpp b/src/storage/v2/indices/indices.cpp index c86ec8442..6068f888f 100644 --- a/src/storage/v2/indices/indices.cpp +++ b/src/storage/v2/indices/indices.cpp @@ -10,8 +10,10 @@ // licenses/APL.txt. #include "storage/v2/indices/indices.hpp" +#include "storage/v2/disk/edge_type_index.hpp" #include "storage/v2/disk/label_index.hpp" #include "storage/v2/disk/label_property_index.hpp" +#include "storage/v2/inmemory/edge_type_index.hpp" #include "storage/v2/inmemory/label_index.hpp" #include "storage/v2/inmemory/label_property_index.hpp" @@ -35,6 +37,8 @@ void Indices::AbortEntries(LabelId label, std::span<std::pair<PropertyValue, Ver void Indices::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) const { static_cast<InMemoryLabelIndex *>(label_index_.get())->RemoveObsoleteEntries(oldest_active_start_timestamp, token); static_cast<InMemoryLabelPropertyIndex *>(label_property_index_.get()) + ->RemoveObsoleteEntries(oldest_active_start_timestamp, token); + static_cast<InMemoryEdgeTypeIndex *>(edge_type_index_.get()) ->RemoveObsoleteEntries(oldest_active_start_timestamp, std::move(token)); } @@ -53,14 +57,21 @@ void Indices::UpdateOnSetProperty(PropertyId property, const PropertyValue &valu label_property_index_->UpdateOnSetProperty(property, value, vertex, tx); } +void Indices::UpdateOnEdgeCreation(Vertex *from, Vertex *to, EdgeRef edge_ref, EdgeTypeId edge_type, + const Transaction &tx) const { + edge_type_index_->UpdateOnEdgeCreation(from, to, edge_ref, edge_type, tx); +} + Indices::Indices(const Config &config, StorageMode storage_mode) { std::invoke([this, config, storage_mode]() { if (storage_mode == StorageMode::IN_MEMORY_TRANSACTIONAL || storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { label_index_ = std::make_unique<InMemoryLabelIndex>(); label_property_index_ = std::make_unique<InMemoryLabelPropertyIndex>(); + edge_type_index_ = std::make_unique<InMemoryEdgeTypeIndex>(); } else { label_index_ = std::make_unique<DiskLabelIndex>(config); label_property_index_ = std::make_unique<DiskLabelPropertyIndex>(config); + edge_type_index_ = std::make_unique<DiskEdgeTypeIndex>(); } }); } diff --git a/src/storage/v2/indices/indices.hpp b/src/storage/v2/indices/indices.hpp index d95187bbb..40cff577f 100644 --- a/src/storage/v2/indices/indices.hpp +++ b/src/storage/v2/indices/indices.hpp @@ -15,6 +15,7 @@ #include <span> #include "storage/v2/id_types.hpp" +#include "storage/v2/indices/edge_type_index.hpp" #include "storage/v2/indices/label_index.hpp" #include "storage/v2/indices/label_property_index.hpp" #include "storage/v2/storage_mode.hpp" @@ -64,8 +65,12 @@ struct Indices { void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, const Transaction &tx) const; + void UpdateOnEdgeCreation(Vertex *from, Vertex *to, EdgeRef edge_ref, EdgeTypeId edge_type, + const Transaction &tx) const; + std::unique_ptr<LabelIndex> label_index_; std::unique_ptr<LabelPropertyIndex> label_property_index_; + std::unique_ptr<EdgeTypeIndex> edge_type_index_; }; } // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/edge_type_index.cpp b/src/storage/v2/inmemory/edge_type_index.cpp new file mode 100644 index 000000000..e439628b4 --- /dev/null +++ b/src/storage/v2/inmemory/edge_type_index.cpp @@ -0,0 +1,318 @@ +// 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/inmemory/edge_type_index.hpp" + +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/indices/indices_utils.hpp" +#include "utils/counter.hpp" + +namespace { + +using Delta = memgraph::storage::Delta; +using Vertex = memgraph::storage::Vertex; +using Edge = memgraph::storage::Edge; +using EdgeRef = memgraph::storage::EdgeRef; +using EdgeTypeId = memgraph::storage::EdgeTypeId; +using Transaction = memgraph::storage::Transaction; +using View = memgraph::storage::View; + +bool IsIndexEntryVisible(Edge *edge, const Transaction *transaction, View view) { + bool exists = true; + bool deleted = true; + Delta *delta = nullptr; + { + auto guard = std::shared_lock{edge->lock}; + deleted = edge->deleted; + delta = edge->delta; + } + ApplyDeltasForRead(transaction, delta, view, [&](const Delta &delta) { + switch (delta.action) { + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + break; + case Delta::Action::RECREATE_OBJECT: { + deleted = false; + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: { + exists = false; + break; + } + } + }); + return exists && !deleted; +} + +using ReturnType = std::optional<std::tuple<EdgeTypeId, Vertex *, EdgeRef>>; +ReturnType VertexDeletedConnectedEdges(Vertex *vertex, Edge *edge, const Transaction *transaction, View view) { + ReturnType link; + Delta *delta = nullptr; + { + auto guard = std::shared_lock{vertex->lock}; + delta = vertex->delta; + } + ApplyDeltasForRead(transaction, delta, view, [&](const Delta &delta) { + switch (delta.action) { + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::SET_PROPERTY: + break; + case Delta::Action::ADD_IN_EDGE: { + if (edge == delta.vertex_edge.edge.ptr) { + link = {delta.vertex_edge.edge_type, delta.vertex_edge.vertex, delta.vertex_edge.edge}; + auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); + MG_ASSERT(it == vertex->in_edges.end(), "Invalid database state!"); + break; + } + } + case Delta::Action::ADD_OUT_EDGE: { + if (edge == delta.vertex_edge.edge.ptr) { + link = {delta.vertex_edge.edge_type, delta.vertex_edge.vertex, delta.vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); + MG_ASSERT(it == vertex->out_edges.end(), "Invalid database state!"); + break; + } + } + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + break; + } + }); + return link; +} + +} // namespace + +namespace memgraph::storage { + +bool InMemoryEdgeTypeIndex::CreateIndex(EdgeTypeId edge_type, utils::SkipList<Vertex>::Accessor vertices) { + auto [it, emplaced] = index_.try_emplace(edge_type); + if (!emplaced) { + return false; + } + + utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; + try { + auto edge_acc = it->second.access(); + for (auto &from_vertex : vertices) { + if (from_vertex.deleted) { + continue; + } + + for (auto &edge : from_vertex.out_edges) { + const auto type = std::get<kEdgeTypeIdPos>(edge); + if (type == edge_type) { + auto *to_vertex = std::get<kVertexPos>(edge); + if (to_vertex->deleted) { + continue; + } + edge_acc.insert({&from_vertex, to_vertex, std::get<kEdgeRefPos>(edge).ptr, 0}); + } + } + } + } catch (const utils::OutOfMemoryException &) { + utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_exception_blocker; + index_.erase(it); + throw; + } + + return true; +} + +bool InMemoryEdgeTypeIndex::DropIndex(EdgeTypeId edge_type) { return index_.erase(edge_type) > 0; } + +bool InMemoryEdgeTypeIndex::IndexExists(EdgeTypeId edge_type) const { return index_.find(edge_type) != index_.end(); } + +std::vector<EdgeTypeId> InMemoryEdgeTypeIndex::ListIndices() const { + std::vector<EdgeTypeId> ret; + ret.reserve(index_.size()); + for (const auto &item : index_) { + ret.push_back(item.first); + } + return ret; +} + +void InMemoryEdgeTypeIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) { + auto maybe_stop = utils::ResettableCounter<2048>(); + + for (auto &label_storage : index_) { + if (token.stop_requested()) return; + + auto edges_acc = label_storage.second.access(); + for (auto it = edges_acc.begin(); it != edges_acc.end();) { + if (maybe_stop() && token.stop_requested()) return; + + auto next_it = it; + ++next_it; + + if (it->timestamp >= oldest_active_start_timestamp) { + it = next_it; + continue; + } + + if (next_it != edges_acc.end() || it->from_vertex->deleted || it->to_vertex->deleted || + !std::ranges::all_of(it->from_vertex->out_edges, [&](const auto &edge) { + auto *to_vertex = std::get<InMemoryEdgeTypeIndex::kVertexPos>(edge); + return to_vertex != it->to_vertex; + })) { + edges_acc.remove(*it); + } + + it = next_it; + } + } +} + +uint64_t InMemoryEdgeTypeIndex::ApproximateEdgeCount(EdgeTypeId edge_type) const { + if (auto it = index_.find(edge_type); it != index_.end()) { + return it->second.size(); + } + return 0; +} + +void InMemoryEdgeTypeIndex::UpdateOnEdgeCreation(Vertex *from, Vertex *to, EdgeRef edge_ref, EdgeTypeId edge_type, + const Transaction &tx) { + auto it = index_.find(edge_type); + if (it == index_.end()) { + return; + } + auto acc = it->second.access(); + acc.insert(Entry{from, to, edge_ref.ptr, tx.start_timestamp}); +} + +void InMemoryEdgeTypeIndex::UpdateOnEdgeModification(Vertex *old_from, Vertex *old_to, Vertex *new_from, Vertex *new_to, + EdgeRef edge_ref, EdgeTypeId edge_type, const Transaction &tx) { + auto it = index_.find(edge_type); + if (it == index_.end()) { + return; + } + auto acc = it->second.access(); + + auto entry_to_update = std::ranges::find_if(acc, [&](const auto &entry) { + return entry.from_vertex == old_from && entry.to_vertex == old_to && entry.edge == edge_ref.ptr; + }); + + acc.remove(Entry{entry_to_update->from_vertex, entry_to_update->to_vertex, entry_to_update->edge, + entry_to_update->timestamp}); + acc.insert(Entry{new_from, new_to, edge_ref.ptr, tx.start_timestamp}); +} + +InMemoryEdgeTypeIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, EdgeTypeId edge_type, + View view, Storage *storage, Transaction *transaction) + : index_accessor_(std::move(index_accessor)), + edge_type_(edge_type), + view_(view), + storage_(storage), + transaction_(transaction) {} + +InMemoryEdgeTypeIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator) + : self_(self), + index_iterator_(index_iterator), + current_edge_accessor_(EdgeRef{nullptr}, EdgeTypeId::FromInt(0), nullptr, nullptr, self_->storage_, nullptr), + current_edge_(nullptr) { + AdvanceUntilValid(); +} + +InMemoryEdgeTypeIndex::Iterable::Iterator &InMemoryEdgeTypeIndex::Iterable::Iterator::operator++() { + ++index_iterator_; + AdvanceUntilValid(); + return *this; +} + +void InMemoryEdgeTypeIndex::Iterable::Iterator::AdvanceUntilValid() { + for (; index_iterator_ != self_->index_accessor_.end(); ++index_iterator_) { + auto *from_vertex = index_iterator_->from_vertex; + auto *to_vertex = index_iterator_->to_vertex; + + if (!IsIndexEntryVisible(index_iterator_->edge, self_->transaction_, self_->view_) || from_vertex->deleted || + to_vertex->deleted) { + continue; + } + + const bool edge_was_deleted = index_iterator_->edge->deleted; + auto [edge_ref, edge_type, deleted_from_vertex, deleted_to_vertex] = GetEdgeInfo(); + MG_ASSERT(edge_ref != EdgeRef(nullptr), "Invalid database state!"); + + if (edge_was_deleted) { + from_vertex = deleted_from_vertex; + to_vertex = deleted_to_vertex; + } + + auto accessor = EdgeAccessor{edge_ref, edge_type, from_vertex, to_vertex, self_->storage_, self_->transaction_}; + if (!accessor.IsVisible(self_->view_)) { + continue; + } + + current_edge_accessor_ = accessor; + current_edge_ = edge_ref; + break; + } +} + +std::tuple<EdgeRef, EdgeTypeId, Vertex *, Vertex *> InMemoryEdgeTypeIndex::Iterable::Iterator::GetEdgeInfo() { + auto *from_vertex = index_iterator_->from_vertex; + auto *to_vertex = index_iterator_->to_vertex; + + if (index_iterator_->edge->deleted) { + const auto missing_in_edge = + VertexDeletedConnectedEdges(from_vertex, index_iterator_->edge, self_->transaction_, self_->view_); + const auto missing_out_edge = + VertexDeletedConnectedEdges(to_vertex, index_iterator_->edge, self_->transaction_, self_->view_); + if (missing_in_edge && missing_out_edge && + std::get<kEdgeRefPos>(*missing_in_edge) == std::get<kEdgeRefPos>(*missing_out_edge)) { + return std::make_tuple(std::get<kEdgeRefPos>(*missing_in_edge), std::get<kEdgeTypeIdPos>(*missing_in_edge), + to_vertex, from_vertex); + } + } + + const auto &from_edges = from_vertex->out_edges; + const auto &to_edges = to_vertex->in_edges; + + auto it = std::find_if(from_edges.begin(), from_edges.end(), [&](const auto &from_entry) { + const auto &from_edge = std::get<kEdgeRefPos>(from_entry); + return std::any_of(to_edges.begin(), to_edges.end(), [&](const auto &to_entry) { + const auto &to_edge = std::get<kEdgeRefPos>(to_entry); + return index_iterator_->edge->gid == from_edge.ptr->gid && from_edge.ptr->gid == to_edge.ptr->gid; + }); + }); + + if (it != from_edges.end()) { + const auto &from_edge = std::get<kEdgeRefPos>(*it); + return std::make_tuple(from_edge, std::get<kEdgeTypeIdPos>(*it), from_vertex, to_vertex); + } + + return {EdgeRef(nullptr), EdgeTypeId::FromUint(0U), nullptr, nullptr}; +} + +void InMemoryEdgeTypeIndex::RunGC() { + for (auto &index_entry : index_) { + index_entry.second.run_gc(); + } +} + +InMemoryEdgeTypeIndex::Iterable InMemoryEdgeTypeIndex::Edges(EdgeTypeId edge_type, View view, Storage *storage, + Transaction *transaction) { + const auto it = index_.find(edge_type); + MG_ASSERT(it != index_.end(), "Index for edge-type {} doesn't exist", edge_type.AsUint()); + return {it->second.access(), edge_type, view, storage, transaction}; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/edge_type_index.hpp b/src/storage/v2/inmemory/edge_type_index.hpp new file mode 100644 index 000000000..db8f7843f --- /dev/null +++ b/src/storage/v2/inmemory/edge_type_index.hpp @@ -0,0 +1,113 @@ +// 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 <map> +#include <utility> + +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/indices/edge_type_index.hpp" +#include "storage/v2/indices/label_index_stats.hpp" +#include "utils/rw_lock.hpp" +#include "utils/synchronized.hpp" + +namespace memgraph::storage { + +class InMemoryEdgeTypeIndex : public storage::EdgeTypeIndex { + private: + struct Entry { + Vertex *from_vertex; + Vertex *to_vertex; + + Edge *edge; + + uint64_t timestamp; + + bool operator<(const Entry &rhs) const { return edge->gid < rhs.edge->gid; } + bool operator==(const Entry &rhs) const { return edge->gid == rhs.edge->gid; } + }; + + public: + InMemoryEdgeTypeIndex() = default; + + /// @throw std::bad_alloc + bool CreateIndex(EdgeTypeId edge_type, utils::SkipList<Vertex>::Accessor vertices); + + /// Returns false if there was no index to drop + bool DropIndex(EdgeTypeId edge_type) override; + + bool IndexExists(EdgeTypeId edge_type) const override; + + std::vector<EdgeTypeId> ListIndices() const override; + + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token); + + uint64_t ApproximateEdgeCount(EdgeTypeId edge_type) const override; + + void UpdateOnEdgeCreation(Vertex *from, Vertex *to, EdgeRef edge_ref, EdgeTypeId edge_type, + const Transaction &tx) override; + + void UpdateOnEdgeModification(Vertex *old_from, Vertex *old_to, Vertex *new_from, Vertex *new_to, EdgeRef edge_ref, + EdgeTypeId edge_type, const Transaction &tx) override; + + static constexpr std::size_t kEdgeTypeIdPos = 0U; + static constexpr std::size_t kVertexPos = 1U; + static constexpr std::size_t kEdgeRefPos = 2U; + + class Iterable { + public: + Iterable(utils::SkipList<Entry>::Accessor index_accessor, EdgeTypeId edge_type, View view, Storage *storage, + Transaction *transaction); + + class Iterator { + public: + Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator); + + EdgeAccessor const &operator*() const { return current_edge_accessor_; } + + bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; } + bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; } + + Iterator &operator++(); + + private: + void AdvanceUntilValid(); + std::tuple<EdgeRef, EdgeTypeId, Vertex *, Vertex *> GetEdgeInfo(); + + Iterable *self_; + utils::SkipList<Entry>::Iterator index_iterator_; + EdgeAccessor current_edge_accessor_; + EdgeRef current_edge_{nullptr}; + }; + + Iterator begin() { return {this, index_accessor_.begin()}; } + Iterator end() { return {this, index_accessor_.end()}; } + + private: + utils::SkipList<Entry>::Accessor index_accessor_; + EdgeTypeId edge_type_; + View view_; + Storage *storage_; + Transaction *transaction_; + }; + + void RunGC(); + + Iterable Edges(EdgeTypeId edge_type, View view, Storage *storage, Transaction *transaction); + + private: + std::map<EdgeTypeId, utils::SkipList<Entry>> index_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp index 3a4fa9b91..1ea909450 100644 --- a/src/storage/v2/inmemory/storage.cpp +++ b/src/storage/v2/inmemory/storage.cpp @@ -20,6 +20,7 @@ #include "storage/v2/durability/snapshot.hpp" #include "storage/v2/edge_direction.hpp" #include "storage/v2/id_types.hpp" +#include "storage/v2/inmemory/edge_type_index.hpp" #include "storage/v2/metadata_delta.hpp" /// REPLICATION /// @@ -350,6 +351,9 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); + // Update indices if they exist. + storage_->indices_.UpdateOnEdgeCreation(from_vertex, to_vertex, edge, edge_type, transaction_); + // Increment edge count. storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); }}; @@ -553,6 +557,11 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetFrom(EdgeAccessor CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, new_from_vertex, edge_ref); to_vertex->in_edges.emplace_back(edge_type, new_from_vertex, edge_ref); + auto *in_memory = static_cast<InMemoryStorage *>(storage_); + auto *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(in_memory->indices_.edge_type_index_.get()); + mem_edge_type_index->UpdateOnEdgeModification(old_from_vertex, to_vertex, new_from_vertex, to_vertex, edge_ref, + edge_type, transaction_); + transaction_.manyDeltasCache.Invalidate(new_from_vertex, edge_type, EdgeDirection::OUT); transaction_.manyDeltasCache.Invalidate(old_from_vertex, edge_type, EdgeDirection::OUT); transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); @@ -659,6 +668,11 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor * CreateAndLinkDelta(&transaction_, new_to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge_ref); new_to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge_ref); + auto *in_memory = static_cast<InMemoryStorage *>(storage_); + auto *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(in_memory->indices_.edge_type_index_.get()); + mem_edge_type_index->UpdateOnEdgeModification(from_vertex, old_to_vertex, from_vertex, new_to_vertex, edge_ref, + edge_type, transaction_); + transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); transaction_.manyDeltasCache.Invalidate(old_to_vertex, edge_type, EdgeDirection::IN); transaction_.manyDeltasCache.Invalidate(new_to_vertex, edge_type, EdgeDirection::IN); @@ -1264,6 +1278,18 @@ utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryA return {}; } +utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex( + EdgeTypeId edge_type) { + MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!"); + auto *in_memory = static_cast<InMemoryStorage *>(storage_); + auto *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(in_memory->indices_.edge_type_index_.get()); + if (!mem_edge_type_index->CreateIndex(edge_type, in_memory->vertices_.access())) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + transaction_.md_deltas.emplace_back(MetadataDelta::edge_index_create, edge_type); + return {}; +} + utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::DropIndex(LabelId label) { MG_ASSERT(unique_guard_.owns_lock(), "Dropping label index requires a unique access to the storage!"); auto *in_memory = static_cast<InMemoryStorage *>(storage_); @@ -1292,6 +1318,18 @@ utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryA return {}; } +utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::DropIndex( + EdgeTypeId edge_type) { + MG_ASSERT(unique_guard_.owns_lock(), "Drop index requires a unique access to the storage!"); + auto *in_memory = static_cast<InMemoryStorage *>(storage_); + auto *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(in_memory->indices_.edge_type_index_.get()); + if (!mem_edge_type_index->DropIndex(edge_type)) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + transaction_.md_deltas.emplace_back(MetadataDelta::edge_index_drop, edge_type); + return {}; +} + utils::BasicResult<StorageExistenceConstraintDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateExistenceConstraint(LabelId label, PropertyId property) { MG_ASSERT(unique_guard_.owns_lock(), "Creating existence requires a unique access to the storage!"); @@ -1383,6 +1421,11 @@ VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices( mem_label_property_index->Vertices(label, property, lower_bound, upper_bound, view, storage_, &transaction_)); } +EdgesIterable InMemoryStorage::InMemoryAccessor::Edges(EdgeTypeId edge_type, View view) { + auto *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(storage_->indices_.edge_type_index_.get()); + return EdgesIterable(mem_edge_type_index->Edges(edge_type, view, storage_, &transaction_)); +} + Transaction InMemoryStorage::CreateTransaction( IsolationLevel isolation_level, StorageMode storage_mode, memgraph::replication_coordination_glue::ReplicationRole replication_role) { @@ -2017,6 +2060,10 @@ bool InMemoryStorage::AppendToWal(const Transaction &transaction, uint64_t final AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_INDEX_CREATE, md_delta.label, final_commit_timestamp); } break; + case MetadataDelta::Action::EDGE_INDEX_CREATE: { + AppendToWalDataDefinition(durability::StorageMetadataOperation::EDGE_TYPE_INDEX_CREATE, md_delta.edge_type, + final_commit_timestamp); + } break; case MetadataDelta::Action::LABEL_PROPERTY_INDEX_CREATE: { const auto &info = md_delta.label_property; AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE, info.label, @@ -2026,6 +2073,10 @@ bool InMemoryStorage::AppendToWal(const Transaction &transaction, uint64_t final AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_INDEX_DROP, md_delta.label, final_commit_timestamp); } break; + case MetadataDelta::Action::EDGE_INDEX_DROP: { + AppendToWalDataDefinition(durability::StorageMetadataOperation::EDGE_TYPE_INDEX_DROP, md_delta.edge_type, + final_commit_timestamp); + } break; case MetadataDelta::Action::LABEL_PROPERTY_INDEX_DROP: { const auto &info = md_delta.label_property; AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP, info.label, @@ -2091,6 +2142,12 @@ void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOpera repl_storage_state_.AppendOperation(operation, label, properties, stats, property_stats, final_commit_timestamp); } +void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, EdgeTypeId edge_type, + uint64_t final_commit_timestamp) { + wal_file_->AppendOperation(operation, edge_type, final_commit_timestamp); + repl_storage_state_.AppendOperation(operation, edge_type, final_commit_timestamp); +} + void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label, const std::set<PropertyId> &properties, LabelPropertyIndexStats property_stats, @@ -2240,7 +2297,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 *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(in_memory->indices_.edge_type_index_.get()); + return {mem_label_index->ListIndices(), mem_label_property_index->ListIndices(), mem_edge_type_index->ListIndices()}; } ConstraintsInfo InMemoryStorage::InMemoryAccessor::ListAllConstraints() const { const auto *mem_storage = static_cast<InMemoryStorage *>(storage_); diff --git a/src/storage/v2/inmemory/storage.hpp b/src/storage/v2/inmemory/storage.hpp index c0e46d0c9..6d10e0fbd 100644 --- a/src/storage/v2/inmemory/storage.hpp +++ b/src/storage/v2/inmemory/storage.hpp @@ -16,6 +16,7 @@ #include <memory> #include <utility> #include "storage/v2/indices/label_index_stats.hpp" +#include "storage/v2/inmemory/edge_type_index.hpp" #include "storage/v2/inmemory/label_index.hpp" #include "storage/v2/inmemory/label_property_index.hpp" #include "storage/v2/inmemory/replication/recovery.hpp" @@ -53,6 +54,7 @@ class InMemoryStorage final : public Storage { const InMemoryStorage *storage); friend class InMemoryLabelIndex; friend class InMemoryLabelPropertyIndex; + friend class InMemoryEdgeTypeIndex; public: enum class CreateSnapshotError : uint8_t { DisabledForReplica, ReachedMaxNumTries }; @@ -107,6 +109,8 @@ class InMemoryStorage final : public Storage { const std::optional<utils::Bound<PropertyValue>> &lower_bound, const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) override; + EdgesIterable Edges(EdgeTypeId edge_type, View view) override; + /// Return approximate number of all vertices in the database. /// Note that this is always an over-estimate and never an under-estimate. uint64_t ApproximateVertexCount() const override { @@ -145,6 +149,10 @@ class InMemoryStorage final : public Storage { label, property, lower, upper); } + uint64_t ApproximateEdgeCount(EdgeTypeId id) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.edge_type_index_->ApproximateEdgeCount(id); + } + template <typename TResult, typename TIndex, typename TIndexKey> std::optional<TResult> GetIndexStatsForIndex(TIndex *index, TIndexKey &&key) const { return index->GetIndexStats(key); @@ -204,6 +212,10 @@ class InMemoryStorage final : public Storage { return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->IndexExists(label, property); } + bool EdgeTypeIndexExists(EdgeTypeId edge_type) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.edge_type_index_->IndexExists(edge_type); + } + IndicesInfo ListAllIndices() const override; ConstraintsInfo ListAllConstraints() const override; @@ -239,6 +251,14 @@ class InMemoryStorage final : public Storage { /// @throw std::bad_alloc utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) override; + /// Create an index. + /// Returns void if the index has been created. + /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `IndexDefinitionError`: the index already exists. + /// @throw std::bad_alloc + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type) override; + /// Drop an existing index. /// Returns void if the index has been dropped. /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: @@ -253,6 +273,13 @@ class InMemoryStorage final : public Storage { /// * `IndexDefinitionError`: the index does not exist. utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) override; + /// Drop an existing index. + /// Returns void if the index has been dropped. + /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `IndexDefinitionError`: the index does not exist. + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(EdgeTypeId edge_type) override; + /// Returns void if the existence constraint has been created. /// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be: /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. @@ -374,20 +401,17 @@ class InMemoryStorage final : public Storage { /// Return true in all cases excepted if any sync replicas have not sent confirmation. [[nodiscard]] bool AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp, DatabaseAccessProtector db_acc); - /// 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. + void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, EdgeTypeId edge_type, + uint64_t final_commit_timestamp); 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. 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. 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. void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label, const std::set<PropertyId> &properties, LabelIndexStats stats, LabelPropertyIndexStats property_stats, uint64_t final_commit_timestamp); diff --git a/src/storage/v2/metadata_delta.hpp b/src/storage/v2/metadata_delta.hpp index 94d806c19..b34966a62 100644 --- a/src/storage/v2/metadata_delta.hpp +++ b/src/storage/v2/metadata_delta.hpp @@ -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, + EDGE_INDEX_CREATE, + EDGE_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 EdgeIndexCreate { + } edge_index_create; + static constexpr struct EdgeIndexDrop { + } edge_index_drop; static constexpr struct ExistenceConstraintCreate { } existence_constraint_create; static constexpr struct ExistenceConstraintDrop { @@ -87,6 +93,11 @@ struct MetadataDelta { MetadataDelta(LabelPropertyIndexStatsClear /*tag*/, LabelId label) : action(Action::LABEL_PROPERTY_INDEX_STATS_CLEAR), label{label} {} + MetadataDelta(EdgeIndexCreate /*tag*/, EdgeTypeId edge_type) + : action(Action::EDGE_INDEX_CREATE), edge_type(edge_type) {} + + MetadataDelta(EdgeIndexDrop /*tag*/, EdgeTypeId edge_type) : action(Action::EDGE_INDEX_DROP), edge_type(edge_type) {} + MetadataDelta(ExistenceConstraintCreate /*tag*/, LabelId label, PropertyId property) : action(Action::EXISTENCE_CONSTRAINT_CREATE), label_property{label, property} {} @@ -114,6 +125,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::EDGE_INDEX_CREATE: + case Action::EDGE_INDEX_DROP: case Action::EXISTENCE_CONSTRAINT_CREATE: case Action::EXISTENCE_CONSTRAINT_DROP: break; @@ -129,6 +142,8 @@ struct MetadataDelta { union { LabelId label; + EdgeTypeId edge_type; + struct { LabelId label; PropertyId property; diff --git a/src/storage/v2/replication/replication_client.cpp b/src/storage/v2/replication/replication_client.cpp index fb332672a..3c1081206 100644 --- a/src/storage/v2/replication/replication_client.cpp +++ b/src/storage/v2/replication/replication_client.cpp @@ -407,6 +407,12 @@ void ReplicaStream::AppendOperation(durability::StorageMetadataOperation operati timestamp); } +void ReplicaStream::AppendOperation(durability::StorageMetadataOperation operation, EdgeTypeId edge_type, + uint64_t timestamp) { + replication::Encoder encoder(stream_.GetBuilder()); + EncodeOperation(&encoder, storage_->name_id_mapper_.get(), operation, edge_type, timestamp); +} + replication::AppendDeltasRes ReplicaStream::Finalize() { return stream_.AwaitResponse(); } } // namespace memgraph::storage diff --git a/src/storage/v2/replication/replication_client.hpp b/src/storage/v2/replication/replication_client.hpp index 063501111..77a9ba40b 100644 --- a/src/storage/v2/replication/replication_client.hpp +++ b/src/storage/v2/replication/replication_client.hpp @@ -65,6 +65,9 @@ class ReplicaStream { const std::set<PropertyId> &properties, const LabelIndexStats &stats, const LabelPropertyIndexStats &property_stats, uint64_t timestamp); + /// @throw rpc::RpcFailedException + void AppendOperation(durability::StorageMetadataOperation operation, EdgeTypeId edge_type, uint64_t timestamp); + /// @throw rpc::RpcFailedException replication::AppendDeltasRes Finalize(); diff --git a/src/storage/v2/replication/replication_storage_state.cpp b/src/storage/v2/replication/replication_storage_state.cpp index 25cf484c9..b8f3fef62 100644 --- a/src/storage/v2/replication/replication_storage_state.cpp +++ b/src/storage/v2/replication/replication_storage_state.cpp @@ -53,6 +53,16 @@ void ReplicationStorageState::AppendOperation(durability::StorageMetadataOperati }); } +void ReplicationStorageState::AppendOperation(durability::StorageMetadataOperation operation, EdgeTypeId edge_type, + uint64_t final_commit_timestamp) { + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->IfStreamingTransaction( + [&](auto &stream) { stream.AppendOperation(operation, edge_type, final_commit_timestamp); }); + } + }); +} + bool ReplicationStorageState::FinalizeTransaction(uint64_t timestamp, Storage *storage, DatabaseAccessProtector db_acc) { return replication_clients_.WithLock([=, db_acc = std::move(db_acc)](auto &clients) mutable { diff --git a/src/storage/v2/replication/replication_storage_state.hpp b/src/storage/v2/replication/replication_storage_state.hpp index 91cec563c..f99807c13 100644 --- a/src/storage/v2/replication/replication_storage_state.hpp +++ b/src/storage/v2/replication/replication_storage_state.hpp @@ -46,6 +46,8 @@ struct ReplicationStorageState { void AppendOperation(durability::StorageMetadataOperation operation, LabelId label, const std::set<PropertyId> &properties, const LabelIndexStats &stats, const LabelPropertyIndexStats &property_stats, uint64_t final_commit_timestamp); + void AppendOperation(durability::StorageMetadataOperation operation, EdgeTypeId edge_type, + uint64_t final_commit_timestamp); bool FinalizeTransaction(uint64_t timestamp, Storage *storage, DatabaseAccessProtector db_acc); // Getters diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 5868d70a3..58936bd56 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -30,6 +30,7 @@ #include "storage/v2/durability/paths.hpp" #include "storage/v2/durability/wal.hpp" #include "storage/v2/edge_accessor.hpp" +#include "storage/v2/edges_iterable.hpp" #include "storage/v2/indices/indices.hpp" #include "storage/v2/mvcc.hpp" #include "storage/v2/replication/enums.hpp" @@ -61,6 +62,7 @@ class EdgeAccessor; struct IndicesInfo { std::vector<LabelId> label; std::vector<std::pair<LabelId, PropertyId>> label_property; + std::vector<EdgeTypeId> edge_type; }; struct ConstraintsInfo { @@ -172,6 +174,8 @@ class Storage { const std::optional<utils::Bound<PropertyValue>> &lower_bound, const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) = 0; + virtual EdgesIterable Edges(EdgeTypeId edge_type, View view) = 0; + virtual Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex); virtual Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex( @@ -192,6 +196,8 @@ class Storage { const std::optional<utils::Bound<PropertyValue>> &lower, const std::optional<utils::Bound<PropertyValue>> &upper) const = 0; + virtual uint64_t ApproximateEdgeCount(EdgeTypeId id) const = 0; + virtual std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const = 0; virtual std::optional<storage::LabelPropertyIndexStats> GetIndexStats( @@ -224,6 +230,8 @@ class Storage { virtual bool LabelPropertyIndexExists(LabelId label, PropertyId property) const = 0; + virtual bool EdgeTypeIndexExists(EdgeTypeId edge_type) const = 0; + virtual IndicesInfo ListAllIndices() const = 0; virtual ConstraintsInfo ListAllConstraints() const = 0; @@ -268,10 +276,14 @@ class Storage { virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) = 0; + virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type) = 0; + virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) = 0; virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) = 0; + virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(EdgeTypeId edge_type) = 0; + virtual utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint( LabelId label, PropertyId property) = 0; diff --git a/src/storage/v2/vertices_iterable.cpp b/src/storage/v2/vertices_iterable.cpp index f6ff46da6..9753052ae 100644 --- a/src/storage/v2/vertices_iterable.cpp +++ b/src/storage/v2/vertices_iterable.cpp @@ -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 @@ -10,7 +10,6 @@ // licenses/APL.txt. #include "storage/v2/vertices_iterable.hpp" - namespace memgraph::storage { VerticesIterable::VerticesIterable(AllVerticesIterable vertices) : type_(Type::ALL) { diff --git a/src/storage/v2/vertices_iterable.hpp b/src/storage/v2/vertices_iterable.hpp index e057e8a38..6075a68a2 100644 --- a/src/storage/v2/vertices_iterable.hpp +++ b/src/storage/v2/vertices_iterable.hpp @@ -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 diff --git a/src/utils/atomic_memory_block.hpp b/src/utils/atomic_memory_block.hpp index c15424549..31a3cf3a9 100644 --- a/src/utils/atomic_memory_block.hpp +++ b/src/utils/atomic_memory_block.hpp @@ -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 diff --git a/src/utils/event_counter.cpp b/src/utils/event_counter.cpp index a7f4d30fb..54ff4ed5c 100644 --- a/src/utils/event_counter.cpp +++ b/src/utils/event_counter.cpp @@ -26,6 +26,7 @@ M(ScanAllByLabelPropertyValueOperator, Operator, "Number of times ScanAllByLabelPropertyValue operator was used.") \ M(ScanAllByLabelPropertyOperator, Operator, "Number of times ScanAllByLabelProperty operator was used.") \ M(ScanAllByIdOperator, Operator, "Number of times ScanAllById operator was used.") \ + M(ScanAllByEdgeTypeOperator, Operator, "Number of times ScanAllByEdgeTypeOperator operator was used.") \ M(ExpandOperator, Operator, "Number of times Expand operator was used.") \ M(ExpandVariableOperator, Operator, "Number of times ExpandVariable operator was used.") \ M(ConstructNamedPathOperator, Operator, "Number of times ConstructNamedPath operator was used.") \ diff --git a/src/utils/settings.cpp b/src/utils/settings.cpp index 4768edc42..5e0954b4b 100644 --- a/src/utils/settings.cpp +++ b/src/utils/settings.cpp @@ -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 diff --git a/src/utils/typeinfo.hpp b/src/utils/typeinfo.hpp index 3ed6128fc..77910f731 100644 --- a/src/utils/typeinfo.hpp +++ b/src/utils/typeinfo.hpp @@ -32,6 +32,7 @@ enum class TypeId : uint64_t { SCAN_ALL_BY_LABEL_PROPERTY_VALUE, SCAN_ALL_BY_LABEL_PROPERTY, SCAN_ALL_BY_ID, + SCAN_ALL_BY_EDGE_TYPE, EXPAND_COMMON, EXPAND, EXPANSION_LAMBDA, @@ -185,6 +186,7 @@ enum class TypeId : uint64_t { AST_EXPLAIN_QUERY, AST_PROFILE_QUERY, AST_INDEX_QUERY, + AST_EDGE_INDEX_QUERY, AST_CREATE, AST_CALL_PROCEDURE, AST_MATCH, diff --git a/tests/integration/durability/tests/v17/test_all/create_dataset.cypher b/tests/integration/durability/tests/v17/test_all/create_dataset.cypher new file mode 100644 index 000000000..9ee350d9a --- /dev/null +++ b/tests/integration/durability/tests/v17/test_all/create_dataset.cypher @@ -0,0 +1,22 @@ +// --storage-items-per-batch is set to 10 +CREATE INDEX ON :`label2`(`prop2`); +CREATE INDEX ON :`label2`(`prop`); +CREATE INDEX ON :`label`; +CREATE INDEX ON :__mg_vertex__(__mg_id__); +CREATE EDGE INDEX ON :`edge_type`; +CREATE (:`edge_index_from`), (:`edge_index_to`); +MATCH (n:`edge_index_from`), (m:`edge_index_to`) CREATE (n)-[r:`edge_type`]->(m); +CREATE (:__mg_vertex__:`label2` {__mg_id__: 0, `prop2`: ["kaj", 2, Null, {`prop4`: -1.341}], `ext`: 2, `prop`: "joj"}); +CREATE (:__mg_vertex__:`label2`:`label` {__mg_id__: 1, `ext`: 2, `prop`: "joj"}); +CREATE (:__mg_vertex__:`label2` {__mg_id__: 2, `prop2`: 2, `prop`: 1}); +CREATE (:__mg_vertex__:`label2` {__mg_id__: 3, `prop2`: 2, `prop`: 2}); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 0 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 1 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 3 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); +CREATE CONSTRAINT ON (u:`label`) ASSERT EXISTS (u.`ext`); +CREATE CONSTRAINT ON (u:`label2`) ASSERT u.`prop2`, u.`prop` IS UNIQUE; +ANALYZE GRAPH; +DROP INDEX ON :__mg_vertex__(__mg_id__); +DROP EDGE INDEX ON :`edge_type`; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v17/test_all/expected_snapshot.cypher b/tests/integration/durability/tests/v17/test_all/expected_snapshot.cypher new file mode 100644 index 000000000..fb2d74667 --- /dev/null +++ b/tests/integration/durability/tests/v17/test_all/expected_snapshot.cypher @@ -0,0 +1,19 @@ + CREATE (:__mg_vertex__:`edge_index_from` {__mg_id__: 0}); + CREATE (:__mg_vertex__:`edge_index_to` {__mg_id__: 1}); + CREATE (:__mg_vertex__:`label2` {__mg_id__: 2, `prop2`: ["kaj", 2, Null, {`prop4`: -1.341}], `ext`: 2, `prop`: "joj"}); + CREATE (:__mg_vertex__:`label2` {__mg_id__: 4, `prop2`: 2, `prop`: 1}); + CREATE (:__mg_vertex__:`label2` {__mg_id__: 5, `prop2`: 2, `prop`: 2}); + CREATE (:__mg_vertex__:`label`:`label2` {__mg_id__: 3, `ext`: 2, `prop`: "joj"}); + CREATE CONSTRAINT ON (u:`label2`) ASSERT u.`prop2`, u.`prop` IS UNIQUE; + CREATE CONSTRAINT ON (u:`label`) ASSERT EXISTS (u.`ext`); + CREATE INDEX ON :__mg_vertex__(__mg_id__); + CREATE INDEX ON :`label2`(`prop2`); + CREATE INDEX ON :`label2`(`prop`); + CREATE INDEX ON :`label`; + DROP INDEX ON :__mg_vertex__(__mg_id__); + MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`edge_type`]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 2 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 3 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 4 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 5 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); diff --git a/tests/integration/durability/tests/v17/test_all/expected_wal.cypher b/tests/integration/durability/tests/v17/test_all/expected_wal.cypher new file mode 100644 index 000000000..33efec9e2 --- /dev/null +++ b/tests/integration/durability/tests/v17/test_all/expected_wal.cypher @@ -0,0 +1,19 @@ + CREATE (:__mg_vertex__:`edge_index_from` {__mg_id__: 0}); + CREATE (:__mg_vertex__:`edge_index_to` {__mg_id__: 1}); + CREATE (:__mg_vertex__:`label2` {__mg_id__: 2, `prop2`: ["kaj", 2, Null, {`prop4`: -1.341}], `prop`: "joj", `ext`: 2}); + CREATE (:__mg_vertex__:`label2` {__mg_id__: 4, `prop2`: 2, `prop`: 1}); + CREATE (:__mg_vertex__:`label2` {__mg_id__: 5, `prop2`: 2, `prop`: 2}); + CREATE (:__mg_vertex__:`label`:`label2` {__mg_id__: 3, `prop`: "joj", `ext`: 2}); + CREATE CONSTRAINT ON (u:`label2`) ASSERT u.`prop2`, u.`prop` IS UNIQUE; + CREATE CONSTRAINT ON (u:`label`) ASSERT EXISTS (u.`ext`); + CREATE INDEX ON :__mg_vertex__(__mg_id__); + CREATE INDEX ON :`label2`(`prop2`); + CREATE INDEX ON :`label2`(`prop`); + CREATE INDEX ON :`label`; + DROP INDEX ON :__mg_vertex__(__mg_id__); + MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`edge_type`]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 2 CREATE (u)-[:`link` {`prop`: -1, `ext`: [false, {`k`: "l"}]}]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 3 CREATE (u)-[:`link` {`prop`: -1, `ext`: [false, {`k`: "l"}]}]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 4 CREATE (u)-[:`link` {`prop`: -1, `ext`: [false, {`k`: "l"}]}]->(v); + MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 5 CREATE (u)-[:`link` {`prop`: -1, `ext`: [false, {`k`: "l"}]}]->(v); diff --git a/tests/integration/durability/tests/v17/test_all/snapshot.bin b/tests/integration/durability/tests/v17/test_all/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..9cc54b4809a1a843cc80955e0b5634a4f70154e1 GIT binary patch literal 2067 zcmcIlJ#Q015M91O2tn2xwnJwKA`&`@i*E_89MMowQljX5w`avRwi8^0Cci8azk&)O zQBl)G3KbB0-kXRsqKMe)Y3I$GH?w<tuV1~NteNF{X$OMrw@XW8JJ$p}xGrE5U?1S^ z4G9eZz5tvGXxk;l7=pHy8pd9hAc40(W!YO2^Xp*wHRJCi*8z+?;kZqqCb+Qll|Rk0 z#)YM?{x9i-Q`e!($>@Qs;D{fiS#stk(Jh>Sy$SoxH0}^Uh*cBLQKiY|&{52<V_4oH zI4XvoBL3dFOCp!p%nqir+_ygeI2s<$Pd+M{8pG*OuN>p*2=y(B+S_9CTqBI($|{>3 zF$AD}{L2F}RFxhu6g5mTIozR5hPXo=hPZ=Svhl<+#_2TML`YAF)Ai~WENzXJ=mgq{ zr4^<jMr}&3tjkeQfUW$!?glm0CG(kvoz~Kl+fhq22@bE0a_Fo{W(jawZx-uQPimb4 zeDAGLZgePFUwfi+f^@U9$^6GFn#R+tA)19X4PoaYKQB(t;T*?BKaP2?9Gs5#CL;=w zeD*DG(VxS6ve4M#TjcJDr&tZ*?%t$|hr9Es1>2#AMR@*U7A@XIcej~YA57oTKi`aa zIOoC9^qIXUwD-tuh+2_vhdP93X%@3Q&9k~ri>xZrGVAA|)2+&o<s|1X%I&P|mT6su o-{d;hX+OkH+U;lYdC?6;43&K<%KAB=I}S~M>=?C%_^U_OFSaa&o&W#< literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_all/wal.bin b/tests/integration/durability/tests/v17/test_all/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..61a33372da4fa550b0bbaf5b614b319121e6eabc GIT binary patch literal 3582 zcmbVO%Wl&^6b(-S;?WwnMu4CSRfQ0)nrD+P*|0*Y(o`t|WV!Yv%A-k?xTSz#!*3z+ zA$$i5RxnQH+&gwAiAolcJ#)^v=ia$9o;R;Q_Es~~-z)2>da}N&hiCStdMfUet5s1c zyFnpebc<!b>Q=*&aCa+yAm4=iD-pOH6mnsqnR9o=an&tKid$)hq1&wZmEC+*ggb?t zrGztE)@Akh&c3dmdj7HK<?U<gbvPOf^Nc&%vaZgfU?Qe4Ofn$-mQ*J}ctT=skm?=v z<asA8?@Wv$aqfBcO?|W-dLC0G`<9v&gcD&)c+sb!;7N(Qt#JEV2{2ZpG5a3mVv%+~ zln$Ds!HLelq#!(2voK~Og^7tNKN>Jn8o8_Jha40cl5?6OVdhDR70LyR$YV8HSn`r6 zrxA%|nsxv~>MWM%3TF)~?$}q9nXcb)2=1&n>r@k+T6FIJ`tz;xBl`W*7zVm=C$V;C z&@rM>xJia_YdBOnBOYGGdBk2BDN76fz6C6g)o5&9X~EWz>Cze?sE^tjK-4+hf~pwM zhN@sKI2lbHnWm}&Wmy%Q8V*i)tVYMeysCh$A=6a>q|Rcf%H$Z*SIdBft4Y^rd&U4Y z6o)qv@G|9fN*P9YtY%?sUdmu=$aKnp)L9IwGbtOe$&@(-1JsZ*mYj2vDL+stA5sNv zMCrBrU1~?#<xFYJQl@M1SN)7oxQH8X4JkOQ8SU&q<8~bzk3H6srXP}N2+T2!U`%OH zB1B1>hPnoY&V@}wOPYR6rlCd-l9fjNnbM$TAWGUa)HNu=T-Y?Ur0FMQ8m`De(lpwb zDGlD%;HFJOU4#C&X=q8)8_M*D<OPJjTCyMvlK8B`d#PIyw+gb06Ub8q;x}eXp-m;^ zrvg7x<6pAD;ItoQ_i`^cvi@1i3q@-rM0T(AmB~TM1!+x#chg!0sTH=zQM(^R-kIM! z6=P3@$;+v6<VW#rklowaE~I|SMr$-U9eU&TXK5w3T}pb(E%K(XtUY6`INw_AI$^D3 z^pf&hNLtG$tl=7#xcblyk**8&QM!T)Y{pZ%O_Ry_o@7gGsbKHTUyWINOp)y8irpj} zT^D&$;$A3R9pCQ^5h!nGMaSV_U5q>6Enuv!u1H7m?59VIe85N9Qbn8v%RHh9%_Y`7 R5^{-0?Dyr0gj}e2^bhc)#{2*P literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_constraints/create_dataset.cypher b/tests/integration/durability/tests/v17/test_constraints/create_dataset.cypher new file mode 100644 index 000000000..96bb4bac4 --- /dev/null +++ b/tests/integration/durability/tests/v17/test_constraints/create_dataset.cypher @@ -0,0 +1,6 @@ +CREATE CONSTRAINT ON (u:`label2`) ASSERT EXISTS (u.`ext2`); +CREATE CONSTRAINT ON (u:`label`) ASSERT EXISTS (u.`ext`); +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`a` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`b` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`c` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label2`) ASSERT u.`a`, u.`b` IS UNIQUE; diff --git a/tests/integration/durability/tests/v17/test_constraints/expected_snapshot.cypher b/tests/integration/durability/tests/v17/test_constraints/expected_snapshot.cypher new file mode 100644 index 000000000..fbe2c28ab --- /dev/null +++ b/tests/integration/durability/tests/v17/test_constraints/expected_snapshot.cypher @@ -0,0 +1,6 @@ +CREATE CONSTRAINT ON (u:`label2`) ASSERT EXISTS (u.`ext2`); +CREATE CONSTRAINT ON (u:`label`) ASSERT EXISTS (u.`ext`); +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`c` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`b` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`a` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label2`) ASSERT u.`b`, u.`a` IS UNIQUE; diff --git a/tests/integration/durability/tests/v17/test_constraints/expected_wal.cypher b/tests/integration/durability/tests/v17/test_constraints/expected_wal.cypher new file mode 100644 index 000000000..9260455ed --- /dev/null +++ b/tests/integration/durability/tests/v17/test_constraints/expected_wal.cypher @@ -0,0 +1,6 @@ +CREATE CONSTRAINT ON (u:`label2`) ASSERT EXISTS (u.`ext2`); +CREATE CONSTRAINT ON (u:`label`) ASSERT EXISTS (u.`ext`); +CREATE CONSTRAINT ON (u:`label2`) ASSERT u.`a`, u.`b` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`a` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`b` IS UNIQUE; +CREATE CONSTRAINT ON (u:`label`) ASSERT u.`c` IS UNIQUE; diff --git a/tests/integration/durability/tests/v17/test_constraints/snapshot.bin b/tests/integration/durability/tests/v17/test_constraints/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..76986ab9af9feb2f1a955fdddbacb9fa3a1ad874 GIT binary patch literal 625 zcmaKpJx&8L5QR4+kSaw+J^=|LjivRh_pePu#Tk(8b)bmSBn5|{=6clJ0z~FbA_c{b zKh1mdJhq>o_Pcp;Tx)t2`UgHBeFD~_BheS&8}M=?(wo4Nq^r0BE7cxI`p)c<U7Vyb zCYb>A5N%iK79N!}P(7E@m99Gpyr(ImQ8I{5ga9o?!f{#(gqs*Zen03v4KlVN<}D%H zAm`W1`f>&{{F(Vtg@swln1gGECRD90x$3O<iEZpRZ2Q)^&TslUH>~E6a}Kdfc1WGK cp3Bn4wi>MCvQFOB+~+_uJb>(Hzjjgi0aYC*O8@`> literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_constraints/wal.bin b/tests/integration/durability/tests/v17/test_constraints/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..f2d54e5fd2ccb96b8792d650d271c29e2c1a47ae GIT binary patch literal 460 zcma)&JqyAx5Qgg~A`TL=J1E@(%~zYQE>7-Uisag4>L}>$A2-lTqmyQO!#&U4ao5XZ z*k|KAlLIF5!f>P~OoesURHb5Np-ZNu)XYLJnJ`9M=xW|qVuGC(-UGORc+ONW4Kqpr z^Sl;DI__%d2sX}gGRN4`HKxIW8w6eA*FEndVNFhAp{1)&gD#Sgpuoqzw{M=zkf$-& k#0G7H<)0cPHfY;_wJxzi+f7<rcRa&%pS0za!r-ViZvzA{R{#J2 literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_edges/create_dataset.cypher b/tests/integration/durability/tests/v17/test_edges/create_dataset.cypher new file mode 100644 index 000000000..ab3b3af6d --- /dev/null +++ b/tests/integration/durability/tests/v17/test_edges/create_dataset.cypher @@ -0,0 +1,60 @@ +// --storage-items-per-batch is set to 7 +CREATE INDEX ON :__mg_vertex__(__mg_id__); +CREATE (:__mg_vertex__ {__mg_id__: 0}); +CREATE (:__mg_vertex__ {__mg_id__: 1}); +CREATE (:__mg_vertex__ {__mg_id__: 2}); +CREATE (:__mg_vertex__ {__mg_id__: 3}); +CREATE (:__mg_vertex__ {__mg_id__: 4}); +CREATE (:__mg_vertex__ {__mg_id__: 5}); +CREATE (:__mg_vertex__ {__mg_id__: 6}); +CREATE (:__mg_vertex__ {__mg_id__: 7}); +CREATE (:__mg_vertex__ {__mg_id__: 8}); +CREATE (:__mg_vertex__ {__mg_id__: 9}); +CREATE (:__mg_vertex__ {__mg_id__: 10}); +CREATE (:__mg_vertex__ {__mg_id__: 11}); +CREATE (:__mg_vertex__ {__mg_id__: 12}); +CREATE (:__mg_vertex__ {__mg_id__: 13}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 14}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 15}); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`edge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ = 3 CREATE (u)-[:`edge` {`prop`: 11}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`edge` {`prop`: true}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 7 CREATE (u)-[:`edge2`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 9 CREATE (u)-[:`edge2` {`prop`: -3.141}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 11 CREATE (u)-[:`edgelink` {`prop`: {`prop`: 1, `prop2`: {`prop4`: 9}}}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 13 CREATE (u)-[:`edgelink` {`prop`: [1, Null, false, "\n\n\n\n\\\"\"\n\t"]}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +ANALYZE GRAPH; +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v17/test_edges/expected_snapshot.cypher b/tests/integration/durability/tests/v17/test_edges/expected_snapshot.cypher new file mode 100644 index 000000000..596753ba5 --- /dev/null +++ b/tests/integration/durability/tests/v17/test_edges/expected_snapshot.cypher @@ -0,0 +1,58 @@ +CREATE INDEX ON :__mg_vertex__(__mg_id__); +CREATE (:__mg_vertex__ {__mg_id__: 0}); +CREATE (:__mg_vertex__ {__mg_id__: 1}); +CREATE (:__mg_vertex__ {__mg_id__: 2}); +CREATE (:__mg_vertex__ {__mg_id__: 3}); +CREATE (:__mg_vertex__ {__mg_id__: 4}); +CREATE (:__mg_vertex__ {__mg_id__: 5}); +CREATE (:__mg_vertex__ {__mg_id__: 6}); +CREATE (:__mg_vertex__ {__mg_id__: 7}); +CREATE (:__mg_vertex__ {__mg_id__: 8}); +CREATE (:__mg_vertex__ {__mg_id__: 9}); +CREATE (:__mg_vertex__ {__mg_id__: 10}); +CREATE (:__mg_vertex__ {__mg_id__: 11}); +CREATE (:__mg_vertex__ {__mg_id__: 12}); +CREATE (:__mg_vertex__ {__mg_id__: 13}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 14}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 15}); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`edge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ = 3 CREATE (u)-[:`edge` {`prop`: 11}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`edge` {`prop`: true}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 7 CREATE (u)-[:`edge2`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 9 CREATE (u)-[:`edge2` {`prop`: -3.141}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 11 CREATE (u)-[:`edgelink` {`prop`: {`prop`: 1, `prop2`: {`prop4`: 9}}}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 13 CREATE (u)-[:`edgelink` {`prop`: [1, Null, false, "\n\n\n\n\\\"\"\n\t"]}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v17/test_edges/expected_wal.cypher b/tests/integration/durability/tests/v17/test_edges/expected_wal.cypher new file mode 100644 index 000000000..596753ba5 --- /dev/null +++ b/tests/integration/durability/tests/v17/test_edges/expected_wal.cypher @@ -0,0 +1,58 @@ +CREATE INDEX ON :__mg_vertex__(__mg_id__); +CREATE (:__mg_vertex__ {__mg_id__: 0}); +CREATE (:__mg_vertex__ {__mg_id__: 1}); +CREATE (:__mg_vertex__ {__mg_id__: 2}); +CREATE (:__mg_vertex__ {__mg_id__: 3}); +CREATE (:__mg_vertex__ {__mg_id__: 4}); +CREATE (:__mg_vertex__ {__mg_id__: 5}); +CREATE (:__mg_vertex__ {__mg_id__: 6}); +CREATE (:__mg_vertex__ {__mg_id__: 7}); +CREATE (:__mg_vertex__ {__mg_id__: 8}); +CREATE (:__mg_vertex__ {__mg_id__: 9}); +CREATE (:__mg_vertex__ {__mg_id__: 10}); +CREATE (:__mg_vertex__ {__mg_id__: 11}); +CREATE (:__mg_vertex__ {__mg_id__: 12}); +CREATE (:__mg_vertex__ {__mg_id__: 13}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 14}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 15}); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`edge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ = 3 CREATE (u)-[:`edge` {`prop`: 11}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`edge` {`prop`: true}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 7 CREATE (u)-[:`edge2`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 9 CREATE (u)-[:`edge2` {`prop`: -3.141}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 11 CREATE (u)-[:`edgelink` {`prop`: {`prop`: 1, `prop2`: {`prop4`: 9}}}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 13 CREATE (u)-[:`edgelink` {`prop`: [1, Null, false, "\n\n\n\n\\\"\"\n\t"]}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v17/test_edges/snapshot.bin b/tests/integration/durability/tests/v17/test_edges/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..070bbe530945bfc947eaa95233be64f6d54aa10b GIT binary patch literal 4297 zcmb7|yKYlK5Qfd21Omy%$>Gi(=SE`*Ut*uMG*ony2+6UJ0wM|#iA2F;P*T(K3>3Tx z4+1ec|F?$UjzSub{@K}?+4*O7PhLDfI#{Zm{yhtOl@t~(pGslpb|t))@J7NX3CoMs znuiiTOZZj^d8l@-A>`pgg(ac#TFb(XN|%R=vsW$s%EKk8tKV84w9AxzmtX$;`sGKQ z`<{m@G9(PyWvlX87j*dH{b9y_b=i*LcDAY={=&Oin5$gm;p%K>EAo-{&la<#T2`0& z{K?;|Mq_?157)F$^?Q=-b%$g)an|#`N=H1g>ZY$+aOjpp6%{6{TUCA%6453+O4RR3 zLd(8=-JunSRvpS5y5rDYhweFa-=PN%J#^@iLysMb4&@Ft99nZ|-JuPKHvfepRhS~E zClUwY0es`Q;R7o5NZJ`hEgWgEw5STP=6#;l$Ue1#M@H2>GP#Ip)CfvG$ex7U2Q+dY z<cdJh65_d1#GZg;A82up3^sdYN;RgjDQHUe%nbKId$<n|S_71vW17eAo)k%1GZXv3 z=<>+uQjbi&Vw%u`rex17bRV?6`yhMLV2bM)VrnXPj~N2DiR=RbA;43nk4Gk7F?Eih zDcNK1J{V`L!9CeiGE|B_a#1F)#6i;7bK%K8aMgHZvDzqjWO5Nx=Lkx1z&P$M_mR)` zLH5K-2dRjWw@&g(9Au9@T`v0|vqS<;xG_62xrnKA1Zl%s8i?G<p8eWvA7oFgbdZo3 zfZRRVYjuhhgr|W=#w~bO?J*-*N=`r^JUX!As5fRG1N5Zt*%@<4O&F*U2m=H%a8jt? z8GVC_Q3<I~Q4G!(6)BSiD$bWIP?0+b0v$ybsKmh38mM3*S)hUnWP$38!trV(3t7c9 zG^h$SpoAc7#7SwRuz3!0@OENn%z3;J1}fehgn^1n-^syU?d0IHbaHUVIXSpwoE+Q@ zPL8N!A!dYJDkn!nDkzQ>v&hN8FmQ6vy8lzSb#XL(;5hOGXafnMk=`6C9v_hVgWYl; zeO*Uaj`qs^w+HWdEMYL#w0RBj-8zQ3UD@WiJUafPQTbUv{-E6=!nL|ity}C2yQ9&l zS?;z*&F)~k-7H#Vzgax(_B)+cuiYNBSX`8YvTXN?X4&g^n%&)Q(cBpn!)DPNb&CFQ bd)OL{!beqcO9G<;mhVZ$8dUs)LRP;408MO2 literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_edges/wal.bin b/tests/integration/durability/tests/v17/test_edges/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..914f49154070a94eb847744868f9f0f10759e93d GIT binary patch literal 6616 zcma)=TW{l36ot#3mRsw|)Z8hVQXw=MC2rED6+&o)03kq?$B+stOfF6cgtltZf<Qd+ zGkE5I@W4OehrmwiwN^U59Qx$$?DegkbB>qYe)iE%ll7JBpZne$>6Q0;dU2<+on8fv z`hKh3+^q%uexufG_xiPVf4^NDcAM>1BT0sPjRQ8f8ui8?=nZPSgWX}R88jNT?y!DP zs|P`IzZ*1r2d#mZf>(CDN7Ku%E6=4@as1;bzDh1;$<J|ISzWG<2XQRP@s9U+`svXU z5w(!De6-Y&Ix}`YKU2RMKS|?9(ks<M549p=s3Ude7=t3R89z;9tek2gSF^|%>PVeA z##yo%uhIB1eq+=^549p=bVlmTF$P6$X8bITv9qd$JOM?<P)F*_G0u|B_<0&X!EcOO z=%H3*jLt}%ImV#K&5UtV9`Tr|7P3}k40WW=9Ag|ok<A!q{m6TY-x#%!wIXA5M(WHl z&XUcz!llLuRW0P1FEWNYQfH2FmTbnGG=@U8&_k`r80tu!kStPco>x9U6WNSkq%m#{ z)k5BEMaED^>dZ0DlFj&K8b6a>sTO*u6&XVvsWZnI6p_vNRT|@-R4wFPUSte)q|O}U zEZK}-r!gKes)ZhEMaED^>dY|)MPxJHqA|iiwUF^rWDNCEC(>fl?IjbjBC&Et%X-^r zRgU&TdZk+Ep;j~+)JvTYjfRd$tenx{BX1jRJ00i?<jG0}@th1s39Ec|N@D6As`2=| zsm8O7&ml=VBvNvIadz(WsH@eC-lh?dR4vBD%#43YI(qt|YhkqUuX@bk9XjB-SivgK z9RlV%^Hv&@-=!QT56KaTO92H<M^8(L7Zcu!{`vEdzqeNZ65gEd(FyZaz@85}8K3^Z zoLX8;$U0miuHeFKGqbSQ+rf>6+q@k3o4NJoa{j{6X0CH)JPETiBTabcOV;7y-<<h2 zmdod1^4I;16`wD%YxZ+et*)&KX5$1L2hT^2GfSqk#fid=%sW6bBQ#0C+LsWpRFWBC zj|AMRB?Rn^WJYL{fJaXW0f$;LBOH=|XGjSFYb}`(-X#IsS3<xElFSJ2k$_Y$A>i~# zW`y@iz@C*5u;G#!;XVn-$PxlJTrwkkKmuL}O9<F-$&BzJ2^;BE2>}}}nGrrF0cW;^ zfDM<-2%nH}%RK`&TrwkkN&;RJOJ=}^OJ;=6Nx1Ev0UIuv5spZ}8)L}~*l@{=5R%{* z&Hy3S+WxwR%m@()czMjuK!WlCG9!FR!b|QMln;;@;g|%xN0!W>e1OadUy<;Ndj{nL zWJWk40k4!LGbkS*Gr~6{yyl)k`2d*_z9j)~mnAbOA0RVAj|BYRri7q;fXoO361LK- z5`yvpG9wI0c+*W#K0s!K??|Y+3Cah^jPN}PciaT!17t>+kg)A0C?6m*!YK)Fxe3Y# z$c%7K!j7AOn^ZC*T%@{w{wq7*KKN&81;_E%YX9u=bXGlV?A_g}4#v~j__RNZuey`V zWEwAjHl1~6^N;)0!@9M0uSW6c;_UJ~o{oP>On+S(B%_POer?`s@3(%_WH#h19rEP= z4k?MLuTu2`=372Sou&WRTUXx$zN@*3NXG1n`ZtKkKR1z`xXHLjH;KqYH<6tVBJ$Bq zWT%6OymS-U=^!FM-9&aeh{#hnk(~}A^3_dbr-O)(-9&ccqTuu(f89iOI*7<)H<6tV iBJ$ZyWT%6Oymk}W=^!G%-9&aeh{$s{k)5T)<NpAH_`ARW literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_indices/create_dataset.cypher b/tests/integration/durability/tests/v17/test_indices/create_dataset.cypher new file mode 100644 index 000000000..739062f19 --- /dev/null +++ b/tests/integration/durability/tests/v17/test_indices/create_dataset.cypher @@ -0,0 +1,6 @@ +CREATE INDEX ON :`label2`; +CREATE INDEX ON :`label2`(`prop2`); +CREATE INDEX ON :`label`(`prop2`); +CREATE INDEX ON :`label`(`prop`); +CREATE EDGE INDEX ON :`edgetype`; +ANALYZE GRAPH; diff --git a/tests/integration/durability/tests/v17/test_indices/expected_snapshot.cypher b/tests/integration/durability/tests/v17/test_indices/expected_snapshot.cypher new file mode 100644 index 000000000..1e930697a --- /dev/null +++ b/tests/integration/durability/tests/v17/test_indices/expected_snapshot.cypher @@ -0,0 +1,5 @@ +CREATE INDEX ON :`label2`; +CREATE INDEX ON :`label`(`prop`); +CREATE INDEX ON :`label`(`prop2`); +CREATE INDEX ON :`label2`(`prop2`); +CREATE EDGE INDEX ON :`edgetype`; diff --git a/tests/integration/durability/tests/v17/test_indices/expected_wal.cypher b/tests/integration/durability/tests/v17/test_indices/expected_wal.cypher new file mode 100644 index 000000000..bfae88b0b --- /dev/null +++ b/tests/integration/durability/tests/v17/test_indices/expected_wal.cypher @@ -0,0 +1,5 @@ +CREATE INDEX ON :`label2`; +CREATE INDEX ON :`label2`(`prop2`); +CREATE INDEX ON :`label`(`prop2`); +CREATE INDEX ON :`label`(`prop`); +CREATE EDGE INDEX ON :`edgetype`; diff --git a/tests/integration/durability/tests/v17/test_indices/snapshot.bin b/tests/integration/durability/tests/v17/test_indices/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..99ad6e0ea29bd82dbabbc8cd429ce40afa116005 GIT binary patch literal 731 zcmbu7J#NB45QUupQ3@J{JwZYQ%}O47h1oVuM3E{d@Or&&EU+L=&XYTE2ilw_9r<Bq zjAR#);>Mrny?L`c^7GSvGl|a6BUD5^@P2jH;J|QXSYMO$!f<5xAeaG}BcKscPlcr^ z0}P0Ix+wV~M=H`KAc5Uuup~nXK2K5sN&aVh_dI`?zGyc!B>O|V4-UA45h1Wk-BP9W zRr&hXl(J66mS(qY<aUY^>fDvJOqKGFIg!cghSGoC$y|hcH6sOcvvk&=Mi-+tu5kMM wE7!62Hck-HXK8CT!C|s;IxKC_Cip_z%w&4yQ*=e1nk-lt^Bw7Z@jG=YU(Ba5i2wiq literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_indices/wal.bin b/tests/integration/durability/tests/v17/test_indices/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..661cba6c1b1f55ffefae349b245c01a11b6533b1 GIT binary patch literal 847 zcmc(cy-UPE5XBEs5wS6(vrwX3<w9<gc)ON1R`w?;!hQ@P5R#B=PE@S?$KAV|O^$F_ z>Q=M7_j_-WeR+N>FK4^+<Xz+BedBQJ-{C}A905xNp%JGfma!&6E;$LsLIY)sP%#+H ziYth;=7b5RNeoLxWSB@2!h;lyCsgv-!{FK6JHqkRKgFpO5=uHMtGRcKQaUmDCnz*+ z-B`r=EaKAA!L!XBZb&$rCC-Ktm%1Zd=!O_w7C1)$e20S0!MryxYSwmvHZfiP3Wd)= z)qQ1x$MF6p5bHeCkhcK#{x#BSO1mYuAL6HnvUJgzqEaSXi?W9<+pFn}FdJ2kt$ADb dP1Y43wwY9-`fV3ie&C<}?<)I`^|za>egKm>sSp4F literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_vertices/create_dataset.cypher b/tests/integration/durability/tests/v17/test_vertices/create_dataset.cypher new file mode 100644 index 000000000..061df375b --- /dev/null +++ b/tests/integration/durability/tests/v17/test_vertices/create_dataset.cypher @@ -0,0 +1,18 @@ +// --storage-items-per-batch is set to 5 +CREATE INDEX ON :__mg_vertex__(__mg_id__); +CREATE (:__mg_vertex__ {__mg_id__: 0}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 1}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 2, `prop`: false}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 3, `prop`: true}); +CREATE (:__mg_vertex__:`label2` {__mg_id__: 4, `prop`: 1}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 5, `prop2`: 3.141}); +CREATE (:__mg_vertex__:`label6` {__mg_id__: 6, `prop3`: true, `prop2`: -314000000}); +CREATE (:__mg_vertex__:`label3`:`label1`:`label2` {__mg_id__: 7}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 8, `prop3`: "str", `prop2`: 2, `prop`: 1}); +CREATE (:__mg_vertex__:`label2`:`label1` {__mg_id__: 9, `prop`: {`prop_nes`: "kaj je"}}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 10, `prop_array`: [1, false, Null, "str", {`prop2`: 2}]}); +CREATE (:__mg_vertex__:`label3`:`label` {__mg_id__: 11, `prop`: {`prop`: [1, false], `prop2`: {}, `prop3`: "test2", `prop4`: "test"}}); +CREATE (:__mg_vertex__ {__mg_id__: 12, `prop`: " \n\"\'\t\\%"}); +ANALYZE GRAPH; +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v17/test_vertices/expected_snapshot.cypher b/tests/integration/durability/tests/v17/test_vertices/expected_snapshot.cypher new file mode 100644 index 000000000..ecdc1229e --- /dev/null +++ b/tests/integration/durability/tests/v17/test_vertices/expected_snapshot.cypher @@ -0,0 +1,16 @@ +CREATE INDEX ON :__mg_vertex__(__mg_id__); +CREATE (:__mg_vertex__ {__mg_id__: 0}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 1}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 2, `prop`: false}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 3, `prop`: true}); +CREATE (:__mg_vertex__:`label2` {__mg_id__: 4, `prop`: 1}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 5, `prop2`: 3.141}); +CREATE (:__mg_vertex__:`label6` {__mg_id__: 6, `prop3`: true, `prop2`: -314000000}); +CREATE (:__mg_vertex__:`label2`:`label3`:`label1` {__mg_id__: 7}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 8, `prop3`: "str", `prop2`: 2, `prop`: 1}); +CREATE (:__mg_vertex__:`label1`:`label2` {__mg_id__: 9, `prop`: {`prop_nes`: "kaj je"}}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 10, `prop_array`: [1, false, Null, "str", {`prop2`: 2}]}); +CREATE (:__mg_vertex__:`label`:`label3` {__mg_id__: 11, `prop`: {`prop`: [1, false], `prop2`: {}, `prop3`: "test2", `prop4`: "test"}}); +CREATE (:__mg_vertex__ {__mg_id__: 12, `prop`: " \n\"\'\t\\%"}); +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v17/test_vertices/expected_wal.cypher b/tests/integration/durability/tests/v17/test_vertices/expected_wal.cypher new file mode 100644 index 000000000..d8f758737 --- /dev/null +++ b/tests/integration/durability/tests/v17/test_vertices/expected_wal.cypher @@ -0,0 +1,16 @@ +CREATE INDEX ON :__mg_vertex__(__mg_id__); +CREATE (:__mg_vertex__ {__mg_id__: 0}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 1}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 2, `prop`: false}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 3, `prop`: true}); +CREATE (:__mg_vertex__:`label2` {__mg_id__: 4, `prop`: 1}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 5, `prop2`: 3.141}); +CREATE (:__mg_vertex__:`label6` {__mg_id__: 6, `prop2`: -314000000, `prop3`: true}); +CREATE (:__mg_vertex__:`label2`:`label3`:`label1` {__mg_id__: 7}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 8, `prop`: 1, `prop2`: 2, `prop3`: "str"}); +CREATE (:__mg_vertex__:`label1`:`label2` {__mg_id__: 9, `prop`: {`prop_nes`: "kaj je"}}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 10, `prop_array`: [1, false, Null, "str", {`prop2`: 2}]}); +CREATE (:__mg_vertex__:`label`:`label3` {__mg_id__: 11, `prop`: {`prop`: [1, false], `prop2`: {}, `prop3`: "test2", `prop4`: "test"}}); +CREATE (:__mg_vertex__ {__mg_id__: 12, `prop`: " \n\"\'\t\\%"}); +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v17/test_vertices/snapshot.bin b/tests/integration/durability/tests/v17/test_vertices/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..8a67d9a7d31453b35945fe69115087c3040990d0 GIT binary patch literal 1739 zcma)7y>8S%7`%l1a3M(Chm&nrNzg%I>Fo9Wb%-LQNtY4{d3`IS5QXUOnsmGeHKNT6 z(D5eJJVQ7qJF~IYM7Wy!c6N5Y+0VYy_lwJ3=l*#opF9d+WAnZwZvYIyF+hLIb25M* z05=|J@yeb50b15zgkT+&e+y|LDc;7)6EC28Enc)>Iar8A!BM^2xE(DUu*x&z3MIlh zQDa(s!ks{h&48m`p8fv*?U(MpZS&;s;eiq>l%$e;{dw}|cJ;_M!)omm%ON#(`V=8_ z0iyosQw!OQjINLr&rl(a;Fxq+QVBLLmUEs&poPA;l@&HYH_%f1C?&9bHUDz;@zO0+ zf<!-;=lb07C;}}z$O0-Y6Z_0_)L!Tg^wrF&7mdyy7y`XEl&hc|<*o&11QJ6)TOs7o zJ90??>Z8;i_J)gD`AnL2_Ti#YH;O-(Zn4bys}?R4y~BY~D(<O%f_*9j&<?;P~I zA6{`g!4c6T;5FA;6b`~PA=(6YeK#NFd|qCY#7Vg*t-HuL{mr`J+t-N22<EIwAMtC^ zpy&J?HR(CuC#GCG<wQ{A&SRh5A0|gW?7S#*I~`PMR#aJ9IO|etXOq;;?5HqiHk{@a t$um2)<*+oVvxQBIVqB$@!K6x!&5SGUU_7d(au{9b^QeMXA^b&w^A~t%WBC98 literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v17/test_vertices/wal.bin b/tests/integration/durability/tests/v17/test_vertices/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..304db455f08ea9363a01f9248e3ece3020f731db GIT binary patch literal 4355 zcma)8%Wl&^6b)}$9!iZ{BZMFdP&Qb~!)cSQ5F6MaRjwBmRI;385~YN;O5E}i5)z-m zn$KXzpRnc|$|RX{@3<a!$ZB%u+;h)8zMk>xmmkLE(&G2Rd8k&-ceQxrK31ziXLF-n z>u&l<FRA-MqZ|0$L2KYQx}89@I_+jX2?*S7)q<ek5`JIwyMEA0+I~kg>wY4Vda%(N z)LXrVqlioE&PBC+>)ukUIDS8jPsAh@ALH0v(xan(9COO<x^r3WZflH)g%mSWb9ThY zTse>S7%vN7rNS4~iesUJSW#hQYsf2#W}I}zn7Jb6GDt%m#!G3}NgBN6SV(>sNkg`V zJR=Q=h`9{X^0hTQ9?{X{@W@+{QvgmWf0^W8B4-f`9mI;{8<1z@0}(NoK|boR9u1$B zf65ps|0c-?ien+oYmt0pYe>w>d@veJ7BQJYf4;be$L8FA8Zf4oZQQ0dU?0aq2eG0y zkgXw+qUIZjh`9`1Ac<>hcx=wPXn1#`KR<u`t}H!gtZW0v+cv$h91AH{)CRIOWaepX zFj>T8hCYyG#Ao|(zWsXj_f-C@*rvClfn>8dskWVK^yVNIQmm+10}`_5&#cJ>G_nk& zZimPLMxlr$87#}UAUkwvDsf<NETluEs6%9HNO-TN5{QVo4ASzoHGI*#WuQj!#Q@W3 z$}MTMI=1P#IG(2IgyGVlH8KdgITkvI6&ZwV4Vihq^5NMaas&X0xE>16ulCs`?$MqR zpD}R_x10;(XNJWGV(Ph&_b%D5><i|SR@@~kaN=_;q+6!Q3S?_YJOVW<Kt#-CXft11 z!((&aPcoS#pS)F6@#tKZ7o=CtJaUgM0X#QDkSCqEM^4;OD~^Q@Vnt3MTSLNioC^jB zbSbldh`5}=EJ$H_8a_J{_~&9VGr(KLS4YM@7h)DBVLnTlxO}MqOT{#8(7#zy;8C5~ zChL>#-Y0i)d2=kJ&w(O$ksUG8U`(D_!)F|Dac)+Yt528qp0E}v^?{PwGposeYrAh$ z#+PdE@c1CDZq=L5D%JjInvM>7X?&85kHs|B_oiu*&bE8it(t@;C&PF+IXpg!r=!n8 z<}X5hF`NkbxAv7)Nr4qrZts5ua*E8Zs_Y%yFeNh$BhUTtFEeh3UPe13omTnhm|?`I z+8GIHVZ^0iXC$PB5u;^iB&3BA@0gvDkQPQ9Ms`L*S{U&P*%=9GVZ_B{XC$PB5!-e~ jLSn&}aAqEH8QB>LX<@{1Zf7K<g%RhVosp2|GVcBZa+C9o literal 0 HcmV?d00001 diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp index 3f64c4f37..f0f60ba91 100644 --- a/tests/manual/interactive_planning.cpp +++ b/tests/manual/interactive_planning.cpp @@ -214,6 +214,8 @@ class InteractiveDbAccessor { return label_property_index_.at(key); } + bool EdgeTypeIndexExists(memgraph::storage::EdgeTypeId edge_type) { return true; } + std::optional<memgraph::storage::LabelIndexStats> GetIndexStats(const memgraph::storage::LabelId label) const { return dba_->GetIndexStats(label); } diff --git a/tests/unit/dbms_database.cpp b/tests/unit/dbms_database.cpp index 535c0c055..0fded2324 100644 --- a/tests/unit/dbms_database.cpp +++ b/tests/unit/dbms_database.cpp @@ -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 diff --git a/tests/unit/query_plan.cpp b/tests/unit/query_plan.cpp index bc4b2660c..5b574c1ff 100644 --- a/tests/unit/query_plan.cpp +++ b/tests/unit/query_plan.cpp @@ -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 @@ -808,13 +808,68 @@ TYPED_TEST(TestPlanner, MatchWhereBeforeExpand) { CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectProduce()); } +TYPED_TEST(TestPlanner, MatchEdgeTypeIndex) { + FakeDbAccessor dba; + auto indexed_edge_type = dba.EdgeType("indexed_edgetype"); + dba.SetIndexCount(indexed_edge_type, 1); + { + // Test MATCH ()-[r:indexed_edgetype]->() RETURN r; + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("anon1"), EDGE("r", memgraph::query::EdgeAtom::Direction::OUT, {"indexed_edgetype"}), + NODE("anon2"))), + RETURN("r"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByEdgeType(), ExpectProduce()); + } + { + // Test MATCH (a)-[r:indexed_edgetype]->() RETURN r; + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("a"), EDGE("r", memgraph::query::EdgeAtom::Direction::OUT, {"indexed_edgetype"}), + NODE("anon2"))), + RETURN("r"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); + } + { + // Test MATCH ()-[r:indexed_edgetype]->(b) RETURN r; + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("anon1"), EDGE("r", memgraph::query::EdgeAtom::Direction::OUT, {"indexed_edgetype"}), + NODE("b"))), + RETURN("r"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); + } + { + // Test MATCH (a)-[r:indexed_edgetype]->(b) RETURN r; + auto *query = QUERY(SINGLE_QUERY( + MATCH( + PATTERN(NODE("a"), EDGE("r", memgraph::query::EdgeAtom::Direction::OUT, {"indexed_edgetype"}), NODE("b"))), + RETURN("r"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); + } + { + // Test MATCH ()-[r:not_indexed_edgetype]->() RETURN r; + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("anon1"), EDGE("r", memgraph::query::EdgeAtom::Direction::OUT, {"not_indexed_edgetype"}), + NODE("anon2"))), + RETURN("r"))); + auto symbol_table = memgraph::query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); + } +} + TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { FakeDbAccessor dba; auto label = dba.Label("label"); auto prop = PROPERTY_PAIR(dba, "prop"); dba.SetIndexCount(label, 1); dba.SetIndexCount(label, prop.second, 1); - { // Test MATCH (n :label) -[r]- (m) WHERE n.prop IS NOT NULL RETURN n auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), diff --git a/tests/unit/query_plan_checker.hpp b/tests/unit/query_plan_checker.hpp index 92089eb82..6eef3841a 100644 --- a/tests/unit/query_plan_checker.hpp +++ b/tests/unit/query_plan_checker.hpp @@ -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 @@ -65,6 +65,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor { PRE_VISIT(ScanAllByLabelPropertyValue); PRE_VISIT(ScanAllByLabelPropertyRange); PRE_VISIT(ScanAllByLabelProperty); + PRE_VISIT(ScanAllByEdgeType); PRE_VISIT(ScanAllById); PRE_VISIT(Expand); PRE_VISIT(ExpandVariable); @@ -170,6 +171,7 @@ using ExpectCreateExpand = OpChecker<CreateExpand>; using ExpectDelete = OpChecker<Delete>; using ExpectScanAll = OpChecker<ScanAll>; using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>; +using ExpectScanAllByEdgeType = OpChecker<ScanAllByEdgeType>; using ExpectScanAllById = OpChecker<ScanAllById>; using ExpectExpand = OpChecker<Expand>; using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>; @@ -560,6 +562,12 @@ class FakeDbAccessor { return 0; } + int64_t EdgesCount(memgraph::storage::EdgeTypeId edge_type) const { + auto found = edge_type_index_.find(edge_type); + if (found != edge_type_index_.end()) return found->second; + return 0; + } + bool LabelIndexExists(memgraph::storage::LabelId label) const { return label_index_.find(label) != label_index_.end(); } @@ -573,6 +581,10 @@ class FakeDbAccessor { return false; } + bool EdgeTypeIndexExists(memgraph::storage::EdgeTypeId edge_type) const { + return edge_type_index_.find(edge_type) != edge_type_index_.end(); + } + std::optional<memgraph::storage::LabelPropertyIndexStats> GetIndexStats( const memgraph::storage::LabelId label, const memgraph::storage::PropertyId property) const { return memgraph::storage::LabelPropertyIndexStats{.statistic = 0, .avg_group_size = 1}; // unique id @@ -594,6 +606,8 @@ class FakeDbAccessor { label_property_index_.emplace_back(label, property, count); } + void SetIndexCount(memgraph::storage::EdgeTypeId edge_type, int64_t count) { edge_type_index_[edge_type] = count; } + memgraph::storage::LabelId NameToLabel(const std::string &name) { auto found = labels_.find(name); if (found != labels_.end()) return found->second; @@ -608,6 +622,8 @@ class FakeDbAccessor { return edge_types_.emplace(name, memgraph::storage::EdgeTypeId::FromUint(edge_types_.size())).first->second; } + memgraph::storage::EdgeTypeId EdgeType(const std::string &name) { return NameToEdgeType(name); } + memgraph::storage::PropertyId NameToProperty(const std::string &name) { auto found = properties_.find(name); if (found != properties_.end()) return found->second; @@ -632,6 +648,7 @@ class FakeDbAccessor { std::unordered_map<memgraph::storage::LabelId, int64_t> label_index_; std::vector<std::tuple<memgraph::storage::LabelId, memgraph::storage::PropertyId, int64_t>> label_property_index_; + std::unordered_map<memgraph::storage::EdgeTypeId, int64_t> edge_type_index_; }; } // namespace memgraph::query::plan diff --git a/tests/unit/storage_v2_decoder_encoder.cpp b/tests/unit/storage_v2_decoder_encoder.cpp index 9b627cb77..15db49b1c 100644 --- a/tests/unit/storage_v2_decoder_encoder.cpp +++ b/tests/unit/storage_v2_decoder_encoder.cpp @@ -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 @@ -337,6 +337,7 @@ TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { case memgraph::storage::durability::Marker::SECTION_CONSTRAINTS: case memgraph::storage::durability::Marker::SECTION_DELTA: case memgraph::storage::durability::Marker::SECTION_EPOCH_HISTORY: + case memgraph::storage::durability::Marker::SECTION_EDGE_INDICES: case memgraph::storage::durability::Marker::SECTION_OFFSETS: case memgraph::storage::durability::Marker::DELTA_VERTEX_CREATE: case memgraph::storage::durability::Marker::DELTA_VERTEX_DELETE: @@ -355,6 +356,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_EDGE_TYPE_INDEX_CREATE: + case memgraph::storage::durability::Marker::DELTA_EDGE_TYPE_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: diff --git a/tests/unit/storage_v2_durability_inmemory.cpp b/tests/unit/storage_v2_durability_inmemory.cpp index 54671077f..7794f2ab9 100644 --- a/tests/unit/storage_v2_durability_inmemory.cpp +++ b/tests/unit/storage_v2_durability_inmemory.cpp @@ -69,6 +69,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { ONLY_EXTENDED, ONLY_EXTENDED_WITH_BASE_INDICES_AND_CONSTRAINTS, BASE_WITH_EXTENDED, + BASE_WITH_EDGE_TYPE_INDEXED, }; public: @@ -270,6 +271,15 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { if (single_transaction) ASSERT_FALSE(acc->Commit().HasError()); } + void CreateEdgeIndex(memgraph::storage::Storage *store, memgraph::storage::EdgeTypeId edge_type) { + { + // Create edge-type index. + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); + ASSERT_FALSE(unique_acc->CreateIndex(edge_type).HasError()); + ASSERT_FALSE(unique_acc->Commit().HasError()); + } + } + void VerifyDataset(memgraph::storage::Storage *store, DatasetType type, bool properties_on_edges, bool verify_info = true) { auto base_label_indexed = store->NameToLabel("base_indexed"); @@ -310,13 +320,19 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { UnorderedElementsAre(std::make_pair(base_label_indexed, property_id), std::make_pair(extended_label_indexed, property_count))); break; + case DatasetType::BASE_WITH_EDGE_TYPE_INDEXED: + ASSERT_THAT(info.label, UnorderedElementsAre(base_label_unindexed)); + ASSERT_THAT(info.label_property, UnorderedElementsAre(std::make_pair(base_label_indexed, property_id))); + ASSERT_THAT(info.edge_type, UnorderedElementsAre(et1)); + break; } } // Verify index statistics { switch (type) { - case DatasetType::ONLY_BASE: { + case DatasetType::ONLY_BASE: + case DatasetType::BASE_WITH_EDGE_TYPE_INDEXED: { const auto l_stats = acc->GetIndexStats(base_label_unindexed); ASSERT_TRUE(l_stats); ASSERT_EQ(l_stats->count, 1); @@ -379,6 +395,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { auto info = acc->ListAllConstraints(); switch (type) { case DatasetType::ONLY_BASE: + case DatasetType::BASE_WITH_EDGE_TYPE_INDEXED: ASSERT_THAT(info.existence, UnorderedElementsAre(std::make_pair(base_label_unindexed, property_id))); ASSERT_THAT(info.unique, UnorderedElementsAre( std::make_pair(base_label_unindexed, std::set{property_id, property_extra}))); @@ -402,6 +419,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { bool have_base_dataset = false; bool have_extended_dataset = false; + bool have_edge_type_indexed_dataset = false; switch (type) { case DatasetType::ONLY_BASE: case DatasetType::ONLY_BASE_WITH_EXTENDED_INDICES_AND_CONSTRAINTS: @@ -415,6 +433,9 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { have_base_dataset = true; have_extended_dataset = true; break; + case DatasetType::BASE_WITH_EDGE_TYPE_INDEXED: + have_base_dataset = true; + have_edge_type_indexed_dataset = true; } // Verify base dataset. @@ -675,6 +696,19 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { } } + if (have_edge_type_indexed_dataset) { + MG_ASSERT(properties_on_edges, "Edge-type indexing needs --properties-on-edges!"); + // Verify edge-type indices. + { + std::vector<memgraph::storage::EdgeAccessor> edges; + edges.reserve(kNumBaseEdges / 2); + for (auto edge : acc->Edges(et1, memgraph::storage::View::OLD)) { + edges.push_back(edge); + } + ASSERT_EQ(edges.size(), kNumBaseEdges / 2); + } + } + if (verify_info) { auto info = store->GetBaseInfo(); if (have_base_dataset) { @@ -2972,3 +3006,42 @@ TEST_P(DurabilityTest, ConstraintsRecoveryFunctionSetting) { &variant_existence_constraint_creation_func); MG_ASSERT(pval_existence, "Chose wrong type of function for recovery of existence constraint data"); } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(DurabilityTest, EdgeTypeIndexRecovered) { + if (GetParam() == false) { + return; + } + // Create snapshot. + { + memgraph::storage::Config config{.salient.items = {.properties_on_edges = GetParam()}, + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; + memgraph::dbms::Database db{config, repl_state}; + CreateBaseDataset(db.storage(), GetParam()); + VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); + CreateEdgeIndex(db.storage(), db.storage()->NameToEdgeType("base_et1")); + VerifyDataset(db.storage(), DatasetType::BASE_WITH_EDGE_TYPE_INDEXED, GetParam()); + } + + ASSERT_EQ(GetSnapshotsList().size(), 1); + ASSERT_EQ(GetBackupSnapshotsList().size(), 0); + ASSERT_EQ(GetWalsList().size(), 0); + ASSERT_EQ(GetBackupWalsList().size(), 0); + + // Recover snapshot. + memgraph::storage::Config config{.salient.items = {.properties_on_edges = GetParam()}, + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; + memgraph::dbms::Database db{config, repl_state}; + VerifyDataset(db.storage(), DatasetType::BASE_WITH_EDGE_TYPE_INDEXED, GetParam()); + + // Try to use the storage. + { + auto acc = db.Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); + ASSERT_TRUE(edge.HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); + } +} diff --git a/tests/unit/storage_v2_indices.cpp b/tests/unit/storage_v2_indices.cpp index 8ee053087..23c82313d 100644 --- a/tests/unit/storage_v2_indices.cpp +++ b/tests/unit/storage_v2_indices.cpp @@ -44,6 +44,8 @@ class IndexTest : public testing::Test { this->prop_val = acc->NameToProperty("val"); this->label1 = acc->NameToLabel("label1"); this->label2 = acc->NameToLabel("label2"); + this->edge_type_id1 = acc->NameToEdgeType("edge_type_1"); + this->edge_type_id2 = acc->NameToEdgeType("edge_type_2"); vertex_id = 0; } @@ -61,6 +63,8 @@ class IndexTest : public testing::Test { PropertyId prop_val; LabelId label1; LabelId label2; + EdgeTypeId edge_type_id1; + EdgeTypeId edge_type_id2; VertexAccessor CreateVertex(Storage::Accessor *accessor) { VertexAccessor vertex = accessor->CreateVertex(); @@ -68,11 +72,23 @@ class IndexTest : public testing::Test { return vertex; } + VertexAccessor CreateVertexWithoutProperties(Storage::Accessor *accessor) { + VertexAccessor vertex = accessor->CreateVertex(); + return vertex; + } + + EdgeAccessor CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, Storage::Accessor *accessor) { + auto edge = accessor->CreateEdge(from, to, edge_type); + MG_ASSERT(!edge.HasError()); + MG_ASSERT(!edge->SetProperty(this->prop_id, PropertyValue(vertex_id++)).HasError()); + return edge.GetValue(); + } + template <class TIterable> std::vector<int64_t> GetIds(TIterable iterable, View view = View::OLD) { std::vector<int64_t> ret; - for (auto vertex : iterable) { - ret.push_back(vertex.GetProperty(this->prop_id, view)->ValueInt()); + for (auto item : iterable) { + ret.push_back(item.GetProperty(this->prop_id, view)->ValueInt()); } return ret; } @@ -1292,3 +1308,368 @@ TYPED_TEST(IndexTest, LabelPropertyIndexClearOldDataFromDisk) { ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); } } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TYPED_TEST(IndexTest, EdgeTypeIndexCreate) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_FALSE(acc->EdgeTypeIndexExists(this->edge_type_id1)); + EXPECT_EQ(acc->ListAllIndices().edge_type.size(), 0); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + for (int i = 0; i < 10; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, i % 2 ? this->edge_type_id1 : this->edge_type_id2, acc.get()); + } + ASSERT_NO_ERROR(acc->Commit()); + } + + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + for (int i = 10; i < 20; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, i % 2 ? this->edge_type_id1 : this->edge_type_id2, acc.get()); + } + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + + acc->AdvanceCommand(); + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + + acc->Abort(); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + for (int i = 10; i < 20; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, i % 2 ? this->edge_type_id1 : this->edge_type_id2, acc.get()); + } + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + acc->AdvanceCommand(); + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + ASSERT_NO_ERROR(acc->Commit()); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + acc->AdvanceCommand(); + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + ASSERT_NO_ERROR(acc->Commit()); + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TYPED_TEST(IndexTest, EdgeTypeIndexDrop) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_FALSE(acc->EdgeTypeIndexExists(this->edge_type_id1)); + EXPECT_EQ(acc->ListAllIndices().edge_type.size(), 0); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + for (int i = 0; i < 10; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, i % 2 ? this->edge_type_id1 : this->edge_type_id2, acc.get()); + } + ASSERT_NO_ERROR(acc->Commit()); + } + + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); + } + + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->DropIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_FALSE(acc->EdgeTypeIndexExists(this->edge_type_id1)); + EXPECT_EQ(acc->ListAllIndices().label.size(), 0); + } + + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_TRUE(unique_acc->DropIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_FALSE(acc->EdgeTypeIndexExists(this->edge_type_id1)); + EXPECT_EQ(acc->ListAllIndices().label.size(), 0); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + for (int i = 10; i < 20; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, i % 2 ? this->edge_type_id1 : this->edge_type_id2, acc.get()); + } + ASSERT_NO_ERROR(acc->Commit()); + } + + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_TRUE(acc->EdgeTypeIndexExists(this->edge_type_id1)); + EXPECT_THAT(acc->ListAllIndices().edge_type, UnorderedElementsAre(this->edge_type_id1)); + } + + { + auto acc = this->storage->Access(ReplicationRole::MAIN); + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + + acc->AdvanceCommand(); + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TYPED_TEST(IndexTest, EdgeTypeIndexBasic) { + // The following steps are performed and index correctness is validated after + // each step: + // 1. Create 10 edges numbered from 0 to 9. + // 2. Add EdgeType1 to odd numbered, and EdgeType2 to even numbered edges. + // 3. Delete even numbered edges. + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id2).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + + auto acc = this->storage->Access(ReplicationRole::MAIN); + EXPECT_THAT(acc->ListAllIndices().edge_type, UnorderedElementsAre(this->edge_type_id1, this->edge_type_id2)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::NEW), View::NEW), IsEmpty()); + + for (int i = 0; i < 10; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, i % 2 ? this->edge_type_id1 : this->edge_type_id2, acc.get()); + } + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::NEW), View::NEW), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + acc->AdvanceCommand(); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::OLD), View::OLD), + UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::NEW), View::NEW), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + for (auto vertex : acc->Vertices(View::OLD)) { + auto edges = vertex.OutEdges(View::OLD)->edges; + for (auto &edge : edges) { + int64_t id = edge.GetProperty(this->prop_id, View::OLD)->ValueInt(); + if (id % 2 == 0) { + ASSERT_NO_ERROR(acc->DetachDelete({}, {&edge}, false)); + } + } + } + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::OLD), View::OLD), + UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::NEW), View::NEW), IsEmpty()); + + acc->AdvanceCommand(); + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id2, View::NEW), View::NEW), IsEmpty()); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TYPED_TEST(IndexTest, EdgeTypeIndexTransactionalIsolation) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + // Check that transactions only see entries they are supposed to see. + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id2).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + + auto acc_before = this->storage->Access(ReplicationRole::MAIN); + auto acc = this->storage->Access(ReplicationRole::MAIN); + auto acc_after = this->storage->Access(ReplicationRole::MAIN); + + for (int i = 0; i < 5; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, this->edge_type_id1, acc.get()); + } + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); + + EXPECT_THAT(this->GetIds(acc_before->Edges(this->edge_type_id1, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc_after->Edges(this->edge_type_id1, View::NEW), View::NEW), IsEmpty()); + + ASSERT_NO_ERROR(acc->Commit()); + + auto acc_after_commit = this->storage->Access(ReplicationRole::MAIN); + + EXPECT_THAT(this->GetIds(acc_before->Edges(this->edge_type_id1, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc_after->Edges(this->edge_type_id1, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc_after_commit->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TYPED_TEST(IndexTest, EdgeTypeIndexCountEstimate) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id2).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + + auto acc = this->storage->Access(ReplicationRole::MAIN); + for (int i = 0; i < 20; ++i) { + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + this->CreateEdge(&vertex_from, &vertex_to, i % 3 ? this->edge_type_id1 : this->edge_type_id2, acc.get()); + } + + EXPECT_EQ(acc->ApproximateEdgeCount(this->edge_type_id1), 13); + EXPECT_EQ(acc->ApproximateEdgeCount(this->edge_type_id2), 7); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TYPED_TEST(IndexTest, EdgeTypeIndexRepeatingEdgeTypesBetweenSameVertices) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + { + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); + EXPECT_FALSE(unique_acc->CreateIndex(this->edge_type_id1).HasError()); + ASSERT_NO_ERROR(unique_acc->Commit()); + } + + auto acc = this->storage->Access(ReplicationRole::MAIN); + auto vertex_from = this->CreateVertexWithoutProperties(acc.get()); + auto vertex_to = this->CreateVertexWithoutProperties(acc.get()); + + for (int i = 0; i < 5; ++i) { + this->CreateEdge(&vertex_from, &vertex_to, this->edge_type_id1, acc.get()); + } + + EXPECT_EQ(acc->ApproximateEdgeCount(this->edge_type_id1), 5); + + EXPECT_THAT(this->GetIds(acc->Edges(this->edge_type_id1, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); + } +} diff --git a/tests/unit/storage_v2_wal_file.cpp b/tests/unit/storage_v2_wal_file.cpp index 07a35d754..dcb7d3326 100644 --- a/tests/unit/storage_v2_wal_file.cpp +++ b/tests/unit/storage_v2_wal_file.cpp @@ -37,6 +37,10 @@ memgraph::storage::durability::WalDeltaData::Type StorageMetadataOperationToWalD return memgraph::storage::durability::WalDeltaData::Type::LABEL_INDEX_CREATE; case memgraph::storage::durability::StorageMetadataOperation::LABEL_INDEX_DROP: return memgraph::storage::durability::WalDeltaData::Type::LABEL_INDEX_DROP; + case memgraph::storage::durability::StorageMetadataOperation::EDGE_TYPE_INDEX_CREATE: + return memgraph::storage::durability::WalDeltaData::Type::EDGE_INDEX_CREATE; + case memgraph::storage::durability::StorageMetadataOperation::EDGE_TYPE_INDEX_DROP: + return memgraph::storage::durability::WalDeltaData::Type::EDGE_INDEX_DROP; case memgraph::storage::durability::StorageMetadataOperation::LABEL_INDEX_STATS_SET: return memgraph::storage::durability::WalDeltaData::Type::LABEL_INDEX_STATS_SET; case memgraph::storage::durability::StorageMetadataOperation::LABEL_INDEX_STATS_CLEAR: @@ -280,6 +284,41 @@ class DeltaGenerator final { case memgraph::storage::durability::StorageMetadataOperation::UNIQUE_CONSTRAINT_DROP: data.operation_label_properties.label = label; data.operation_label_properties.properties = properties; + break; + case memgraph::storage::durability::StorageMetadataOperation::EDGE_TYPE_INDEX_CREATE: + case memgraph::storage::durability::StorageMetadataOperation::EDGE_TYPE_INDEX_DROP: + MG_ASSERT(false, "Invalid function call!"); + } + data_.emplace_back(timestamp_, data); + } + } + + void AppendEdgeTypeOperation(memgraph::storage::durability::StorageMetadataOperation operation, + const std::string &edge_type) { + auto edge_type_id = memgraph::storage::EdgeTypeId::FromUint(mapper_.NameToId(edge_type)); + wal_file_.AppendOperation(operation, edge_type_id, timestamp_); + if (valid_) { + UpdateStats(timestamp_, 1); + memgraph::storage::durability::WalDeltaData data; + data.type = StorageMetadataOperationToWalDeltaDataType(operation); + switch (operation) { + case memgraph::storage::durability::StorageMetadataOperation::EDGE_TYPE_INDEX_CREATE: + case memgraph::storage::durability::StorageMetadataOperation::EDGE_TYPE_INDEX_DROP: + data.operation_edge_type.edge_type = edge_type; + break; + case memgraph::storage::durability::StorageMetadataOperation::LABEL_INDEX_CREATE: + case memgraph::storage::durability::StorageMetadataOperation::LABEL_INDEX_DROP: + case memgraph::storage::durability::StorageMetadataOperation::LABEL_INDEX_STATS_CLEAR: + case memgraph::storage::durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR: + case memgraph::storage::durability::StorageMetadataOperation::LABEL_INDEX_STATS_SET: + case memgraph::storage::durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE: + case memgraph::storage::durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP: + case memgraph::storage::durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE: + case memgraph::storage::durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP:; + case memgraph::storage::durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_SET: + case memgraph::storage::durability::StorageMetadataOperation::UNIQUE_CONSTRAINT_CREATE: + case memgraph::storage::durability::StorageMetadataOperation::UNIQUE_CONSTRAINT_DROP: + MG_ASSERT(false, "Invalid function call!"); } data_.emplace_back(timestamp_, data); }