2017-03-22 23:38:43 +08:00
|
|
|
#pragma once
|
|
|
|
|
2017-06-21 17:29:13 +08:00
|
|
|
#include <gflags/gflags.h>
|
|
|
|
|
2019-10-10 17:23:33 +08:00
|
|
|
#include "database/graph_db.hpp"
|
|
|
|
#include "database/graph_db_accessor.hpp"
|
2017-03-22 23:38:43 +08:00
|
|
|
#include "query/context.hpp"
|
2019-09-11 22:10:53 +08:00
|
|
|
#include "query/db_accessor.hpp"
|
2017-12-22 20:39:31 +08:00
|
|
|
#include "query/frontend/ast/ast.hpp"
|
2019-01-16 18:30:17 +08:00
|
|
|
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
2017-06-15 00:53:02 +08:00
|
|
|
#include "query/frontend/stripped.hpp"
|
2017-04-13 16:01:16 +08:00
|
|
|
#include "query/interpret/frame.hpp"
|
2017-09-19 22:58:22 +08:00
|
|
|
#include "query/plan/operator.hpp"
|
2019-11-12 17:47:02 +08:00
|
|
|
#include "query/stream.hpp"
|
2019-10-10 17:23:33 +08:00
|
|
|
#include "utils/memory.hpp"
|
2019-08-20 15:33:31 +08:00
|
|
|
#include "utils/skip_list.hpp"
|
2019-01-07 16:18:52 +08:00
|
|
|
#include "utils/spin_lock.hpp"
|
2017-07-15 01:33:45 +08:00
|
|
|
#include "utils/timer.hpp"
|
2019-10-07 23:31:25 +08:00
|
|
|
#include "utils/tsc.hpp"
|
2017-03-22 23:38:43 +08:00
|
|
|
|
2018-08-24 16:12:04 +08:00
|
|
|
DECLARE_bool(query_cost_planner);
|
Flags cleanup and QueryEngine removal
Summary:
I started with cleaning flags up (removing unused ones, documenting undocumented ones). There were some flags to remove in `QueryEngine`. Seeing how we never use hardcoded queries (AFAIK last Mislav's testing also indicated they aren't faster then interpretation), when removing those unused flags the `QueryEngine` becomes obsolete. That means that a bunch of other stuff becomes obsolete, along with the hardcoded queries. So I removed it all (this has been discussed and approved on the daily).
Some flags that were previously undocumented in `docs/user_technical/installation` are now documented. The following flags are NOT documented and in my opinion should not be displayed when starting `./memgraph --help` (@mferencevic):
```
query_vertex_count_to_expand_existsing (from rule_based_planner.cpp)
query_max_plans (rule_based_planner.cpp)
```
If you think that another organization is needed w.r.t. flag visibility, comment.
@teon.banek: I had to remove some stuff from CMakeLists to make it buildable. Please review what I removed and clean up if necessary if/when this lands. If the needed changes are minor, you can also comment.
Reviewers: buda, mislav.bradac, teon.banek, mferencevic
Reviewed By: buda, mislav.bradac
Subscribers: pullbot, mferencevic, teon.banek
Differential Revision: https://phabricator.memgraph.io/D825
2017-09-22 22:17:09 +08:00
|
|
|
DECLARE_int32(query_plan_cache_ttl);
|
2017-06-15 00:53:02 +08:00
|
|
|
|
2017-03-22 23:38:43 +08:00
|
|
|
namespace query {
|
|
|
|
|
2019-10-10 17:23:33 +08:00
|
|
|
static constexpr size_t kExecutionMemoryBlockSize = 1U * 1024U * 1024U;
|
|
|
|
|
2020-01-15 20:58:41 +08:00
|
|
|
class AuthQueryHandler {
|
|
|
|
public:
|
|
|
|
AuthQueryHandler() = default;
|
|
|
|
virtual ~AuthQueryHandler() = default;
|
|
|
|
|
|
|
|
AuthQueryHandler(const AuthQueryHandler &) = delete;
|
|
|
|
AuthQueryHandler(AuthQueryHandler &&) = delete;
|
|
|
|
AuthQueryHandler &operator=(const AuthQueryHandler &) = delete;
|
|
|
|
AuthQueryHandler &operator=(AuthQueryHandler &&) = delete;
|
|
|
|
|
|
|
|
/// Return false if the user already exists.
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual bool CreateUser(const std::string &username,
|
|
|
|
const std::optional<std::string> &password) = 0;
|
|
|
|
|
|
|
|
/// Return false if the user does not exist.
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual bool DropUser(const std::string &username) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual void SetPassword(const std::string &username,
|
|
|
|
const std::optional<std::string> &password) = 0;
|
|
|
|
|
|
|
|
/// Return false if the role already exists.
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual bool CreateRole(const std::string &rolename) = 0;
|
|
|
|
|
|
|
|
/// Return false if the role does not exist.
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual bool DropRole(const std::string &rolename) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual std::vector<TypedValue> GetUsernames() = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual std::vector<TypedValue> GetRolenames() = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual std::optional<std::string> GetRolenameForUser(
|
|
|
|
const std::string &username) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual std::vector<TypedValue> GetUsernamesForRole(
|
|
|
|
const std::string &rolename) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual void SetRole(const std::string &username,
|
|
|
|
const std::string &rolename) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual void ClearRole(const std::string &username) = 0;
|
|
|
|
|
|
|
|
virtual std::vector<std::vector<TypedValue>> GetPrivileges(
|
|
|
|
const std::string &user_or_role) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual void GrantPrivilege(
|
|
|
|
const std::string &user_or_role,
|
|
|
|
const std::vector<AuthQuery::Privilege> &privileges) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual void DenyPrivilege(
|
|
|
|
const std::string &user_or_role,
|
|
|
|
const std::vector<AuthQuery::Privilege> &privileges) = 0;
|
|
|
|
|
|
|
|
/// @throw QueryRuntimeException if an error ocurred.
|
|
|
|
virtual void RevokePrivilege(
|
|
|
|
const std::string &user_or_role,
|
|
|
|
const std::vector<AuthQuery::Privilege> &privileges) = 0;
|
|
|
|
};
|
|
|
|
|
2019-10-30 21:05:47 +08:00
|
|
|
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
|
|
|
|
|
2019-10-17 20:48:04 +08:00
|
|
|
/**
|
|
|
|
* A container for data related to the preparation of a query.
|
|
|
|
*/
|
|
|
|
struct PreparedQuery {
|
|
|
|
std::vector<std::string> header;
|
|
|
|
std::vector<AuthQuery::Privilege> privileges;
|
2019-10-30 21:05:47 +08:00
|
|
|
std::function<QueryHandlerResult(AnyStream *stream)> query_handler;
|
2019-10-17 20:48:04 +08:00
|
|
|
};
|
|
|
|
|
2018-08-24 16:12:04 +08:00
|
|
|
// TODO: Maybe this should move to query/plan/planner.
|
|
|
|
/// Interface for accessing the root operator of a logical plan.
|
|
|
|
class LogicalPlan {
|
|
|
|
public:
|
|
|
|
virtual ~LogicalPlan() {}
|
|
|
|
|
|
|
|
virtual const plan::LogicalOperator &GetRoot() const = 0;
|
|
|
|
virtual double GetCost() const = 0;
|
|
|
|
virtual const SymbolTable &GetSymbolTable() const = 0;
|
Remove GraphDbAccessor and storage types from Ast
Summary:
This diff removes the need for a database when parsing a query and
creating an Ast. Instead of storing storage::{Label,Property,EdgeType}
in Ast nodes, we store the name and an index into all of the names. This
allows for easy creation of a map from {Label,Property,EdgeType} index
into the concrete storage type. Obviously, this comes with a performance
penalty during execution, but it should be minor. The upside is that the
query/frontend minimally depends on storage (PropertyValue), which makes
writing tests easier as well as running them a lot faster (there is no
database setup). This is most noticeable in the ast_serialization test
which took a long time due to start up of a distributed database.
Reviewers: mtomic, llugovic
Reviewed By: mtomic
Subscribers: mferencevic, pullbot
Differential Revision: https://phabricator.memgraph.io/D1774
2019-01-14 21:41:37 +08:00
|
|
|
virtual const AstStorage &GetAstStorage() const = 0;
|
2018-08-24 16:12:04 +08:00
|
|
|
};
|
|
|
|
|
2019-10-23 21:12:12 +08:00
|
|
|
class CachedPlan {
|
|
|
|
public:
|
|
|
|
explicit CachedPlan(std::unique_ptr<LogicalPlan> plan);
|
2018-03-13 17:35:14 +08:00
|
|
|
|
2019-10-23 21:12:12 +08:00
|
|
|
const auto &plan() const { return plan_->GetRoot(); }
|
|
|
|
double cost() const { return plan_->GetCost(); }
|
|
|
|
const auto &symbol_table() const { return plan_->GetSymbolTable(); }
|
|
|
|
const auto &ast_storage() const { return plan_->GetAstStorage(); }
|
2018-10-24 23:44:53 +08:00
|
|
|
|
2019-10-23 21:12:12 +08:00
|
|
|
bool IsExpired() const {
|
|
|
|
return cache_timer_.Elapsed() >
|
|
|
|
std::chrono::seconds(FLAGS_query_plan_cache_ttl);
|
2019-10-07 23:31:25 +08:00
|
|
|
};
|
|
|
|
|
2019-10-23 21:12:12 +08:00
|
|
|
private:
|
|
|
|
std::unique_ptr<LogicalPlan> plan_;
|
|
|
|
utils::Timer cache_timer_;
|
|
|
|
};
|
2019-10-07 23:31:25 +08:00
|
|
|
|
2019-10-23 21:12:12 +08:00
|
|
|
struct CachedQuery {
|
|
|
|
AstStorage ast_storage;
|
|
|
|
Query *query;
|
|
|
|
std::vector<AuthQuery::Privilege> required_privileges;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct QueryCacheEntry {
|
|
|
|
bool operator==(const QueryCacheEntry &other) const {
|
|
|
|
return first == other.first;
|
|
|
|
}
|
|
|
|
bool operator<(const QueryCacheEntry &other) const {
|
|
|
|
return first < other.first;
|
|
|
|
}
|
|
|
|
bool operator==(const HashType &other) const { return first == other; }
|
|
|
|
bool operator<(const HashType &other) const { return first < other; }
|
|
|
|
|
|
|
|
HashType first;
|
|
|
|
// TODO: Maybe store the query string here and use it as a key with the hash
|
|
|
|
// so that we eliminate the risk of hash collisions.
|
|
|
|
CachedQuery second;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct PlanCacheEntry {
|
|
|
|
bool operator==(const PlanCacheEntry &other) const {
|
|
|
|
return first == other.first;
|
|
|
|
}
|
|
|
|
bool operator<(const PlanCacheEntry &other) const {
|
|
|
|
return first < other.first;
|
|
|
|
}
|
|
|
|
bool operator==(const HashType &other) const { return first == other; }
|
|
|
|
bool operator<(const HashType &other) const { return first < other; }
|
|
|
|
|
|
|
|
HashType first;
|
|
|
|
// TODO: Maybe store the query string here and use it as a key with the hash
|
|
|
|
// so that we eliminate the risk of hash collisions.
|
|
|
|
std::shared_ptr<CachedPlan> second;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds data shared between multiple `Interpreter` instances (which might be
|
|
|
|
* running concurrently).
|
|
|
|
*
|
|
|
|
* Users should initialize the context but should not modify it after it has
|
|
|
|
* been passed to an `Interpreter` instance.
|
|
|
|
*/
|
|
|
|
struct InterpreterContext {
|
2019-10-10 17:23:33 +08:00
|
|
|
explicit InterpreterContext(storage::Storage *db)
|
|
|
|
: db(db) {
|
|
|
|
CHECK(db) << "Storage must not be NULL";
|
|
|
|
}
|
|
|
|
|
|
|
|
storage::Storage *db;
|
|
|
|
|
2019-10-17 20:48:04 +08:00
|
|
|
// ANTLR has singleton instance that is shared between threads. It is
|
|
|
|
// protected by locks inside of ANTLR. Unfortunately, they are not protected
|
|
|
|
// in a very good way. Once we have ANTLR version without race conditions we
|
|
|
|
// can remove this lock. This will probably never happen since ANTLR
|
2019-10-23 21:12:12 +08:00
|
|
|
// developers introduce more bugs in each version. Fortunately, we have
|
|
|
|
// cache so this lock probably won't impact performance much...
|
|
|
|
utils::SpinLock antlr_lock;
|
2019-11-25 22:08:16 +08:00
|
|
|
std::optional<double> tsc_frequency{utils::GetTSCFrequency()};
|
|
|
|
std::atomic<bool> is_shutting_down{false};
|
|
|
|
// The default execution timeout is 3 minutes.
|
|
|
|
double execution_timeout_sec{180.0};
|
2019-10-23 21:12:12 +08:00
|
|
|
|
2020-01-15 20:58:41 +08:00
|
|
|
AuthQueryHandler *auth{nullptr};
|
2019-10-23 21:12:12 +08:00
|
|
|
|
|
|
|
utils::SkipList<QueryCacheEntry> ast_cache;
|
|
|
|
utils::SkipList<PlanCacheEntry> plan_cache;
|
|
|
|
};
|
2019-10-07 23:31:25 +08:00
|
|
|
|
2019-11-25 22:08:16 +08:00
|
|
|
/// Function that is used to tell all active interpreters that they should stop
|
|
|
|
/// their ongoing execution.
|
|
|
|
inline void Shutdown(InterpreterContext *context) {
|
|
|
|
context->is_shutting_down.store(true, std::memory_order_release);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Function used to set the maximum execution timeout in seconds.
|
|
|
|
inline void SetExecutionTimeout(InterpreterContext *context, double timeout) {
|
|
|
|
context->execution_timeout_sec = timeout;
|
|
|
|
}
|
|
|
|
|
2019-10-17 20:48:04 +08:00
|
|
|
class Interpreter final {
|
2019-10-23 21:12:12 +08:00
|
|
|
public:
|
2019-10-07 23:31:25 +08:00
|
|
|
explicit Interpreter(InterpreterContext *interpreter_context);
|
2017-12-22 20:39:31 +08:00
|
|
|
Interpreter(const Interpreter &) = delete;
|
|
|
|
Interpreter &operator=(const Interpreter &) = delete;
|
|
|
|
Interpreter(Interpreter &&) = delete;
|
|
|
|
Interpreter &operator=(Interpreter &&) = delete;
|
2019-10-17 20:48:04 +08:00
|
|
|
~Interpreter() { Abort(); }
|
2019-10-10 17:23:33 +08:00
|
|
|
|
2017-12-22 20:39:31 +08:00
|
|
|
/**
|
2019-10-17 20:48:04 +08:00
|
|
|
* Prepare a query for execution.
|
|
|
|
*
|
|
|
|
* To prepare a query for execution means to preprocess the query and adjust
|
|
|
|
* the state of the `Interpreter` in such a way so that the next call to
|
|
|
|
* `PullAll` executes the query.
|
|
|
|
*
|
|
|
|
* @throw raft::CantExecuteQueries if the Memgraph instance is not a Raft
|
|
|
|
* leader and a query other than an Info Raft query was given
|
|
|
|
* @throw query::QueryException
|
2017-12-22 20:39:31 +08:00
|
|
|
*/
|
2019-10-18 22:27:47 +08:00
|
|
|
std::pair<std::vector<std::string>, std::vector<query::AuthQuery::Privilege>>
|
|
|
|
Prepare(const std::string &query,
|
2020-01-22 23:20:13 +08:00
|
|
|
const std::map<std::string, storage::PropertyValue> ¶ms);
|
2019-10-10 17:23:33 +08:00
|
|
|
|
2019-10-17 20:48:04 +08:00
|
|
|
/**
|
|
|
|
* Execute the last prepared query and stream *all* of the results into the
|
|
|
|
* given stream.
|
|
|
|
*
|
2019-10-18 22:27:47 +08:00
|
|
|
* It is not possible to prepare a query once and execute it multiple times,
|
|
|
|
* i.e. `Prepare` has to be called before *every* call to `PullAll`.
|
|
|
|
*
|
2019-10-17 20:48:04 +08:00
|
|
|
* TStream should be a type implementing the `Stream` concept, i.e. it should
|
|
|
|
* contain the member function `void Result(const std::vector<TypedValue> &)`.
|
|
|
|
* The provided vector argument is valid only for the duration of the call to
|
|
|
|
* `Result`. The stream should make an explicit copy if it wants to use it
|
|
|
|
* further.
|
|
|
|
*
|
|
|
|
* @throw utils::BasicException
|
|
|
|
* @throw query::QueryException
|
|
|
|
*/
|
2019-10-10 17:23:33 +08:00
|
|
|
template <typename TStream>
|
2019-10-17 20:48:04 +08:00
|
|
|
std::map<std::string, TypedValue> PullAll(TStream *result_stream);
|
2019-10-10 17:23:33 +08:00
|
|
|
|
2019-10-17 20:48:04 +08:00
|
|
|
/**
|
|
|
|
* Abort the current multicommand transaction.
|
|
|
|
*/
|
2019-10-10 17:23:33 +08:00
|
|
|
void Abort();
|
2017-03-22 23:38:43 +08:00
|
|
|
|
2017-06-08 00:28:31 +08:00
|
|
|
private:
|
2019-10-07 23:31:25 +08:00
|
|
|
InterpreterContext *interpreter_context_;
|
2019-10-17 20:48:04 +08:00
|
|
|
std::optional<PreparedQuery> prepared_query_;
|
|
|
|
std::map<std::string, TypedValue> summary_;
|
2018-03-15 22:00:43 +08:00
|
|
|
|
2019-10-10 17:23:33 +08:00
|
|
|
std::optional<storage::Storage::Accessor> db_accessor_;
|
|
|
|
std::optional<DbAccessor> execution_db_accessor_;
|
|
|
|
bool in_explicit_transaction_{false};
|
|
|
|
bool expect_rollback_{false};
|
|
|
|
utils::MonotonicBufferResource execution_memory_{kExecutionMemoryBlockSize};
|
|
|
|
|
2019-10-30 21:05:47 +08:00
|
|
|
PreparedQuery PrepareTransactionQuery(std::string_view query_upper);
|
2019-10-10 17:23:33 +08:00
|
|
|
void Commit();
|
|
|
|
void AdvanceCommand();
|
|
|
|
void AbortCommand();
|
2017-06-08 00:28:31 +08:00
|
|
|
};
|
2017-04-26 22:12:39 +08:00
|
|
|
|
2019-10-17 20:48:04 +08:00
|
|
|
template <typename TStream>
|
|
|
|
std::map<std::string, TypedValue> Interpreter::PullAll(TStream *result_stream) {
|
2019-10-30 21:05:47 +08:00
|
|
|
CHECK(prepared_query_) << "Trying to call PullAll without a prepared query";
|
2019-10-17 20:48:04 +08:00
|
|
|
|
|
|
|
try {
|
|
|
|
// Wrap the (statically polymorphic) stream type into a common type which
|
|
|
|
// the handler knows.
|
|
|
|
AnyStream stream{result_stream, &execution_memory_};
|
2019-10-30 21:05:47 +08:00
|
|
|
QueryHandlerResult res = prepared_query_->query_handler(&stream);
|
|
|
|
// Erase the prepared query in order to enforce that every call to `PullAll`
|
|
|
|
// must be preceded by a call to `Prepare`.
|
|
|
|
prepared_query_ = std::nullopt;
|
2019-10-17 20:48:04 +08:00
|
|
|
|
|
|
|
if (!in_explicit_transaction_) {
|
2019-10-30 21:05:47 +08:00
|
|
|
switch (res) {
|
|
|
|
case QueryHandlerResult::COMMIT:
|
|
|
|
Commit();
|
|
|
|
break;
|
|
|
|
case QueryHandlerResult::ABORT:
|
|
|
|
Abort();
|
|
|
|
break;
|
|
|
|
case QueryHandlerResult::NOTHING:
|
|
|
|
// The only cases in which we have nothing to do are those where we're
|
|
|
|
// either in an explicit transaction or the query is such that a
|
|
|
|
// transaction wasn't started on a call to `Prepare()`.
|
|
|
|
CHECK(in_explicit_transaction_ || !db_accessor_);
|
|
|
|
break;
|
2019-10-17 20:48:04 +08:00
|
|
|
}
|
|
|
|
}
|
2019-10-30 21:05:47 +08:00
|
|
|
} catch (const ExplicitTransactionUsageException &) {
|
|
|
|
// Just let the exception propagate for error reporting purposes, but don't
|
|
|
|
// abort the current command.
|
|
|
|
throw;
|
2019-10-17 20:48:04 +08:00
|
|
|
#ifdef MG_SINGLE_NODE_HA
|
|
|
|
} catch (const query::HintedAbortError &) {
|
|
|
|
AbortCommand();
|
|
|
|
throw utils::BasicException("Transaction was asked to abort.");
|
|
|
|
#endif
|
|
|
|
} catch (const utils::BasicException &) {
|
|
|
|
AbortCommand();
|
|
|
|
throw;
|
|
|
|
}
|
2019-10-18 22:27:47 +08:00
|
|
|
|
|
|
|
return summary_;
|
2019-10-17 20:48:04 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 22:12:39 +08:00
|
|
|
} // namespace query
|