Add EXPLAIN to openCypher
Summary: * Move PlanPrinter from test to memgraph * Add explainQuery to MemgraphCypher.g4 * Add Explain operator * Update changelog Reviewers: mtomic, buda, ipaljak Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1555
This commit is contained in:
parent
6615a9de53
commit
50c75c56a4
@ -10,6 +10,7 @@
|
||||
|
||||
* [Enterprise Ed.] Authentication and authorization support.
|
||||
* [Enterprise Ed.] Kafka integration.
|
||||
* Add `EXPLAIN` clause to openCypher
|
||||
|
||||
## v0.12.0
|
||||
|
||||
|
@ -158,3 +158,41 @@ to true is matched:
|
||||
MATCH (n)
|
||||
RETURN CASE WHEN n.height < 30 THEN "short" WHEN n.height > 300 THEN "tall" END
|
||||
```
|
||||
|
||||
### EXPLAIN
|
||||
|
||||
The `EXPLAIN` clause is used to get a visual representation of operations a
|
||||
given query will perform. This can be a useful tool for understanding the
|
||||
query execution and fine tuning the behaviour.
|
||||
|
||||
NOTE: Query will not be executed. `EXPLAIN` only shows the operations that
|
||||
would need to be taken.
|
||||
|
||||
For example:
|
||||
|
||||
```opencypher
|
||||
EXPLAIN MATCH (n :Person) WHERE n.height < 30 RETURN n;
|
||||
```
|
||||
|
||||
Will display:
|
||||
|
||||
```plaintext
|
||||
* Produce {n}
|
||||
* Filter
|
||||
* ScanAllByLabel (n :Person)
|
||||
```
|
||||
|
||||
This makes it evident that indexing on a property value `height` is not used.
|
||||
|
||||
We create an index with:
|
||||
|
||||
```opencypher
|
||||
CREATE INDEX ON :Person(height)
|
||||
```
|
||||
|
||||
Running the same `EXPLAIN` will now display:
|
||||
|
||||
```plaintext
|
||||
* Produce {n}
|
||||
* ScanAllByLabelPropertyRange (n :Person {height})
|
||||
```
|
||||
|
@ -56,6 +56,7 @@ set(memgraph_src_files
|
||||
query/plan/distributed.cpp
|
||||
query/plan/operator.cpp
|
||||
query/plan/preprocess.cpp
|
||||
query/plan/pretty_print.cpp
|
||||
query/plan/rule_based_planner.cpp
|
||||
query/plan/variable_start_planner.cpp
|
||||
query/repl.cpp
|
||||
|
@ -155,6 +155,7 @@ struct CypherUnion {
|
||||
struct Query {
|
||||
singleQuery @0 :Tree;
|
||||
cypherUnions @1 :List(Tree);
|
||||
explain @2 :Bool;
|
||||
}
|
||||
|
||||
struct BinaryOperator {
|
||||
@ -431,7 +432,7 @@ struct AuthQuery {
|
||||
role @2 :Text;
|
||||
userOrRole @3 :Text;
|
||||
password @4 :Tree;
|
||||
privileges @5 :List(Privilege);
|
||||
privileges @5 :List(Privilege);
|
||||
}
|
||||
|
||||
struct CreateStream {
|
||||
|
@ -2700,6 +2700,7 @@ void Query::Save(capnp::Tree::Builder *tree_builder,
|
||||
}
|
||||
|
||||
void Query::Save(capnp::Query::Builder *builder, std::vector<int> *saved_uids) {
|
||||
builder->setExplain(explain_);
|
||||
if (single_query_) {
|
||||
auto sq_builder = builder->initSingleQuery();
|
||||
single_query_->Save(&sq_builder, saved_uids);
|
||||
@ -2716,6 +2717,7 @@ void Query::Load(const capnp::Tree::Reader &reader, AstStorage *storage,
|
||||
std::vector<int> *loaded_uids) {
|
||||
Tree::Load(reader, storage, loaded_uids);
|
||||
auto query_reader = reader.getQuery();
|
||||
explain_ = query_reader.getExplain();
|
||||
if (query_reader.hasSingleQuery()) {
|
||||
const auto sq_reader = query_reader.getSingleQuery();
|
||||
single_query_ =
|
||||
|
@ -1728,6 +1728,7 @@ class Query : public Tree {
|
||||
for (auto *cypher_union : cypher_unions_) {
|
||||
query->cypher_unions_.push_back(cypher_union->Clone(storage));
|
||||
}
|
||||
query->explain_ = explain_;
|
||||
return query;
|
||||
}
|
||||
|
||||
@ -1736,7 +1737,11 @@ class Query : public Tree {
|
||||
void Save(capnp::Tree::Builder *builder,
|
||||
std::vector<int> *saved_uids) override;
|
||||
|
||||
/// True if this is an EXPLAIN query
|
||||
bool explain_ = false;
|
||||
/// First and potentially only query
|
||||
SingleQuery *single_query_ = nullptr;
|
||||
/// Contains remaining queries that should form a union with `single_query_`
|
||||
std::vector<CypherUnion *> cypher_unions_;
|
||||
|
||||
protected:
|
||||
|
@ -25,6 +25,14 @@ namespace query::frontend {
|
||||
|
||||
const std::string CypherMainVisitor::kAnonPrefix = "anon";
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitExplainQuery(
|
||||
MemgraphCypher::ExplainQueryContext *ctx) {
|
||||
visitChildren(ctx);
|
||||
CHECK(query_);
|
||||
query_->explain_ = true;
|
||||
return query_;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitAuthQuery(
|
||||
MemgraphCypher::AuthQueryContext *ctx) {
|
||||
query_ = storage_.query();
|
||||
|
@ -132,6 +132,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Query*
|
||||
*/
|
||||
antlrcpp::Any visitExplainQuery(MemgraphCypher::ExplainQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Query*
|
||||
*/
|
||||
|
@ -48,8 +48,11 @@ symbolicName : UnescapedSymbolicName
|
||||
query : regularQuery
|
||||
| authQuery
|
||||
| streamQuery
|
||||
| explainQuery
|
||||
;
|
||||
|
||||
explainQuery : EXPLAIN regularQuery ;
|
||||
|
||||
authQuery : createRole
|
||||
| dropRole
|
||||
| showRoles
|
||||
|
@ -6,7 +6,7 @@
|
||||
* and bitsets) if needed.
|
||||
*/
|
||||
|
||||
lexer grammar MemgraphCypherLexer ;
|
||||
lexer grammar MemgraphCypherLexer ;
|
||||
|
||||
import CypherLexer ;
|
||||
|
||||
@ -17,6 +17,7 @@ BATCHES : B A T C H E S ;
|
||||
DATA : D A T A ;
|
||||
DENY : D E N Y ;
|
||||
DROP : D R O P ;
|
||||
EXPLAIN : E X P L A I N ;
|
||||
FOR : F O R ;
|
||||
FROM : F R O M ;
|
||||
GRANT : G R A N T ;
|
||||
|
@ -168,7 +168,7 @@ class Interpreter {
|
||||
|
||||
/**
|
||||
* Generates an Results object for the parameters. The resulting object
|
||||
* can the be Pulled with it's results written to an arbitrary stream.
|
||||
* can be Pulled with its results written to an arbitrary stream.
|
||||
*/
|
||||
Results operator()(const std::string &query,
|
||||
database::GraphDbAccessor &db_accessor,
|
||||
|
@ -95,6 +95,16 @@ class IndependentSubtreeFinder : public HierarchicalLogicalOperatorVisitor {
|
||||
bool Visit(StartStopAllStreams &) override { return true; }
|
||||
bool Visit(TestStream &) override { return true; }
|
||||
|
||||
// Treat Explain as if the query is planned without it
|
||||
bool PreVisit(Explain &explain) override {
|
||||
prev_ops_.push_back(&explain);
|
||||
return true;
|
||||
}
|
||||
bool PostVisit(Explain &explain) override {
|
||||
prev_ops_.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(ScanAll &scan) override {
|
||||
prev_ops_.push_back(&scan);
|
||||
return true;
|
||||
@ -1041,6 +1051,26 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Treat Explain as if the query is planned without it
|
||||
bool PreVisit(Explain &explain) override {
|
||||
CHECK(prev_ops_.empty());
|
||||
prev_ops_.push_back(&explain);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Explain &explain) override {
|
||||
// Set Explain as the final operator on master.
|
||||
if (ShouldSplit()) {
|
||||
auto input = explain.input();
|
||||
auto pull_id = AddWorkerPlan(input);
|
||||
Split(explain, std::make_shared<PullRemote>(
|
||||
input, pull_id,
|
||||
input->OutputSymbols(distributed_plan_.symbol_table)));
|
||||
}
|
||||
prev_ops_.pop_back();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip needs to skip only the first N results from *all* of the results.
|
||||
// Therefore, the earliest (deepest in the plan tree) encountered Skip will
|
||||
// break the plan in 2 parts.
|
||||
@ -1140,9 +1170,10 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
|
||||
// Shallow copy Distinct
|
||||
auto pull_id = AddWorkerPlan(std::make_shared<Distinct>(distinct));
|
||||
auto input = distinct.input();
|
||||
Split(distinct, std::make_shared<PullRemote>(
|
||||
input, pull_id, input->OutputSymbols(
|
||||
distributed_plan_.symbol_table)));
|
||||
Split(distinct,
|
||||
std::make_shared<PullRemote>(
|
||||
input, pull_id,
|
||||
input->OutputSymbols(distributed_plan_.symbol_table)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/interpret/eval.hpp"
|
||||
#include "query/path.hpp"
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/hashing/fnv.hpp"
|
||||
@ -4613,4 +4614,52 @@ std::unique_ptr<Cursor> TestStream::MakeCursor(
|
||||
return std::make_unique<TestStreamCursor>(*this, db);
|
||||
}
|
||||
|
||||
Explain::Explain(const std::shared_ptr<LogicalOperator> &input,
|
||||
const Symbol &output_symbol)
|
||||
: input_(input), output_symbol_(output_symbol) {}
|
||||
|
||||
ACCEPT_WITH_INPUT(Explain);
|
||||
|
||||
std::vector<Symbol> Explain::OutputSymbols(const SymbolTable &) const {
|
||||
return {output_symbol_};
|
||||
}
|
||||
|
||||
std::vector<Symbol> Explain::ModifiedSymbols(const SymbolTable &table) const {
|
||||
return OutputSymbols(table);
|
||||
}
|
||||
|
||||
class ExplainCursor : public Cursor {
|
||||
public:
|
||||
ExplainCursor(const Explain &self, const database::GraphDbAccessor &dba,
|
||||
const Symbol &output_symbol)
|
||||
: printed_plan_rows_([&dba, &self]() {
|
||||
std::stringstream stream;
|
||||
PrettyPrint(dba, self.input().get(), &stream);
|
||||
return utils::Split(stream.str(), "\n");
|
||||
}()),
|
||||
print_it_(printed_plan_rows_.begin()),
|
||||
output_symbol_(output_symbol) {}
|
||||
|
||||
bool Pull(Frame &frame, Context &ctx) override {
|
||||
if (print_it_ != printed_plan_rows_.end()) {
|
||||
frame[output_symbol_] = *print_it_;
|
||||
print_it_++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Reset() override { print_it_ = printed_plan_rows_.begin(); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> printed_plan_rows_;
|
||||
std::vector<std::string>::iterator print_it_;
|
||||
Symbol output_symbol_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Cursor> Explain::MakeCursor(
|
||||
database::GraphDbAccessor &dba) const {
|
||||
return std::make_unique<ExplainCursor>(*this, dba, output_symbol_);
|
||||
}
|
||||
|
||||
} // namespace query::plan
|
||||
|
@ -109,6 +109,7 @@ class ShowStreams;
|
||||
class StartStopStream;
|
||||
class StartStopAllStreams;
|
||||
class TestStream;
|
||||
class Explain;
|
||||
|
||||
using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||
@ -118,7 +119,7 @@ using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
|
||||
ExpandUniquenessFilter<VertexAccessor>,
|
||||
ExpandUniquenessFilter<EdgeAccessor>, Accumulate, Aggregate, Skip, Limit,
|
||||
OrderBy, Merge, Optional, Unwind, Distinct, Union, PullRemote, Synchronize,
|
||||
Cartesian, PullRemoteOrderBy>;
|
||||
Cartesian, PullRemoteOrderBy, Explain>;
|
||||
|
||||
using LogicalOperatorLeafVisitor =
|
||||
::utils::LeafVisitor<Once, CreateIndex, AuthHandler, CreateStream,
|
||||
@ -2661,6 +2662,32 @@ in the db.")
|
||||
#>cpp TestStream() {} cpp<#)
|
||||
(:serialize :capnp))
|
||||
|
||||
(lcp:define-class explain (logical-operator)
|
||||
((input "std::shared_ptr<LogicalOperator>"
|
||||
:capnp-save #'save-operator-pointer
|
||||
:capnp-load #'load-operator-pointer)
|
||||
(output-symbol "Symbol" :reader t))
|
||||
(:documentation "Pretty print a LogicalOperator plan")
|
||||
(:public
|
||||
#>cpp
|
||||
Explain(const std::shared_ptr<LogicalOperator> &input,
|
||||
const Symbol &output_symbol);
|
||||
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
std::unique_ptr<Cursor> MakeCursor(
|
||||
database::GraphDbAccessor & db) const override;
|
||||
std::vector<Symbol> OutputSymbols(const SymbolTable &) 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;
|
||||
}
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp Explain () {} cpp<#)
|
||||
(:serialize :capnp))
|
||||
|
||||
(lcp:pop-namespace) ;; plan
|
||||
(lcp:pop-namespace) ;; query
|
||||
|
@ -98,6 +98,12 @@ auto MakeLogicalPlan(TPlanningContext &context, const Parameters ¶meters,
|
||||
prev_op, prev_op->OutputSymbols(context.symbol_table));
|
||||
}
|
||||
|
||||
if (context.ast_storage.query()->explain_) {
|
||||
last_op = std::make_unique<Explain>(
|
||||
std::move(last_op),
|
||||
context.symbol_table.CreateSymbol("QUERY PLAN", false));
|
||||
}
|
||||
|
||||
return std::make_pair(std::move(last_op), total_cost);
|
||||
}
|
||||
|
||||
|
@ -477,29 +477,28 @@ std::vector<SingleQueryPart> CollectSingleQueryParts(
|
||||
return query_parts;
|
||||
}
|
||||
|
||||
QueryParts CollectQueryParts(SymbolTable &symbol_table,
|
||||
AstStorage &storage) {
|
||||
auto query = storage.query();
|
||||
QueryParts CollectQueryParts(SymbolTable &symbol_table, AstStorage &storage) {
|
||||
auto *query = storage.query();
|
||||
std::vector<QueryPart> query_parts;
|
||||
|
||||
auto *single_query = query->single_query_;
|
||||
CHECK(single_query) << "Expected at least a single query";
|
||||
query_parts.push_back(
|
||||
QueryPart{CollectSingleQueryParts(symbol_table, storage, single_query)});
|
||||
|
||||
bool distinct = false;
|
||||
|
||||
if (auto *single_query = query->single_query_) {
|
||||
query_parts.push_back(QueryPart{
|
||||
CollectSingleQueryParts(symbol_table, storage, single_query)});
|
||||
}
|
||||
|
||||
for (auto *cypher_union : query->cypher_unions_) {
|
||||
if (cypher_union->distinct_) {
|
||||
distinct = true;
|
||||
}
|
||||
|
||||
if (auto *single_query = cypher_union->single_query_) {
|
||||
query_parts.push_back(QueryPart{
|
||||
CollectSingleQueryParts(symbol_table, storage, single_query),
|
||||
cypher_union});
|
||||
}
|
||||
auto *single_query = cypher_union->single_query_;
|
||||
CHECK(single_query) << "Expected UNION to have a query";
|
||||
query_parts.push_back(
|
||||
QueryPart{CollectSingleQueryParts(symbol_table, storage, single_query),
|
||||
cypher_union});
|
||||
}
|
||||
return QueryParts{query_parts, distinct};
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace query::plan
|
||||
|
311
src/query/plan/pretty_print.cpp
Normal file
311
src/query/plan/pretty_print.cpp
Normal file
@ -0,0 +1,311 @@
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
|
||||
#include "database/graph_db_accessor.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
|
||||
namespace query::plan {
|
||||
|
||||
namespace {
|
||||
|
||||
class PlanPrinter final : public HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::Visit;
|
||||
|
||||
explicit PlanPrinter(const database::GraphDbAccessor *dba, std::ostream *out)
|
||||
: dba_(dba), out_(out) {}
|
||||
|
||||
#define PRE_VISIT(TOp) \
|
||||
bool PreVisit(query::plan::TOp &) override { \
|
||||
WithPrintLn([](auto &out) { out << "* " << #TOp; }); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
PRE_VISIT(CreateNode);
|
||||
PRE_VISIT(CreateExpand);
|
||||
PRE_VISIT(Delete);
|
||||
|
||||
bool PreVisit(query::plan::ScanAll &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAll"
|
||||
<< " (" << op.output_symbol().name() << ")";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ScanAllByLabel &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabel"
|
||||
<< " (" << op.output_symbol().name() << " :"
|
||||
<< dba_->LabelName(op.label()) << ")";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ScanAllByLabelPropertyValue &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyValue"
|
||||
<< " (" << op.output_symbol().name() << " :"
|
||||
<< dba_->LabelName(op.label()) << " {"
|
||||
<< dba_->PropertyName(op.property()) << "})";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ScanAllByLabelPropertyRange &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyRange"
|
||||
<< " (" << op.output_symbol().name() << " :"
|
||||
<< dba_->LabelName(op.label()) << " {"
|
||||
<< dba_->PropertyName(op.property()) << "})";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Expand &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Expand";
|
||||
PrintExpand(out, op);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ExpandVariable &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ExpandVariable";
|
||||
PrintExpand(out, op);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Produce &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Produce {";
|
||||
utils::PrintIterable(
|
||||
out, op.named_expressions(), ", ",
|
||||
[](auto &out, const auto &nexpr) { out << nexpr->name_; });
|
||||
out << "}";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(SetProperty);
|
||||
PRE_VISIT(SetProperties);
|
||||
PRE_VISIT(SetLabels);
|
||||
PRE_VISIT(RemoveProperty);
|
||||
PRE_VISIT(RemoveLabels);
|
||||
PRE_VISIT(ExpandUniquenessFilter<VertexAccessor>);
|
||||
PRE_VISIT(ExpandUniquenessFilter<EdgeAccessor>);
|
||||
PRE_VISIT(Accumulate);
|
||||
|
||||
bool PreVisit(query::plan::Aggregate &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Aggregate {";
|
||||
utils::PrintIterable(
|
||||
out, op.aggregations(), ", ",
|
||||
[](auto &out, const auto &aggr) { out << aggr.output_sym.name(); });
|
||||
out << "} {";
|
||||
utils::PrintIterable(
|
||||
out, op.remember(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
PRE_VISIT(Skip);
|
||||
PRE_VISIT(Limit);
|
||||
|
||||
bool PreVisit(query::plan::OrderBy &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* OrderBy {";
|
||||
utils::PrintIterable(
|
||||
out, op.output_symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Merge &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* Merge"; });
|
||||
Branch(*op.merge_match(), "On Match");
|
||||
Branch(*op.merge_create(), "On Create");
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Optional &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* Optional"; });
|
||||
Branch(*op.optional());
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
PRE_VISIT(Unwind);
|
||||
PRE_VISIT(Distinct);
|
||||
|
||||
bool Visit(query::plan::Once &op) override {
|
||||
// Ignore checking Once, it is implicitly at the end.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::CreateIndex &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* CreateIndex"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::AuthHandler &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* AuthHandler"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::CreateStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* CreateStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::DropStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* DropStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::ShowStreams &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* ShowStreams"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::StartStopStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* StartStopStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::StartStopAllStreams &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* StartStopAllStreams"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::TestStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* TestStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Explain &explain) override {
|
||||
WithPrintLn([&explain](auto &out) {
|
||||
out << "* Explain {" << explain.output_symbol().name() << "}";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::PullRemote &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* PullRemote [" << op.plan_id() << "] {";
|
||||
utils::PrintIterable(
|
||||
out, op.symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
WithPrintLn([](auto &out) { out << "|\\"; });
|
||||
++depth_;
|
||||
WithPrintLn([](auto &out) { out << "* workers"; });
|
||||
--depth_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Synchronize &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* Synchronize";
|
||||
if (op.advance_command()) out << " (ADV CMD)";
|
||||
});
|
||||
if (op.pull_remote()) Branch(*op.pull_remote());
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Cartesian &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* Cartesian {";
|
||||
utils::PrintIterable(
|
||||
out, op.left_symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << " : ";
|
||||
utils::PrintIterable(
|
||||
out, op.right_symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
Branch(*op.right_op());
|
||||
op.left_op()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::PullRemoteOrderBy &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* PullRemoteOrderBy {";
|
||||
utils::PrintIterable(
|
||||
out, op.symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
|
||||
WithPrintLn([](auto &out) { out << "|\\"; });
|
||||
++depth_;
|
||||
WithPrintLn([](auto &out) { out << "* workers"; });
|
||||
--depth_;
|
||||
return true;
|
||||
}
|
||||
#undef PRE_VISIT
|
||||
|
||||
private:
|
||||
bool DefaultPreVisit() override {
|
||||
WithPrintLn([](auto &out) { out << "* Unknown operator!"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call fun with output stream. The stream is prefixed with amount of spaces
|
||||
// corresponding to the current depth_.
|
||||
template <class TFun>
|
||||
void WithPrintLn(TFun fun) {
|
||||
*out_ << " ";
|
||||
for (int i = 0; i < depth_; ++i) {
|
||||
*out_ << "| ";
|
||||
}
|
||||
fun(*out_);
|
||||
*out_ << std::endl;
|
||||
}
|
||||
|
||||
// Forward this printer to another operator branch by incrementing the depth
|
||||
// and printing the branch name.
|
||||
void Branch(query::plan::LogicalOperator &op,
|
||||
const std::string &branch_name = "") {
|
||||
WithPrintLn([&](auto &out) { out << "|\\ " << branch_name; });
|
||||
++depth_;
|
||||
op.Accept(*this);
|
||||
--depth_;
|
||||
}
|
||||
|
||||
void PrintExpand(std::ostream &out, const query::plan::ExpandCommon &op) {
|
||||
out << " (" << op.input_symbol().name() << ")"
|
||||
<< (op.direction() == query::EdgeAtom::Direction::IN ? "<-" : "-")
|
||||
<< "[" << op.edge_symbol().name() << "]"
|
||||
<< (op.direction() == query::EdgeAtom::Direction::OUT ? "->" : "-")
|
||||
<< "(" << op.node_symbol().name() << ")";
|
||||
}
|
||||
|
||||
int depth_ = 0;
|
||||
const database::GraphDbAccessor *dba_{nullptr};
|
||||
std::ostream *out_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void PrettyPrint(const database::GraphDbAccessor &dba,
|
||||
LogicalOperator *plan_root, std::ostream *out) {
|
||||
PlanPrinter printer(&dba, out);
|
||||
plan_root->Accept(printer);
|
||||
}
|
||||
|
||||
} // namespace query::plan
|
27
src/query/plan/pretty_print.hpp
Normal file
27
src/query/plan/pretty_print.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace database {
|
||||
class GraphDbAccessor;
|
||||
}
|
||||
|
||||
namespace query::plan {
|
||||
|
||||
class LogicalOperator;
|
||||
|
||||
/// Pretty print a `LogicalOperator` plan to a `std::ostream`.
|
||||
/// GraphDbAccessor is needed for resolving label and property names.
|
||||
/// Note that `plan_root` isn't modified, but we can't take it as a const
|
||||
/// because we don't have support for visiting a const LogicalOperator.
|
||||
void PrettyPrint(const database::GraphDbAccessor &dba,
|
||||
LogicalOperator *plan_root, std::ostream *out);
|
||||
|
||||
/// Overload of `PrettyPrint` which defaults the `std::ostream` to `std::cout`.
|
||||
inline void PrettyPrint(const database::GraphDbAccessor &dba,
|
||||
LogicalOperator *plan_root) {
|
||||
PrettyPrint(dba, plan_root, &std::cout);
|
||||
}
|
||||
|
||||
} // namespace query::plan
|
@ -21,6 +21,7 @@
|
||||
#include "query/plan/cost_estimator.hpp"
|
||||
#include "query/plan/distributed.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "utils/hashing/fnv.hpp"
|
||||
#include "utils/string.hpp"
|
||||
@ -364,285 +365,6 @@ class InteractiveDbAccessor {
|
||||
}
|
||||
};
|
||||
|
||||
class PlanPrinter : public query::plan::HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::Visit;
|
||||
|
||||
explicit PlanPrinter(database::GraphDbAccessor &dba) : dba_(dba) {}
|
||||
|
||||
#define PRE_VISIT(TOp) \
|
||||
bool PreVisit(query::plan::TOp &) override { \
|
||||
WithPrintLn([](auto &out) { out << "* " << #TOp; }); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
PRE_VISIT(CreateNode);
|
||||
PRE_VISIT(CreateExpand);
|
||||
PRE_VISIT(Delete);
|
||||
|
||||
bool PreVisit(query::plan::ScanAll &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAll"
|
||||
<< " (" << op.output_symbol().name() << ")";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ScanAllByLabel &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabel"
|
||||
<< " (" << op.output_symbol().name() << " :"
|
||||
<< dba_.LabelName(op.label()) << ")";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ScanAllByLabelPropertyValue &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyValue"
|
||||
<< " (" << op.output_symbol().name() << " :"
|
||||
<< dba_.LabelName(op.label()) << " {"
|
||||
<< dba_.PropertyName(op.property()) << "})";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ScanAllByLabelPropertyRange &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyRange"
|
||||
<< " (" << op.output_symbol().name() << " :"
|
||||
<< dba_.LabelName(op.label()) << " {"
|
||||
<< dba_.PropertyName(op.property()) << "})";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Expand &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Expand";
|
||||
PrintExpand(out, op);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::ExpandVariable &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ExpandVariable";
|
||||
PrintExpand(out, op);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Produce &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Produce {";
|
||||
utils::PrintIterable(
|
||||
out, op.named_expressions(), ", ",
|
||||
[](auto &out, const auto &nexpr) { out << nexpr->name_; });
|
||||
out << "}";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(SetProperty);
|
||||
PRE_VISIT(SetProperties);
|
||||
PRE_VISIT(SetLabels);
|
||||
PRE_VISIT(RemoveProperty);
|
||||
PRE_VISIT(RemoveLabels);
|
||||
PRE_VISIT(ExpandUniquenessFilter<VertexAccessor>);
|
||||
PRE_VISIT(ExpandUniquenessFilter<EdgeAccessor>);
|
||||
PRE_VISIT(Accumulate);
|
||||
|
||||
bool PreVisit(query::plan::Aggregate &op) override {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Aggregate {";
|
||||
utils::PrintIterable(
|
||||
out, op.aggregations(), ", ",
|
||||
[](auto &out, const auto &aggr) { out << aggr.output_sym.name(); });
|
||||
out << "} {";
|
||||
utils::PrintIterable(
|
||||
out, op.remember(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
PRE_VISIT(Skip);
|
||||
PRE_VISIT(Limit);
|
||||
|
||||
bool PreVisit(query::plan::OrderBy &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* OrderBy {";
|
||||
utils::PrintIterable(
|
||||
out, op.output_symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Merge &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* Merge"; });
|
||||
Branch(*op.merge_match(), "On Match");
|
||||
Branch(*op.merge_create(), "On Create");
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Optional &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* Optional"; });
|
||||
Branch(*op.optional());
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
PRE_VISIT(Unwind);
|
||||
PRE_VISIT(Distinct);
|
||||
|
||||
bool Visit(query::plan::Once &op) override {
|
||||
// Ignore checking Once, it is implicitly at the end.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::CreateIndex &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* CreateIndex"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::AuthHandler &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* AuthHandler"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::CreateStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* CreateStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::DropStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* DropStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::ShowStreams &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* ShowStreams"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::StartStopStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* StartStopStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::StartStopAllStreams &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* StartStopAllStreams"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::plan::TestStream &op) override {
|
||||
WithPrintLn([](auto &out) { out << "* TestStream"; });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::PullRemote &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* PullRemote [" << op.plan_id() << "] {";
|
||||
utils::PrintIterable(
|
||||
out, op.symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
WithPrintLn([](auto &out) { out << "|\\"; });
|
||||
++depth_;
|
||||
WithPrintLn([](auto &out) { out << "* workers"; });
|
||||
--depth_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Synchronize &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* Synchronize";
|
||||
if (op.advance_command()) out << " (ADV CMD)";
|
||||
});
|
||||
if (op.pull_remote()) Branch(*op.pull_remote());
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::Cartesian &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* Cartesian {";
|
||||
utils::PrintIterable(
|
||||
out, op.left_symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << " : ";
|
||||
utils::PrintIterable(
|
||||
out, op.right_symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
Branch(*op.right_op());
|
||||
op.left_op()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(query::plan::PullRemoteOrderBy &op) override {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* PullRemoteOrderBy {";
|
||||
utils::PrintIterable(
|
||||
out, op.symbols(), ", ",
|
||||
[](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
|
||||
WithPrintLn([](auto &out) { out << "|\\"; });
|
||||
++depth_;
|
||||
WithPrintLn([](auto &out) { out << "* workers"; });
|
||||
--depth_;
|
||||
return true;
|
||||
}
|
||||
#undef PRE_VISIT
|
||||
|
||||
private:
|
||||
// Call fun with output stream. The stream is prefixed with amount of spaces
|
||||
// corresponding to the current depth_.
|
||||
template <class TFun>
|
||||
void WithPrintLn(TFun fun) {
|
||||
std::cout << " ";
|
||||
for (int i = 0; i < depth_; ++i) {
|
||||
std::cout << "| ";
|
||||
}
|
||||
fun(std::cout);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Forward this printer to another operator branch by incrementing the depth
|
||||
// and printing the branch name.
|
||||
void Branch(query::plan::LogicalOperator &op,
|
||||
const std::string &branch_name = "") {
|
||||
WithPrintLn([&](auto &out) { out << "|\\ " << branch_name; });
|
||||
++depth_;
|
||||
op.Accept(*this);
|
||||
--depth_;
|
||||
}
|
||||
|
||||
void PrintExpand(std::ostream &out, const query::plan::ExpandCommon &op) {
|
||||
out << " (" << op.input_symbol().name() << ")"
|
||||
<< (op.direction() == query::EdgeAtom::Direction::IN ? "<-" : "-")
|
||||
<< "[" << op.edge_symbol().name() << "]"
|
||||
<< (op.direction() == query::EdgeAtom::Direction::OUT ? "->" : "-")
|
||||
<< "(" << op.node_symbol().name() << ")";
|
||||
}
|
||||
|
||||
int depth_ = 0;
|
||||
database::GraphDbAccessor &dba_;
|
||||
};
|
||||
|
||||
// Shorthand for a vector of pairs (logical_plan, cost).
|
||||
typedef std::vector<
|
||||
std::pair<std::unique_ptr<query::plan::LogicalOperator>, double>>
|
||||
@ -671,13 +393,12 @@ DEFCOMMAND(Top) {
|
||||
std::stringstream ss(args[0]);
|
||||
ss >> n_plans;
|
||||
if (ss.fail() || !ss.eof()) return;
|
||||
PlanPrinter printer(dba);
|
||||
n_plans = std::min(static_cast<int64_t>(plans.size()), n_plans);
|
||||
for (int64_t i = 0; i < n_plans; ++i) {
|
||||
auto &plan_pair = plans[i];
|
||||
std::cout << "---- Plan #" << i << " ---- " << std::endl;
|
||||
std::cout << "cost: " << plan_pair.second << std::endl;
|
||||
plan_pair.first->Accept(printer);
|
||||
query::plan::PrettyPrint(dba, plan_pair.first.get());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
@ -690,8 +411,7 @@ DEFCOMMAND(Show) {
|
||||
const auto &plan = plans[plan_ix].first;
|
||||
auto cost = plans[plan_ix].second;
|
||||
std::cout << "Plan cost: " << cost << std::endl;
|
||||
PlanPrinter printer(dba);
|
||||
plan->Accept(printer);
|
||||
query::plan::PrettyPrint(dba, plan.get());
|
||||
}
|
||||
|
||||
DEFCOMMAND(ShowDistributed) {
|
||||
@ -704,8 +424,7 @@ DEFCOMMAND(ShowDistributed) {
|
||||
auto distributed_plan = MakeDistributedPlan(*plan, symbol_table, plan_id);
|
||||
{
|
||||
std::cout << "---- Master Plan ---- " << std::endl;
|
||||
PlanPrinter printer(dba);
|
||||
distributed_plan.master_plan->Accept(printer);
|
||||
query::plan::PrettyPrint(dba, distributed_plan.master_plan.get());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
for (size_t i = 0; i < distributed_plan.worker_plans.size(); ++i) {
|
||||
@ -713,8 +432,7 @@ DEFCOMMAND(ShowDistributed) {
|
||||
std::shared_ptr<query::plan::LogicalOperator> worker_plan;
|
||||
std::tie(id, worker_plan) = distributed_plan.worker_plans[i];
|
||||
std::cout << "---- Worker Plan #" << id << " ---- " << std::endl;
|
||||
PlanPrinter printer(dba);
|
||||
worker_plan->Accept(printer);
|
||||
query::plan::PrettyPrint(dba, worker_plan.get());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
@ -2330,4 +2330,33 @@ TYPED_TEST(CypherMainVisitorTest, TestStream) {
|
||||
SyntaxException);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, TestExplainRegularQuery) {
|
||||
{
|
||||
TypeParam ast_generator("RETURN n");
|
||||
EXPECT_FALSE(ast_generator.query_->explain_);
|
||||
}
|
||||
{
|
||||
TypeParam ast_generator("EXPLAIN RETURN n");
|
||||
EXPECT_TRUE(ast_generator.query_->explain_);
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, TestExplainExplainQuery) {
|
||||
EXPECT_THROW(TypeParam ast_generator("EXPLAIN EXPLAIN RETURN n"),
|
||||
SyntaxException);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, TestExplainAuthQuery) {
|
||||
TypeParam ast_generator("SHOW ROLES");
|
||||
EXPECT_FALSE(ast_generator.query_->explain_);
|
||||
EXPECT_THROW(TypeParam ast_generator("EXPLAIN SHOW ROLES"), SyntaxException);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, TestExplainStreamQuery) {
|
||||
TypeParam ast_generator("SHOW STREAMS");
|
||||
EXPECT_FALSE(ast_generator.query_->explain_);
|
||||
EXPECT_THROW(TypeParam ast_generator("EXPLAIN SHOW STREAMS"),
|
||||
SyntaxException);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -141,6 +141,8 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor {
|
||||
VISIT(StartStopAllStreams);
|
||||
VISIT(TestStream);
|
||||
|
||||
PRE_VISIT(Explain);
|
||||
|
||||
#undef PRE_VISIT
|
||||
#undef VISIT
|
||||
|
||||
@ -394,6 +396,8 @@ class ExpectAuthHandler : public OpChecker<AuthHandler> {
|
||||
EXPECT_EQ(auth_handler.user(), user_);
|
||||
EXPECT_EQ(auth_handler.role(), role_);
|
||||
EXPECT_EQ(auth_handler.user_or_role(), user_or_role_);
|
||||
// TODO(mtomic): We need to somehow test the password expression.
|
||||
EXPECT_TRUE(password_);
|
||||
EXPECT_TRUE(auth_handler.password());
|
||||
EXPECT_EQ(auth_handler.privileges(), privileges_);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user