From edaa03a960324ab38fa0208e4e244cf63152dffd Mon Sep 17 00:00:00 2001 From: Teon Banek <teon.banek@memgraph.io> Date: Tue, 12 Nov 2019 15:41:16 +0100 Subject: [PATCH] Expose explicit Label index creation through Cypher Reviewers: mferencevic, ipaljak Reviewed By: mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2547 --- .../frontend/ast/cypher_main_visitor.cpp | 12 ++++++---- .../frontend/opencypher/grammar/Cypher.g4 | 4 ++-- src/query/interpreter.cpp | 24 ++++++++++++++----- src/query/plan/rewrite/index_lookup.hpp | 24 +++++++++++++------ src/query/plan/vertex_count_cache.hpp | 4 ++++ tests/manual/interactive_planning.cpp | 2 ++ tests/unit/query_plan.cpp | 22 +++++++++++++---- tests/unit/query_plan_checker.hpp | 4 ++++ 8 files changed, 73 insertions(+), 23 deletions(-) diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index c9c8a3864..c5621f471 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -154,8 +154,10 @@ antlrcpp::Any CypherMainVisitor::visitCreateIndex( auto *index_query = storage_->Create<IndexQuery>(); index_query->action_ = IndexQuery::Action::CREATE; index_query->label_ = AddLabel(ctx->labelName()->accept(this)); - PropertyIx name_key = ctx->propertyKeyName()->accept(this); - index_query->properties_ = {name_key}; + if (ctx->propertyKeyName()) { + PropertyIx name_key = ctx->propertyKeyName()->accept(this); + index_query->properties_ = {name_key}; + } return index_query; } @@ -163,8 +165,10 @@ antlrcpp::Any CypherMainVisitor::visitDropIndex( MemgraphCypher::DropIndexContext *ctx) { auto *index_query = storage_->Create<IndexQuery>(); index_query->action_ = IndexQuery::Action::DROP; - PropertyIx key = ctx->propertyKeyName()->accept(this); - index_query->properties_ = {key}; + if (ctx->propertyKeyName()) { + PropertyIx key = ctx->propertyKeyName()->accept(this); + index_query->properties_ = {key}; + } index_query->label_ = AddLabel(ctx->labelName()->accept(this)); return index_query; } diff --git a/src/query/frontend/opencypher/grammar/Cypher.g4 b/src/query/frontend/opencypher/grammar/Cypher.g4 index d34cdf5d5..554449584 100644 --- a/src/query/frontend/opencypher/grammar/Cypher.g4 +++ b/src/query/frontend/opencypher/grammar/Cypher.g4 @@ -306,9 +306,9 @@ integerLiteral : DecimalLiteral | HexadecimalLiteral ; -createIndex : CREATE INDEX ON ':' labelName '(' propertyKeyName ')' ; +createIndex : CREATE INDEX ON ':' labelName ( '(' propertyKeyName ')' )? ; -dropIndex : DROP INDEX ON ':' labelName '(' propertyKeyName ')' ; +dropIndex : DROP INDEX ON ':' labelName ( '(' propertyKeyName ')' )? ; doubleLiteral : FloatingLiteral ; diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index 32d527c8d..7b7fab805 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -850,15 +850,21 @@ PreparedQuery PrepareIndexQuery( #ifdef MG_SINGLE_NODE_V2 handler = [interpreter_context, label, properties = std::move(properties), invalidate_plan_cache = std::move(invalidate_plan_cache)] { - CHECK(properties.size() == 1); - interpreter_context->db->CreateIndex(label, properties[0]); + if (properties.empty()) { + interpreter_context->db->CreateIndex(label); + } else { + CHECK(properties.size() == 1U); + interpreter_context->db->CreateIndex(label, properties[0]); + } invalidate_plan_cache(); }; #else handler = [dba, label, properties = std::move(properties), invalidate_plan_cache = std::move(invalidate_plan_cache)] { + // Old storage creates label index by default. + if (properties.empty()) return; try { - CHECK(properties.size() == 1); + CHECK(properties.size() == 1U); dba->CreateIndex(label, properties[0]); invalidate_plan_cache(); } catch (const database::ConstraintViolationException &e) { @@ -876,15 +882,21 @@ PreparedQuery PrepareIndexQuery( #ifdef MG_SINGLE_NODE_V2 handler = [interpreter_context, label, properties = std::move(properties), invalidate_plan_cache = std::move(invalidate_plan_cache)] { - CHECK(properties.size() == 1); - interpreter_context->db->DropIndex(label, properties[0]); + if (properties.empty()) { + interpreter_context->db->DropIndex(label); + } else { + CHECK(properties.size() == 1U); + interpreter_context->db->DropIndex(label, properties[0]); + } invalidate_plan_cache(); }; #else handler = [dba, label, properties = std::move(properties), invalidate_plan_cache = std::move(invalidate_plan_cache)] { + if (properties.empty()) + throw QueryRuntimeException("Label index cannot be dropped!"); try { - CHECK(properties.size() == 1); + CHECK(properties.size() == 1U); dba->DropIndex(label, properties[0]); invalidate_plan_cache(); } catch (const database::TransactionException &e) { diff --git a/src/query/plan/rewrite/index_lookup.hpp b/src/query/plan/rewrite/index_lookup.hpp index 1ad7a07d0..c8bac9475 100644 --- a/src/query/plan/rewrite/index_lookup.hpp +++ b/src/query/plan/rewrite/index_lookup.hpp @@ -435,14 +435,22 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { return db_->NameToProperty(prop.name); } - LabelIx FindBestLabelIndex(const std::unordered_set<LabelIx> &labels) { + std::optional<LabelIx> FindBestLabelIndex( + const std::unordered_set<LabelIx> &labels) { CHECK(!labels.empty()) << "Trying to find the best label without any labels."; - return *std::min_element(labels.begin(), labels.end(), - [this](const auto &label1, const auto &label2) { - return db_->VerticesCount(GetLabel(label1)) < - db_->VerticesCount(GetLabel(label2)); - }); + std::optional<LabelIx> best_label; + for (const auto &label : labels) { + if (!db_->LabelIndexExists(GetLabel(label))) continue; + if (!best_label) { + best_label = label; + continue; + } + if (db_->VerticesCount(GetLabel(label)) < + db_->VerticesCount(GetLabel(*best_label))) + best_label = label; + } + return best_label; } // Finds the label-property combination which has indexed the lowest amount of @@ -560,7 +568,9 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { prop_filter.value_, view); } } - auto label = FindBestLabelIndex(labels); + auto maybe_label = FindBestLabelIndex(labels); + if (!maybe_label) return nullptr; + const auto &label = *maybe_label; if (max_vertex_count && db_->VerticesCount(GetLabel(label)) > *max_vertex_count) { // Don't create an indexed lookup, since we have more labeled vertices diff --git a/src/query/plan/vertex_count_cache.hpp b/src/query/plan/vertex_count_cache.hpp index dfaf3b479..50e2ca618 100644 --- a/src/query/plan/vertex_count_cache.hpp +++ b/src/query/plan/vertex_count_cache.hpp @@ -69,6 +69,10 @@ class VertexCountCache { return bounds_vertex_count.at(bounds); } + bool LabelIndexExists(storage::Label label) { + return db_->LabelIndexExists(label); + } + bool LabelPropertyIndexExists(storage::Label label, storage::Property property) { return db_->LabelPropertyIndexExists(label, property); diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp index b833400ac..d1fe621f8 100644 --- a/tests/manual/interactive_planning.cpp +++ b/tests/manual/interactive_planning.cpp @@ -200,6 +200,8 @@ class InteractiveDbAccessor { "' in range " + range_string.str()); } + bool LabelIndexExists(storage::Label label) { return true; } + bool LabelPropertyIndexExists(storage::Label label_id, storage::Property property_id) { auto label = dba_->LabelName(label_id); diff --git a/tests/unit/query_plan.cpp b/tests/unit/query_plan.cpp index 1b2d35d64..0afede6d3 100644 --- a/tests/unit/query_plan.cpp +++ b/tests/unit/query_plan.cpp @@ -167,10 +167,21 @@ TYPED_TEST(TestPlanner, MatchLabeledNodes) { auto *as_n = NEXPR("n", IDENT("n")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", label))), RETURN(as_n))); - auto symbol_table = query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); - CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), - ExpectProduce()); + { + // Without created label index + auto symbol_table = query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), + ExpectProduce()); + } + { + // With created label index + dba.SetIndexCount(dba.Label(label), 0); + auto symbol_table = query::MakeSymbolTable(query); + auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), + ExpectProduce()); + } } TYPED_TEST(TestPlanner, MatchPathReturn) { @@ -1130,6 +1141,7 @@ TYPED_TEST(TestPlanner, UnableToUsePropertyIndex) { FakeDbAccessor dba; auto label = dba.Label("label"); auto property = dba.Property("property"); + dba.SetIndexCount(label, 0); dba.SetIndexCount(label, property, 0); AstStorage storage; auto *query = QUERY(SINGLE_QUERY( @@ -1149,6 +1161,7 @@ TYPED_TEST(TestPlanner, SecondPropertyIndex) { FakeDbAccessor dba; auto label = dba.Label("label"); auto property = PROPERTY_PAIR("property"); + dba.SetIndexCount(label, 0); dba.SetIndexCount(label, dba.Property("property"), 0); AstStorage storage; auto n_prop = PROPERTY_LOOKUP("n", property); @@ -1296,6 +1309,7 @@ TYPED_TEST(TestPlanner, MatchDoubleScanToExpandExisting) { // Test MATCH (n) -[r]- (m :label) RETURN r FakeDbAccessor dba; auto label = "label"; + dba.SetIndexCount(dba.Label(label), 0); AstStorage storage; auto *query = QUERY(SINGLE_QUERY( MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m", label))), RETURN("r"))); diff --git a/tests/unit/query_plan_checker.hpp b/tests/unit/query_plan_checker.hpp index 897d7a939..f315a81cd 100644 --- a/tests/unit/query_plan_checker.hpp +++ b/tests/unit/query_plan_checker.hpp @@ -397,6 +397,10 @@ class FakeDbAccessor { return 0; } + bool LabelIndexExists(storage::Label label) const { + return label_index_.find(label) != label_index_.end(); + } + bool LabelPropertyIndexExists(storage::Label label, storage::Property property) const { for (auto &index : label_property_index_) {