Decouple text indices from other index types

This commit is contained in:
Ante Pušić 2024-02-09 22:31:19 +01:00
parent 3114c69627
commit 3bc570ead8
11 changed files with 165 additions and 57 deletions

View File

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

View File

@ -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<void>);
memgraph::query::IndexQuery::Action action_;
memgraph::query::IndexQuery::Type type_;
memgraph::query::LabelIx label_;
std::vector<memgraph::query::PropertyIx> properties_;
std::string index_name_;
IndexQuery *Clone(AstStorage *storage) const override {
IndexQuery *object = storage->Create<IndexQuery>();
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<PropertyIx> 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<void>);
memgraph::query::TextIndexQuery::Action action_;
memgraph::query::LabelIx label_;
std::string index_name_;
TextIndexQuery *Clone(AstStorage *storage) const override {
TextIndexQuery *object = storage->Create<TextIndexQuery>();
object->action_ = action_;
object->label_ = storage->GetLabelIx(label_.name);
object->index_name_ = index_name_;
return object;
}
protected:
IndexQuery(Action action, Type type, LabelIx label, std::vector<PropertyIx> properties)
: action_(action), type_(type), label_(label), properties_(properties) {}
IndexQuery(Action action, Type type, LabelIx label, std::vector<PropertyIx> 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;

View File

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

View File

@ -243,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<TextIndexQuery *>(ctx->children[0]->accept(this));
query_ = text_index_query;
return text_index_query;
}
antlrcpp::Any CypherMainVisitor::visitCreateIndex(MemgraphCypher::CreateIndexContext *ctx) {
auto *index_query = storage_->Create<IndexQuery>();
index_query->action_ = IndexQuery::Action::CREATE;
index_query->type_ = IndexQuery::Type::LOOKUP;
index_query->label_ = AddLabel(std::any_cast<std::string>(ctx->labelName()->accept(this)));
if (ctx->propertyKeyName()) {
auto name_key = std::any_cast<PropertyIx>(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<IndexQuery>();
auto *index_query = storage_->Create<TextIndexQuery>();
index_query->index_name_ = std::any_cast<std::string>(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<std::string>(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<IndexQuery>();
index_query->action_ = IndexQuery::Action::DROP;
index_query->type_ = IndexQuery::Type::LOOKUP;
if (ctx->propertyKeyName()) {
auto key = std::any_cast<PropertyIx>(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<IndexQuery>();
auto *index_query = storage_->Create<TextIndexQuery>();
index_query->index_name_ = std::any_cast<std::string>(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;
}

View File

@ -148,6 +148,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitIndexQuery(MemgraphCypher::IndexQueryContext *ctx) override;
/**
* @return TextIndexQuery*
*/
antlrcpp::Any visitTextIndexQuery(MemgraphCypher::TextIndexQueryContext *ctx) override;
/**
* @return ExplainQuery*
*/
@ -489,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;

View File

@ -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 )* ;

View File

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

View File

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

View File

@ -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<storage::PropertyId> properties;
std::vector<std::string> 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<utils::BasicResult<storage::StorageIndexDefinitionError, void>> 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<utils::BasicResult<storage::StorageIndexDefinitionError, void>> 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<Notification> *notifications, CurrentDB &current_db) {
if (in_explicit_transaction) {
throw IndexInMulticommandTxException();
}
auto *text_index_query = utils::Downcast<TextIndexQuery>(parsed_query.query);
std::function<void(Notification &)> handler;
// TODO: we will need transaction for replication
MG_ASSERT(current_db.db_acc_, "Text index query expects a current DB");
auto &db_acc = *current_db.db_acc_;
MG_ASSERT(current_db.db_transactional_accessor_, "Text index query expects a current DB transaction");
auto *dba = &*current_db.execution_db_accessor_;
// Creating an index influences computed plan costs.
auto invalidate_plan_cache = [plan_cache = db_acc->plan_cache()] {
plan_cache->WithLock([&](auto &cache) { cache.reset(); });
};
auto *storage = db_acc->storage();
auto label = storage->NameToLabel(text_index_query->label_.name);
auto &index_name = text_index_query->index_name_;
Notification index_notification(SeverityLevel::INFO);
switch (text_index_query->action_) {
case TextIndexQuery::Action::CREATE: {
index_notification.code = NotificationCode::CREATE_INDEX;
index_notification.title = fmt::format("Created text index on label {}.", text_index_query->label_.name);
// TODO: not just storage + invalidate_plan_cache. Need a DB transaction (for replication)
handler = [dba, label, index_name, label_name = text_index_query->label_.name,
invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) {
std::optional<utils::BasicResult<storage::StorageIndexDefinitionError, void>> 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<utils::BasicResult<storage::StorageIndexDefinitionError, void>> 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<int> /*unused*/) mutable {
handler(index_notification);
notifications->push_back(index_notification);
return QueryHandlerResult::COMMIT; // TODO: Will need to become COMMIT when we fix replication
},
RWType::W};
}
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
InterpreterContext *interpreter_context, Interpreter &interpreter) {
if (in_explicit_transaction) {
@ -4227,13 +4293,14 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
utils::Downcast<CypherQuery>(parsed_query.query) || utils::Downcast<ExplainQuery>(parsed_query.query) ||
utils::Downcast<ProfileQuery>(parsed_query.query) || utils::Downcast<DumpQuery>(parsed_query.query) ||
utils::Downcast<TriggerQuery>(parsed_query.query) || utils::Downcast<AnalyzeGraphQuery>(parsed_query.query) ||
utils::Downcast<IndexQuery>(parsed_query.query) || utils::Downcast<DatabaseInfoQuery>(parsed_query.query) ||
utils::Downcast<ConstraintQuery>(parsed_query.query);
utils::Downcast<IndexQuery>(parsed_query.query) || utils::Downcast<TextIndexQuery>(parsed_query.query) ||
utils::Downcast<DatabaseInfoQuery>(parsed_query.query) || utils::Downcast<ConstraintQuery>(parsed_query.query);
if (!in_explicit_transaction_ && requires_db_transaction) {
// TODO: ATM only a single database, will change when we have multiple database transactions
bool could_commit = utils::Downcast<CypherQuery>(parsed_query.query) != nullptr;
bool unique = utils::Downcast<IndexQuery>(parsed_query.query) != nullptr ||
utils::Downcast<TextIndexQuery>(parsed_query.query) != nullptr ||
utils::Downcast<ConstraintQuery>(parsed_query.query) != nullptr ||
upper_case_query.find(kSchemaAssert) != std::string::npos;
SetupDatabaseTransaction(could_commit, unique);
@ -4270,6 +4337,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<IndexQuery>(parsed_query.query)) {
prepared_query = PrepareIndexQuery(std::move(parsed_query), in_explicit_transaction_,
&query_execution->notifications, current_db_);
} else if (utils::Downcast<TextIndexQuery>(parsed_query.query)) {
prepared_query = PrepareTextIndexQuery(std::move(parsed_query), in_explicit_transaction_,
&query_execution->notifications, current_db_);
} else if (utils::Downcast<AnalyzeGraphQuery>(parsed_query.query)) {
prepared_query = PrepareAnalyzeGraphQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {

View File

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

View File

@ -563,9 +563,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
memgraph::query::test_common::OnCreate { \
std::vector<memgraph::query::Clause *> { __VA_ARGS__ } \
}
#define CREATE_INDEX_ON(label, property) \
storage.Create<memgraph::query::IndexQuery>(memgraph::query::IndexQuery::Action::CREATE, \
memgraph::query::IndexQuery::Type::LOOKUP, (label), \
#define CREATE_INDEX_ON(label, property) \
storage.Create<memgraph::query::IndexQuery>(memgraph::query::IndexQuery::Action::CREATE, (label), \
std::vector<memgraph::query::PropertyIx>{(property)})
#define QUERY(...) memgraph::query::test_common::GetQuery(this->storage, __VA_ARGS__)
#define SINGLE_QUERY(...) \