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:
Teon Banek 2018-08-23 13:15:15 +02:00
parent 6615a9de53
commit 50c75c56a4
21 changed files with 575 additions and 309 deletions

View File

@ -10,6 +10,7 @@
* [Enterprise Ed.] Authentication and authorization support.
* [Enterprise Ed.] Kafka integration.
* Add `EXPLAIN` clause to openCypher
## v0.12.0

View File

@ -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})
```

View File

@ -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

View File

@ -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 {

View File

@ -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_ =

View File

@ -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:

View File

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

View File

@ -132,6 +132,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
return expression;
}
/**
* @return Query*
*/
antlrcpp::Any visitExplainQuery(MemgraphCypher::ExplainQueryContext *ctx) override;
/**
* @return Query*
*/

View File

@ -48,8 +48,11 @@ symbolicName : UnescapedSymbolicName
query : regularQuery
| authQuery
| streamQuery
| explainQuery
;
explainQuery : EXPLAIN regularQuery ;
authQuery : createRole
| dropRole
| showRoles

View File

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

View File

@ -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,

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -98,6 +98,12 @@ auto MakeLogicalPlan(TPlanningContext &context, const Parameters &parameters,
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);
}

View File

@ -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

View 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

View 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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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_);
}