diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp index 57d5398ab..1a32d2485 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::TextIndexQuery::kType{utils::TypeId::AST_TEXT_INDEX_QUERY, "TextIndexQuery", + &query::Query::kType}; + constexpr utils::TypeInfo query::Create::kType{utils::TypeId::AST_CREATE, "Create", &query::Clause::kType}; constexpr utils::TypeInfo query::CallProcedure::kType{utils::TypeId::AST_CALL_PROCEDURE, "CallProcedure", diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index ad99e30f0..e448fb28a 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -2196,39 +2196,59 @@ class IndexQuery : public memgraph::query::Query { enum class Action { CREATE, DROP }; - // IndexQuery distinguishes two types of indices. Lookup indices (label and label & property) make it faster to look - // up nodes, whereas text indices enable text search. - enum class Type { LOOKUP, TEXT }; - IndexQuery() = default; DEFVISITABLE(QueryVisitor); memgraph::query::IndexQuery::Action action_; - memgraph::query::IndexQuery::Type type_; memgraph::query::LabelIx label_; std::vector properties_; - std::string index_name_; IndexQuery *Clone(AstStorage *storage) const override { IndexQuery *object = storage->Create(); object->action_ = action_; - object->type_ = type_; object->label_ = storage->GetLabelIx(label_.name); object->properties_.resize(properties_.size()); for (auto i = 0; i < object->properties_.size(); ++i) { object->properties_[i] = storage->GetPropertyIx(properties_[i].name); } + return object; + } + + protected: + IndexQuery(Action action, LabelIx label, std::vector properties) + : action_(action), label_(std::move(label)), properties_(std::move(properties)) {} + + private: + friend class AstStorage; +}; + +class TextIndexQuery : public memgraph::query::Query { + public: + static const utils::TypeInfo kType; + const utils::TypeInfo &GetTypeInfo() const override { return kType; } + + enum class Action { CREATE, DROP }; + + TextIndexQuery() = default; + + DEFVISITABLE(QueryVisitor); + + memgraph::query::TextIndexQuery::Action action_; + memgraph::query::LabelIx label_; + std::string index_name_; + + TextIndexQuery *Clone(AstStorage *storage) const override { + TextIndexQuery *object = storage->Create(); + object->action_ = action_; + object->label_ = storage->GetLabelIx(label_.name); object->index_name_ = index_name_; return object; } protected: - IndexQuery(Action action, Type type, LabelIx label, std::vector properties) - : action_(action), type_(type), label_(label), properties_(properties) {} - - IndexQuery(Action action, Type type, LabelIx label, std::vector properties, std::string index_name) - : action_(action), type_(type), label_(label), properties_(properties), index_name_(index_name) {} + TextIndexQuery(Action action, LabelIx label, std::string index_name) + : action_(action), label_(std::move(label)), index_name_(index_name) {} private: friend class AstStorage; diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp index 5d463d3ee..d54d0cd99 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 TextIndexQuery; class DatabaseInfoQuery; class SystemInfoQuery; class ConstraintQuery; @@ -143,11 +144,11 @@ class ExpressionVisitor template class QueryVisitor - : public utils::Visitor {}; + : public utils::Visitor {}; } // namespace memgraph::query diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index 2817cbd3c..e4bedebdc 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -243,10 +243,16 @@ antlrcpp::Any CypherMainVisitor::visitIndexQuery(MemgraphCypher::IndexQueryConte return index_query; } +antlrcpp::Any CypherMainVisitor::visitTextIndexQuery(MemgraphCypher::TextIndexQueryContext *ctx) { + MG_ASSERT(ctx->children.size() == 1, "TextIndexQuery should have exactly one child!"); + auto *text_index_query = std::any_cast(ctx->children[0]->accept(this)); + query_ = text_index_query; + return text_index_query; +} + antlrcpp::Any CypherMainVisitor::visitCreateIndex(MemgraphCypher::CreateIndexContext *ctx) { auto *index_query = storage_->Create(); index_query->action_ = IndexQuery::Action::CREATE; - index_query->type_ = IndexQuery::Type::LOOKUP; index_query->label_ = AddLabel(std::any_cast(ctx->labelName()->accept(this))); if (ctx->propertyKeyName()) { auto name_key = std::any_cast(ctx->propertyKeyName()->accept(this)); @@ -256,10 +262,9 @@ antlrcpp::Any CypherMainVisitor::visitCreateIndex(MemgraphCypher::CreateIndexCon } antlrcpp::Any CypherMainVisitor::visitCreateTextIndex(MemgraphCypher::CreateTextIndexContext *ctx) { - auto *index_query = storage_->Create(); + auto *index_query = storage_->Create(); index_query->index_name_ = std::any_cast(ctx->indexName()->accept(this)); - index_query->action_ = IndexQuery::Action::CREATE; - index_query->type_ = IndexQuery::Type::TEXT; + index_query->action_ = TextIndexQuery::Action::CREATE; index_query->label_ = AddLabel(std::any_cast(ctx->labelName()->accept(this))); return index_query; } @@ -267,7 +272,6 @@ antlrcpp::Any CypherMainVisitor::visitCreateTextIndex(MemgraphCypher::CreateText antlrcpp::Any CypherMainVisitor::visitDropIndex(MemgraphCypher::DropIndexContext *ctx) { auto *index_query = storage_->Create(); index_query->action_ = IndexQuery::Action::DROP; - index_query->type_ = IndexQuery::Type::LOOKUP; if (ctx->propertyKeyName()) { auto key = std::any_cast(ctx->propertyKeyName()->accept(this)); index_query->properties_ = {key}; @@ -277,10 +281,9 @@ antlrcpp::Any CypherMainVisitor::visitDropIndex(MemgraphCypher::DropIndexContext } antlrcpp::Any CypherMainVisitor::visitDropTextIndex(MemgraphCypher::DropTextIndexContext *ctx) { - auto *index_query = storage_->Create(); + auto *index_query = storage_->Create(); index_query->index_name_ = std::any_cast(ctx->indexName()->accept(this)); - index_query->action_ = IndexQuery::Action::DROP; - index_query->type_ = IndexQuery::Type::TEXT; + index_query->action_ = TextIndexQuery::Action::DROP; return index_query; } diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp index 2c09df2a7..a1ae1de38 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 TextIndexQuery* + */ + antlrcpp::Any visitTextIndexQuery(MemgraphCypher::TextIndexQueryContext *ctx) override; + /** * @return ExplainQuery* */ @@ -489,7 +494,7 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { antlrcpp::Any visitCreateIndex(MemgraphCypher::CreateIndexContext *ctx) override; /** - * @return IndexQuery* + * @return TextIndexQuery* */ antlrcpp::Any visitCreateTextIndex(MemgraphCypher::CreateTextIndexContext *ctx) override; @@ -499,7 +504,7 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { antlrcpp::Any visitDropIndex(MemgraphCypher::DropIndexContext *ctx) override; /** - * @return IndexQuery* + * @return TextIndexQuery* */ antlrcpp::Any visitDropTextIndex(MemgraphCypher::DropTextIndexContext *ctx) override; diff --git a/src/query/frontend/opencypher/grammar/Cypher.g4 b/src/query/frontend/opencypher/grammar/Cypher.g4 index 0d550d0c5..1623cd651 100644 --- a/src/query/frontend/opencypher/grammar/Cypher.g4 +++ b/src/query/frontend/opencypher/grammar/Cypher.g4 @@ -25,6 +25,7 @@ statement : query ; query : cypherQuery | indexQuery + | textIndexQuery | explainQuery | profileQuery | databaseInfoQuery @@ -63,7 +64,9 @@ profileQuery : PROFILE cypherQuery ; cypherQuery : singleQuery ( cypherUnion )* ( queryMemoryLimit )? ; -indexQuery : createIndex | dropIndex | createTextIndex | dropTextIndex; +indexQuery : createIndex | dropIndex; + +textIndexQuery : createTextIndex | dropTextIndex; singleQuery : clause ( clause )* ; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index 0597967c7..e8adaaa39 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 + | textIndexQuery | explainQuery | profileQuery | databaseInfoQuery diff --git a/src/query/frontend/semantic/required_privileges.cpp b/src/query/frontend/semantic/required_privileges.cpp index ef66a75ac..271bf2914 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, public HierarchicalTreeVis void Visit(IndexQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); } + void Visit(TextIndexQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); } + void Visit(AnalyzeGraphQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::INDEX); } void Visit(AuthQuery & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::AUTH); } diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index 5215bdfe6..594715687 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -2543,7 +2543,6 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans auto *storage = db_acc->storage(); auto label = storage->NameToLabel(index_query->label_.name); - auto &index_name = index_query->index_name_; std::vector properties; std::vector properties_string; @@ -2567,20 +2566,12 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans fmt::format("Created index on label {} on properties {}.", index_query->label_.name, properties_stringified); // TODO: not just storage + invalidate_plan_cache. Need a DB transaction (for replication) - handler = [dba, index_type = index_query->type_, label, index_name, - properties_stringified = std::move(properties_stringified), label_name = index_query->label_.name, - properties = std::move(properties), + handler = [dba, label, properties_stringified = std::move(properties_stringified), + label_name = index_query->label_.name, properties = std::move(properties), invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) { MG_ASSERT(properties.size() <= 1U); std::optional> maybe_index_error; - if (index_type == IndexQuery::Type::LOOKUP) { - maybe_index_error = properties.empty() ? dba->CreateIndex(label) : dba->CreateIndex(label, properties[0]); - } else if (index_type == IndexQuery::Type::TEXT) { - if (!flags::run_time::GetExperimentalTextSearchEnabled()) { - throw QueryException("To use text indices, enable the text search feature."); - } - maybe_index_error = dba->CreateTextIndex(index_name, label); - } + maybe_index_error = properties.empty() ? dba->CreateIndex(label) : dba->CreateIndex(label, properties[0]); utils::OnScopeExit invalidator(invalidate_plan_cache); if (maybe_index_error.has_value() && maybe_index_error.value().HasError()) { @@ -2597,20 +2588,12 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans index_notification.title = fmt::format("Dropped index on label {} on properties {}.", index_query->label_.name, utils::Join(properties_string, ", ")); // TODO: not just storage + invalidate_plan_cache. Need a DB transaction (for replication) - handler = [dba, index_type = index_query->type_, label, index_name, - properties_stringified = std::move(properties_stringified), label_name = index_query->label_.name, - properties = std::move(properties), + handler = [dba, label, properties_stringified = std::move(properties_stringified), + label_name = index_query->label_.name, properties = std::move(properties), invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) { MG_ASSERT(properties.size() <= 1U); std::optional> maybe_index_error; - if (index_type == IndexQuery::Type::LOOKUP) { - maybe_index_error = properties.empty() ? dba->DropIndex(label) : dba->DropIndex(label, properties[0]); - } else if (index_type == IndexQuery::Type::TEXT) { - if (!flags::run_time::GetExperimentalTextSearchEnabled()) { - throw QueryException("To use text indices, enable the text search feature."); - } - maybe_index_error = dba->DropTextIndex(index_name); - } + maybe_index_error = properties.empty() ? dba->DropIndex(label) : dba->DropIndex(label, properties[0]); utils::OnScopeExit invalidator(invalidate_plan_cache); if (maybe_index_error.has_value() && maybe_index_error.value().HasError()) { @@ -2635,6 +2618,89 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans RWType::W}; } +PreparedQuery PrepareTextIndexQuery(ParsedQuery parsed_query, bool in_explicit_transaction, + std::vector *notifications, CurrentDB ¤t_db) { + if (in_explicit_transaction) { + throw IndexInMulticommandTxException(); + } + + auto *text_index_query = utils::Downcast(parsed_query.query); + std::function handler; + + // TODO: we will need transaction for replication + MG_ASSERT(current_db.db_acc_, "Text index query expects a current DB"); + auto &db_acc = *current_db.db_acc_; + + MG_ASSERT(current_db.db_transactional_accessor_, "Text index query expects a current DB transaction"); + auto *dba = &*current_db.execution_db_accessor_; + + // Creating an index influences computed plan costs. + auto invalidate_plan_cache = [plan_cache = db_acc->plan_cache()] { + plan_cache->WithLock([&](auto &cache) { cache.reset(); }); + }; + + auto *storage = db_acc->storage(); + auto label = storage->NameToLabel(text_index_query->label_.name); + auto &index_name = text_index_query->index_name_; + + Notification index_notification(SeverityLevel::INFO); + switch (text_index_query->action_) { + case TextIndexQuery::Action::CREATE: { + index_notification.code = NotificationCode::CREATE_INDEX; + index_notification.title = fmt::format("Created text index on label {}.", text_index_query->label_.name); + + // TODO: not just storage + invalidate_plan_cache. Need a DB transaction (for replication) + handler = [dba, label, index_name, label_name = text_index_query->label_.name, + invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) { + std::optional> maybe_index_error; + if (!flags::run_time::GetExperimentalTextSearchEnabled()) { + throw TextSearchDisabledException(); + } + maybe_index_error = dba->CreateTextIndex(index_name, label); + utils::OnScopeExit invalidator(invalidate_plan_cache); + + if (maybe_index_error.has_value() && maybe_index_error.value().HasError()) { + index_notification.code = NotificationCode::EXISTENT_INDEX; + index_notification.title = fmt::format("Text index on label {} already exists.", label_name); + // ABORT? + } + }; + break; + } + case TextIndexQuery::Action::DROP: { + index_notification.code = NotificationCode::DROP_INDEX; + index_notification.title = fmt::format("Dropped text index on label {}.", text_index_query->label_.name); + // TODO: not just storage + invalidate_plan_cache. Need a DB transaction (for replication) + handler = [dba, label, index_name, label_name = text_index_query->label_.name, + invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) { + std::optional> maybe_index_error; + if (!flags::run_time::GetExperimentalTextSearchEnabled()) { + throw TextSearchDisabledException(); + } + maybe_index_error = dba->DropTextIndex(index_name); + utils::OnScopeExit invalidator(invalidate_plan_cache); + + if (maybe_index_error.has_value() && maybe_index_error.value().HasError()) { + index_notification.code = NotificationCode::NONEXISTENT_INDEX; + index_notification.title = fmt::format("Text index on label {} 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 /*unused*/) mutable { + handler(index_notification); + notifications->push_back(index_notification); + return QueryHandlerResult::COMMIT; // TODO: Will need to become COMMIT when we fix replication + }, + RWType::W}; +} + PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction, InterpreterContext *interpreter_context, Interpreter &interpreter) { if (in_explicit_transaction) { @@ -4227,13 +4293,14 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || - utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || - utils::Downcast(parsed_query.query); + utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || + utils::Downcast(parsed_query.query) || utils::Downcast(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(parsed_query.query) != nullptr; bool unique = utils::Downcast(parsed_query.query) != nullptr || + utils::Downcast(parsed_query.query) != nullptr || utils::Downcast(parsed_query.query) != nullptr || upper_case_query.find(kSchemaAssert) != std::string::npos; SetupDatabaseTransaction(could_commit, unique); @@ -4270,6 +4337,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, } else if (utils::Downcast(parsed_query.query)) { prepared_query = PrepareIndexQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications, current_db_); + } else if (utils::Downcast(parsed_query.query)) { + prepared_query = PrepareTextIndexQuery(std::move(parsed_query), in_explicit_transaction_, + &query_execution->notifications, current_db_); } else if (utils::Downcast(parsed_query.query)) { prepared_query = PrepareAnalyzeGraphQuery(std::move(parsed_query), in_explicit_transaction_, current_db_); } else if (utils::Downcast(parsed_query.query)) { diff --git a/src/utils/typeinfo.hpp b/src/utils/typeinfo.hpp index 1640a70f7..1fe273c0a 100644 --- a/src/utils/typeinfo.hpp +++ b/src/utils/typeinfo.hpp @@ -173,6 +173,7 @@ enum class TypeId : uint64_t { AST_EXPLAIN_QUERY, AST_PROFILE_QUERY, AST_INDEX_QUERY, + AST_TEXT_INDEX_QUERY, AST_CREATE, AST_CALL_PROCEDURE, AST_MATCH, diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index 55120c424..7fafaad9f 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -563,9 +563,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec memgraph::query::test_common::OnCreate { \ std::vector { __VA_ARGS__ } \ } -#define CREATE_INDEX_ON(label, property) \ - storage.Create(memgraph::query::IndexQuery::Action::CREATE, \ - memgraph::query::IndexQuery::Type::LOOKUP, (label), \ +#define CREATE_INDEX_ON(label, property) \ + storage.Create(memgraph::query::IndexQuery::Action::CREATE, (label), \ std::vector{(property)}) #define QUERY(...) memgraph::query::test_common::GetQuery(this->storage, __VA_ARGS__) #define SINGLE_QUERY(...) \