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.] Authentication and authorization support.
|
||||||
* [Enterprise Ed.] Kafka integration.
|
* [Enterprise Ed.] Kafka integration.
|
||||||
|
* Add `EXPLAIN` clause to openCypher
|
||||||
|
|
||||||
## v0.12.0
|
## v0.12.0
|
||||||
|
|
||||||
|
@ -158,3 +158,41 @@ to true is matched:
|
|||||||
MATCH (n)
|
MATCH (n)
|
||||||
RETURN CASE WHEN n.height < 30 THEN "short" WHEN n.height > 300 THEN "tall" END
|
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/distributed.cpp
|
||||||
query/plan/operator.cpp
|
query/plan/operator.cpp
|
||||||
query/plan/preprocess.cpp
|
query/plan/preprocess.cpp
|
||||||
|
query/plan/pretty_print.cpp
|
||||||
query/plan/rule_based_planner.cpp
|
query/plan/rule_based_planner.cpp
|
||||||
query/plan/variable_start_planner.cpp
|
query/plan/variable_start_planner.cpp
|
||||||
query/repl.cpp
|
query/repl.cpp
|
||||||
|
@ -155,6 +155,7 @@ struct CypherUnion {
|
|||||||
struct Query {
|
struct Query {
|
||||||
singleQuery @0 :Tree;
|
singleQuery @0 :Tree;
|
||||||
cypherUnions @1 :List(Tree);
|
cypherUnions @1 :List(Tree);
|
||||||
|
explain @2 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BinaryOperator {
|
struct BinaryOperator {
|
||||||
@ -431,7 +432,7 @@ struct AuthQuery {
|
|||||||
role @2 :Text;
|
role @2 :Text;
|
||||||
userOrRole @3 :Text;
|
userOrRole @3 :Text;
|
||||||
password @4 :Tree;
|
password @4 :Tree;
|
||||||
privileges @5 :List(Privilege);
|
privileges @5 :List(Privilege);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CreateStream {
|
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) {
|
void Query::Save(capnp::Query::Builder *builder, std::vector<int> *saved_uids) {
|
||||||
|
builder->setExplain(explain_);
|
||||||
if (single_query_) {
|
if (single_query_) {
|
||||||
auto sq_builder = builder->initSingleQuery();
|
auto sq_builder = builder->initSingleQuery();
|
||||||
single_query_->Save(&sq_builder, saved_uids);
|
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) {
|
std::vector<int> *loaded_uids) {
|
||||||
Tree::Load(reader, storage, loaded_uids);
|
Tree::Load(reader, storage, loaded_uids);
|
||||||
auto query_reader = reader.getQuery();
|
auto query_reader = reader.getQuery();
|
||||||
|
explain_ = query_reader.getExplain();
|
||||||
if (query_reader.hasSingleQuery()) {
|
if (query_reader.hasSingleQuery()) {
|
||||||
const auto sq_reader = query_reader.getSingleQuery();
|
const auto sq_reader = query_reader.getSingleQuery();
|
||||||
single_query_ =
|
single_query_ =
|
||||||
|
@ -1728,6 +1728,7 @@ class Query : public Tree {
|
|||||||
for (auto *cypher_union : cypher_unions_) {
|
for (auto *cypher_union : cypher_unions_) {
|
||||||
query->cypher_unions_.push_back(cypher_union->Clone(storage));
|
query->cypher_unions_.push_back(cypher_union->Clone(storage));
|
||||||
}
|
}
|
||||||
|
query->explain_ = explain_;
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1736,7 +1737,11 @@ class Query : public Tree {
|
|||||||
void Save(capnp::Tree::Builder *builder,
|
void Save(capnp::Tree::Builder *builder,
|
||||||
std::vector<int> *saved_uids) override;
|
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;
|
SingleQuery *single_query_ = nullptr;
|
||||||
|
/// Contains remaining queries that should form a union with `single_query_`
|
||||||
std::vector<CypherUnion *> cypher_unions_;
|
std::vector<CypherUnion *> cypher_unions_;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -25,6 +25,14 @@ namespace query::frontend {
|
|||||||
|
|
||||||
const std::string CypherMainVisitor::kAnonPrefix = "anon";
|
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(
|
antlrcpp::Any CypherMainVisitor::visitAuthQuery(
|
||||||
MemgraphCypher::AuthQueryContext *ctx) {
|
MemgraphCypher::AuthQueryContext *ctx) {
|
||||||
query_ = storage_.query();
|
query_ = storage_.query();
|
||||||
|
@ -132,6 +132,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
|||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query*
|
||||||
|
*/
|
||||||
|
antlrcpp::Any visitExplainQuery(MemgraphCypher::ExplainQueryContext *ctx) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Query*
|
* @return Query*
|
||||||
*/
|
*/
|
||||||
|
@ -48,8 +48,11 @@ symbolicName : UnescapedSymbolicName
|
|||||||
query : regularQuery
|
query : regularQuery
|
||||||
| authQuery
|
| authQuery
|
||||||
| streamQuery
|
| streamQuery
|
||||||
|
| explainQuery
|
||||||
;
|
;
|
||||||
|
|
||||||
|
explainQuery : EXPLAIN regularQuery ;
|
||||||
|
|
||||||
authQuery : createRole
|
authQuery : createRole
|
||||||
| dropRole
|
| dropRole
|
||||||
| showRoles
|
| showRoles
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* and bitsets) if needed.
|
* and bitsets) if needed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
lexer grammar MemgraphCypherLexer ;
|
lexer grammar MemgraphCypherLexer ;
|
||||||
|
|
||||||
import CypherLexer ;
|
import CypherLexer ;
|
||||||
|
|
||||||
@ -17,6 +17,7 @@ BATCHES : B A T C H E S ;
|
|||||||
DATA : D A T A ;
|
DATA : D A T A ;
|
||||||
DENY : D E N Y ;
|
DENY : D E N Y ;
|
||||||
DROP : D R O P ;
|
DROP : D R O P ;
|
||||||
|
EXPLAIN : E X P L A I N ;
|
||||||
FOR : F O R ;
|
FOR : F O R ;
|
||||||
FROM : F R O M ;
|
FROM : F R O M ;
|
||||||
GRANT : G R A N T ;
|
GRANT : G R A N T ;
|
||||||
|
@ -168,7 +168,7 @@ class Interpreter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an Results object for the parameters. The resulting object
|
* 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,
|
Results operator()(const std::string &query,
|
||||||
database::GraphDbAccessor &db_accessor,
|
database::GraphDbAccessor &db_accessor,
|
||||||
|
@ -95,6 +95,16 @@ class IndependentSubtreeFinder : public HierarchicalLogicalOperatorVisitor {
|
|||||||
bool Visit(StartStopAllStreams &) override { return true; }
|
bool Visit(StartStopAllStreams &) override { return true; }
|
||||||
bool Visit(TestStream &) 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 {
|
bool PreVisit(ScanAll &scan) override {
|
||||||
prev_ops_.push_back(&scan);
|
prev_ops_.push_back(&scan);
|
||||||
return true;
|
return true;
|
||||||
@ -1041,6 +1051,26 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
|
|||||||
return true;
|
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.
|
// 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
|
// Therefore, the earliest (deepest in the plan tree) encountered Skip will
|
||||||
// break the plan in 2 parts.
|
// break the plan in 2 parts.
|
||||||
@ -1140,9 +1170,10 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor {
|
|||||||
// Shallow copy Distinct
|
// Shallow copy Distinct
|
||||||
auto pull_id = AddWorkerPlan(std::make_shared<Distinct>(distinct));
|
auto pull_id = AddWorkerPlan(std::make_shared<Distinct>(distinct));
|
||||||
auto input = distinct.input();
|
auto input = distinct.input();
|
||||||
Split(distinct, std::make_shared<PullRemote>(
|
Split(distinct,
|
||||||
input, pull_id, input->OutputSymbols(
|
std::make_shared<PullRemote>(
|
||||||
distributed_plan_.symbol_table)));
|
input, pull_id,
|
||||||
|
input->OutputSymbols(distributed_plan_.symbol_table)));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "query/frontend/semantic/symbol_table.hpp"
|
#include "query/frontend/semantic/symbol_table.hpp"
|
||||||
#include "query/interpret/eval.hpp"
|
#include "query/interpret/eval.hpp"
|
||||||
#include "query/path.hpp"
|
#include "query/path.hpp"
|
||||||
|
#include "query/plan/pretty_print.hpp"
|
||||||
#include "utils/algorithm.hpp"
|
#include "utils/algorithm.hpp"
|
||||||
#include "utils/exceptions.hpp"
|
#include "utils/exceptions.hpp"
|
||||||
#include "utils/hashing/fnv.hpp"
|
#include "utils/hashing/fnv.hpp"
|
||||||
@ -4613,4 +4614,52 @@ std::unique_ptr<Cursor> TestStream::MakeCursor(
|
|||||||
return std::make_unique<TestStreamCursor>(*this, db);
|
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
|
} // namespace query::plan
|
||||||
|
@ -109,6 +109,7 @@ class ShowStreams;
|
|||||||
class StartStopStream;
|
class StartStopStream;
|
||||||
class StartStopAllStreams;
|
class StartStopAllStreams;
|
||||||
class TestStream;
|
class TestStream;
|
||||||
|
class Explain;
|
||||||
|
|
||||||
using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
|
using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
|
||||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||||
@ -118,7 +119,7 @@ using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
|
|||||||
ExpandUniquenessFilter<VertexAccessor>,
|
ExpandUniquenessFilter<VertexAccessor>,
|
||||||
ExpandUniquenessFilter<EdgeAccessor>, Accumulate, Aggregate, Skip, Limit,
|
ExpandUniquenessFilter<EdgeAccessor>, Accumulate, Aggregate, Skip, Limit,
|
||||||
OrderBy, Merge, Optional, Unwind, Distinct, Union, PullRemote, Synchronize,
|
OrderBy, Merge, Optional, Unwind, Distinct, Union, PullRemote, Synchronize,
|
||||||
Cartesian, PullRemoteOrderBy>;
|
Cartesian, PullRemoteOrderBy, Explain>;
|
||||||
|
|
||||||
using LogicalOperatorLeafVisitor =
|
using LogicalOperatorLeafVisitor =
|
||||||
::utils::LeafVisitor<Once, CreateIndex, AuthHandler, CreateStream,
|
::utils::LeafVisitor<Once, CreateIndex, AuthHandler, CreateStream,
|
||||||
@ -2661,6 +2662,32 @@ in the db.")
|
|||||||
#>cpp TestStream() {} cpp<#)
|
#>cpp TestStream() {} cpp<#)
|
||||||
(:serialize :capnp))
|
(: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) ;; plan
|
||||||
(lcp:pop-namespace) ;; query
|
(lcp:pop-namespace) ;; query
|
||||||
|
@ -98,6 +98,12 @@ auto MakeLogicalPlan(TPlanningContext &context, const Parameters ¶meters,
|
|||||||
prev_op, prev_op->OutputSymbols(context.symbol_table));
|
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);
|
return std::make_pair(std::move(last_op), total_cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,29 +477,28 @@ std::vector<SingleQueryPart> CollectSingleQueryParts(
|
|||||||
return query_parts;
|
return query_parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryParts CollectQueryParts(SymbolTable &symbol_table,
|
QueryParts CollectQueryParts(SymbolTable &symbol_table, AstStorage &storage) {
|
||||||
AstStorage &storage) {
|
auto *query = storage.query();
|
||||||
auto query = storage.query();
|
|
||||||
std::vector<QueryPart> query_parts;
|
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;
|
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_) {
|
for (auto *cypher_union : query->cypher_unions_) {
|
||||||
if (cypher_union->distinct_) {
|
if (cypher_union->distinct_) {
|
||||||
distinct = true;
|
distinct = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto *single_query = cypher_union->single_query_) {
|
auto *single_query = cypher_union->single_query_;
|
||||||
query_parts.push_back(QueryPart{
|
CHECK(single_query) << "Expected UNION to have a query";
|
||||||
CollectSingleQueryParts(symbol_table, storage, single_query),
|
query_parts.push_back(
|
||||||
cypher_union});
|
QueryPart{CollectSingleQueryParts(symbol_table, storage, single_query),
|
||||||
}
|
cypher_union});
|
||||||
}
|
}
|
||||||
return QueryParts{query_parts, distinct};
|
return QueryParts{query_parts, distinct};
|
||||||
};
|
}
|
||||||
|
|
||||||
} // namespace query::plan
|
} // 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/cost_estimator.hpp"
|
||||||
#include "query/plan/distributed.hpp"
|
#include "query/plan/distributed.hpp"
|
||||||
#include "query/plan/planner.hpp"
|
#include "query/plan/planner.hpp"
|
||||||
|
#include "query/plan/pretty_print.hpp"
|
||||||
#include "query/typed_value.hpp"
|
#include "query/typed_value.hpp"
|
||||||
#include "utils/hashing/fnv.hpp"
|
#include "utils/hashing/fnv.hpp"
|
||||||
#include "utils/string.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).
|
// Shorthand for a vector of pairs (logical_plan, cost).
|
||||||
typedef std::vector<
|
typedef std::vector<
|
||||||
std::pair<std::unique_ptr<query::plan::LogicalOperator>, double>>
|
std::pair<std::unique_ptr<query::plan::LogicalOperator>, double>>
|
||||||
@ -671,13 +393,12 @@ DEFCOMMAND(Top) {
|
|||||||
std::stringstream ss(args[0]);
|
std::stringstream ss(args[0]);
|
||||||
ss >> n_plans;
|
ss >> n_plans;
|
||||||
if (ss.fail() || !ss.eof()) return;
|
if (ss.fail() || !ss.eof()) return;
|
||||||
PlanPrinter printer(dba);
|
|
||||||
n_plans = std::min(static_cast<int64_t>(plans.size()), n_plans);
|
n_plans = std::min(static_cast<int64_t>(plans.size()), n_plans);
|
||||||
for (int64_t i = 0; i < n_plans; ++i) {
|
for (int64_t i = 0; i < n_plans; ++i) {
|
||||||
auto &plan_pair = plans[i];
|
auto &plan_pair = plans[i];
|
||||||
std::cout << "---- Plan #" << i << " ---- " << std::endl;
|
std::cout << "---- Plan #" << i << " ---- " << std::endl;
|
||||||
std::cout << "cost: " << plan_pair.second << 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;
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -690,8 +411,7 @@ DEFCOMMAND(Show) {
|
|||||||
const auto &plan = plans[plan_ix].first;
|
const auto &plan = plans[plan_ix].first;
|
||||||
auto cost = plans[plan_ix].second;
|
auto cost = plans[plan_ix].second;
|
||||||
std::cout << "Plan cost: " << cost << std::endl;
|
std::cout << "Plan cost: " << cost << std::endl;
|
||||||
PlanPrinter printer(dba);
|
query::plan::PrettyPrint(dba, plan.get());
|
||||||
plan->Accept(printer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFCOMMAND(ShowDistributed) {
|
DEFCOMMAND(ShowDistributed) {
|
||||||
@ -704,8 +424,7 @@ DEFCOMMAND(ShowDistributed) {
|
|||||||
auto distributed_plan = MakeDistributedPlan(*plan, symbol_table, plan_id);
|
auto distributed_plan = MakeDistributedPlan(*plan, symbol_table, plan_id);
|
||||||
{
|
{
|
||||||
std::cout << "---- Master Plan ---- " << std::endl;
|
std::cout << "---- Master Plan ---- " << std::endl;
|
||||||
PlanPrinter printer(dba);
|
query::plan::PrettyPrint(dba, distributed_plan.master_plan.get());
|
||||||
distributed_plan.master_plan->Accept(printer);
|
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < distributed_plan.worker_plans.size(); ++i) {
|
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::shared_ptr<query::plan::LogicalOperator> worker_plan;
|
||||||
std::tie(id, worker_plan) = distributed_plan.worker_plans[i];
|
std::tie(id, worker_plan) = distributed_plan.worker_plans[i];
|
||||||
std::cout << "---- Worker Plan #" << id << " ---- " << std::endl;
|
std::cout << "---- Worker Plan #" << id << " ---- " << std::endl;
|
||||||
PlanPrinter printer(dba);
|
query::plan::PrettyPrint(dba, worker_plan.get());
|
||||||
worker_plan->Accept(printer);
|
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2330,4 +2330,33 @@ TYPED_TEST(CypherMainVisitorTest, TestStream) {
|
|||||||
SyntaxException);
|
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
|
} // namespace
|
||||||
|
@ -141,6 +141,8 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor {
|
|||||||
VISIT(StartStopAllStreams);
|
VISIT(StartStopAllStreams);
|
||||||
VISIT(TestStream);
|
VISIT(TestStream);
|
||||||
|
|
||||||
|
PRE_VISIT(Explain);
|
||||||
|
|
||||||
#undef PRE_VISIT
|
#undef PRE_VISIT
|
||||||
#undef VISIT
|
#undef VISIT
|
||||||
|
|
||||||
@ -394,6 +396,8 @@ class ExpectAuthHandler : public OpChecker<AuthHandler> {
|
|||||||
EXPECT_EQ(auth_handler.user(), user_);
|
EXPECT_EQ(auth_handler.user(), user_);
|
||||||
EXPECT_EQ(auth_handler.role(), role_);
|
EXPECT_EQ(auth_handler.role(), role_);
|
||||||
EXPECT_EQ(auth_handler.user_or_role(), user_or_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_TRUE(auth_handler.password());
|
||||||
EXPECT_EQ(auth_handler.privileges(), privileges_);
|
EXPECT_EQ(auth_handler.privileges(), privileges_);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user