Include JSON serialized logical plan in summary for EXPLAIN
Reviewers: teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1831
This commit is contained in:
parent
a871212675
commit
7542f5b0ba
@ -27,8 +27,8 @@ set(mg_single_node_sources
|
||||
glue/auth.cpp
|
||||
glue/communication.cpp
|
||||
query/common.cpp
|
||||
query/frontend/ast/pretty_print.cpp
|
||||
query/frontend/ast/cypher_main_visitor.cpp
|
||||
query/frontend/ast/pretty_print.cpp
|
||||
query/frontend/semantic/required_privileges.cpp
|
||||
query/frontend/semantic/symbol_generator.cpp
|
||||
query/frontend/stripped.cpp
|
||||
@ -263,6 +263,7 @@ set(mg_single_node_ha_sources
|
||||
raft/raft_server.cpp
|
||||
query/common.cpp
|
||||
query/frontend/ast/cypher_main_visitor.cpp
|
||||
query/frontend/ast/pretty_print.cpp
|
||||
query/frontend/semantic/required_privileges.cpp
|
||||
query/frontend/semantic/symbol_generator.cpp
|
||||
query/frontend/stripped.cpp
|
||||
|
@ -157,4 +157,10 @@ void DistributedInterpreter::PrettyPrintPlan(
|
||||
plan::DistributedPrettyPrint(dba, plan_root, out);
|
||||
}
|
||||
|
||||
std::string DistributedInterpreter::PlanToJson(
|
||||
const database::GraphDbAccessor &dba,
|
||||
const plan::LogicalOperator *plan_root) {
|
||||
return plan::DistributedPlanToJson(dba, plan_root).dump();
|
||||
}
|
||||
|
||||
} // namespace query
|
||||
|
@ -28,6 +28,9 @@ class DistributedInterpreter final : public Interpreter {
|
||||
void PrettyPrintPlan(const database::GraphDbAccessor &,
|
||||
const plan::LogicalOperator *, std::ostream *) override;
|
||||
|
||||
std::string PlanToJson(const database::GraphDbAccessor &,
|
||||
const plan::LogicalOperator *) override;
|
||||
|
||||
std::atomic<int64_t> next_plan_id_{0};
|
||||
distributed::PlanDispatcher *plan_dispatcher_{nullptr};
|
||||
};
|
||||
|
@ -61,6 +61,11 @@ void Interpreter::PrettyPrintPlan(const database::GraphDbAccessor &dba,
|
||||
plan::PrettyPrint(dba, plan_root, out);
|
||||
}
|
||||
|
||||
std::string Interpreter::PlanToJson(const database::GraphDbAccessor &dba,
|
||||
const plan::LogicalOperator *plan_root) {
|
||||
return plan::PlanToJson(dba, plan_root).dump();
|
||||
}
|
||||
|
||||
struct Callback {
|
||||
std::vector<std::string> header;
|
||||
std::function<std::vector<std::vector<TypedValue>>()> fn;
|
||||
@ -687,6 +692,8 @@ Interpreter::Results Interpreter::operator()(
|
||||
printed_plan_rows.push_back(std::vector<TypedValue>{row});
|
||||
}
|
||||
|
||||
summary["explain"] = PlanToJson(db_accessor, &cypher_query_plan->plan());
|
||||
|
||||
SymbolTable symbol_table;
|
||||
auto query_plan_symbol = symbol_table.CreateSymbol("QUERY PLAN", false);
|
||||
std::vector<Symbol> output_symbols{query_plan_symbol};
|
||||
|
@ -223,6 +223,9 @@ class Interpreter {
|
||||
virtual void PrettyPrintPlan(const database::GraphDbAccessor &,
|
||||
const plan::LogicalOperator *, std::ostream *);
|
||||
|
||||
virtual std::string PlanToJson(const database::GraphDbAccessor &,
|
||||
const plan::LogicalOperator *);
|
||||
|
||||
private:
|
||||
ConcurrentMap<HashType, CachedQuery> ast_cache_;
|
||||
PlanCacheT plan_cache_;
|
||||
|
@ -87,7 +87,6 @@ bool DistributedPlanPrinter::PreVisit(DistributedCreateExpand &op) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#undef PRE_VISIT
|
||||
|
||||
bool DistributedPlanPrinter::PreVisit(query::plan::Synchronize &op) {
|
||||
@ -108,4 +107,143 @@ void DistributedPrettyPrint(const database::GraphDbAccessor &dba,
|
||||
const_cast<LogicalOperator *>(plan_root)->Accept(printer);
|
||||
}
|
||||
|
||||
nlohmann::json DistributedPlanToJson(const database::GraphDbAccessor &dba,
|
||||
const LogicalOperator *plan_root) {
|
||||
impl::DistributedPlanToJsonVisitor visitor(&dba);
|
||||
const_cast<LogicalOperator *>(plan_root)->Accept(visitor);
|
||||
return visitor.output();
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DistributedPlanToJsonVisitor implementation
|
||||
//
|
||||
// The JSON formatted plan is consumed (or will be) by Memgraph Lab, and
|
||||
// therefore should not be changed before synchronizing with whoever is
|
||||
// maintaining Memgraph Lab. Hopefully, one day integration tests will exist and
|
||||
// there will be no need to be super careful.
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
bool DistributedPlanToJsonVisitor::PreVisit(DistributedExpand &op) {
|
||||
json self;
|
||||
self["name"] = "DistributedExpand";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_symbol"] = ToJson(op.common_.node_symbol);
|
||||
self["edge_symbol"] = ToJson(op.common_.edge_symbol);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *dba_);
|
||||
self["direction"] = ToString(op.common_.direction);
|
||||
self["existing_node"] = op.common_.existing_node;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DistributedPlanToJsonVisitor::PreVisit(DistributedExpandBfs &op) {
|
||||
json self;
|
||||
self["name"] = "DistributedExpandBfs";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_symbol"] = ToJson(op.common_.node_symbol);
|
||||
self["edge_symbol"] = ToJson(op.common_.edge_symbol);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *dba_);
|
||||
self["direction"] = ToString(op.common_.direction);
|
||||
self["lower_bound"] = op.lower_bound_ ? ToJson(op.lower_bound_) : json();
|
||||
self["upper_bound"] = op.upper_bound_ ? ToJson(op.upper_bound_) : json();
|
||||
self["existing_node"] = op.common_.existing_node;
|
||||
|
||||
self["filter_lambda"] = op.filter_lambda_.expression
|
||||
? ToJson(op.filter_lambda_.expression)
|
||||
: json();
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DistributedPlanToJsonVisitor::PreVisit(PullRemote &op) {
|
||||
json self;
|
||||
self["name"] = "PullRemote";
|
||||
self["symbols"] = ToJson(op.symbols_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DistributedPlanToJsonVisitor::PreVisit(PullRemoteOrderBy &op) {
|
||||
json self;
|
||||
self["name"] = "PullRemoteOrderBy";
|
||||
|
||||
for (auto i = 0; i < op.order_by_.size(); ++i) {
|
||||
json json;
|
||||
json["ordering"] = ToString(op.compare_.ordering_[i]);
|
||||
json["expression"] = ToJson(op.order_by_[i]);
|
||||
self["order_by"].push_back(json);
|
||||
}
|
||||
self["symbols"] = ToJson(op.symbols_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DistributedPlanToJsonVisitor::PreVisit(DistributedCreateNode &op) {
|
||||
json self;
|
||||
self["name"] = "DistributedCreateNode";
|
||||
self["node_info"] = ToJson(op.node_info_, *dba_);
|
||||
self["on_random_worker"] = op.on_random_worker_;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DistributedPlanToJsonVisitor::PreVisit(DistributedCreateExpand &op) {
|
||||
json self;
|
||||
self["name"] = "DistributedCreateExpand";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_info"] = ToJson(op.node_info_, *dba_);
|
||||
self["edge_info"] = ToJson(op.edge_info_, *dba_);
|
||||
self["existing_node"] = op.existing_node_;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DistributedPlanToJsonVisitor::PreVisit(Synchronize &op) {
|
||||
json self;
|
||||
self["name"] = "Synchronize";
|
||||
self["advance_command"] = op.advance_command_;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
if (op.pull_remote_) {
|
||||
op.pull_remote_->Accept(*this);
|
||||
self["pull_remote"] = PopOutput();
|
||||
} else {
|
||||
self["pull_remote"] = json();
|
||||
}
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace impl
|
||||
|
||||
} // namespace query::plan
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "query/plan/distributed_ops.hpp"
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
namespace query::plan {
|
||||
|
||||
void DistributedPrettyPrint(const database::GraphDbAccessor &dba,
|
||||
@ -15,6 +17,9 @@ inline void DistributedPrettyPrint(const database::GraphDbAccessor &dba,
|
||||
DistributedPrettyPrint(dba, plan_root, &std::cout);
|
||||
}
|
||||
|
||||
nlohmann::json DistributedPlanToJson(const database::GraphDbAccessor &dba,
|
||||
const LogicalOperator *plan_root);
|
||||
|
||||
class DistributedPlanPrinter : public PlanPrinter,
|
||||
public DistributedOperatorVisitor {
|
||||
public:
|
||||
@ -37,4 +42,30 @@ class DistributedPlanPrinter : public PlanPrinter,
|
||||
bool PreVisit(Synchronize &) override;
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
class DistributedPlanToJsonVisitor : public PlanToJsonVisitor,
|
||||
public DistributedOperatorVisitor {
|
||||
public:
|
||||
using DistributedOperatorVisitor::PostVisit;
|
||||
using DistributedOperatorVisitor::PreVisit;
|
||||
using DistributedOperatorVisitor::Visit;
|
||||
using PlanToJsonVisitor::PlanToJsonVisitor;
|
||||
using PlanToJsonVisitor::PostVisit;
|
||||
using PlanToJsonVisitor::PreVisit;
|
||||
using PlanToJsonVisitor::Visit;
|
||||
|
||||
bool PreVisit(DistributedExpand &) override;
|
||||
bool PreVisit(DistributedExpandBfs &) override;
|
||||
|
||||
bool PreVisit(PullRemote &) override;
|
||||
bool PreVisit(PullRemoteOrderBy &) override;
|
||||
|
||||
bool PreVisit(DistributedCreateNode &) override;
|
||||
bool PreVisit(DistributedCreateExpand &) override;
|
||||
bool PreVisit(Synchronize &) override;
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
} // namespace query::plan
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
|
||||
#include "database/graph_db_accessor.hpp"
|
||||
#include "query/frontend/ast/pretty_print.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace query::plan {
|
||||
|
||||
@ -228,4 +230,582 @@ void PrettyPrint(const database::GraphDbAccessor &dba,
|
||||
const_cast<LogicalOperator *>(plan_root)->Accept(printer);
|
||||
}
|
||||
|
||||
nlohmann::json PlanToJson(const database::GraphDbAccessor &dba,
|
||||
const LogicalOperator *plan_root) {
|
||||
impl::PlanToJsonVisitor visitor(&dba);
|
||||
// FIXME(mtomic): We should make visitors that take const arguments.
|
||||
const_cast<LogicalOperator *>(plan_root)->Accept(visitor);
|
||||
return visitor.output();
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PlanToJsonVisitor implementation
|
||||
//
|
||||
// The JSON formatted plan is consumed (or will be) by Memgraph Lab, and
|
||||
// therefore should not be changed before synchronizing with whoever is
|
||||
// maintaining Memgraph Lab. Hopefully, one day integration tests will exist and
|
||||
// there will be no need to be super careful.
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
//////////////////////////// HELPER FUNCTIONS /////////////////////////////////
|
||||
// TODO: It would be nice to have enum->string functions auto-generated.
|
||||
std::string ToString(EdgeAtom::Direction dir) {
|
||||
switch (dir) {
|
||||
case EdgeAtom::Direction::BOTH:
|
||||
return "both";
|
||||
case EdgeAtom::Direction::IN:
|
||||
return "in";
|
||||
case EdgeAtom::Direction::OUT:
|
||||
return "out";
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString(EdgeAtom::Type type) {
|
||||
switch (type) {
|
||||
case EdgeAtom::Type::BREADTH_FIRST:
|
||||
return "bfs";
|
||||
case EdgeAtom::Type::DEPTH_FIRST:
|
||||
return "dfs";
|
||||
case EdgeAtom::Type::WEIGHTED_SHORTEST_PATH:
|
||||
return "wsp";
|
||||
case EdgeAtom::Type::SINGLE:
|
||||
return "single";
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString(Ordering ord) {
|
||||
switch (ord) {
|
||||
case Ordering::ASC:
|
||||
return "asc";
|
||||
case Ordering::DESC:
|
||||
return "desc";
|
||||
}
|
||||
}
|
||||
|
||||
json ToJson(Expression *expression) {
|
||||
std::stringstream sstr;
|
||||
PrintExpression(expression, &sstr);
|
||||
return sstr.str();
|
||||
}
|
||||
|
||||
json ToJson(const utils::Bound<Expression *> &bound) {
|
||||
json json;
|
||||
switch (bound.type()) {
|
||||
case utils::BoundType::INCLUSIVE:
|
||||
json["type"] = "inclusive";
|
||||
break;
|
||||
case utils::BoundType::EXCLUSIVE:
|
||||
json["type"] = "exclusive";
|
||||
break;
|
||||
}
|
||||
|
||||
json["value"] = ToJson(bound.value());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
json ToJson(const Symbol &symbol) { return symbol.name(); }
|
||||
|
||||
json ToJson(storage::EdgeType edge_type, const database::GraphDbAccessor &dba) {
|
||||
return dba.EdgeTypeName(edge_type);
|
||||
}
|
||||
|
||||
json ToJson(storage::Label label, const database::GraphDbAccessor &dba) {
|
||||
return dba.LabelName(label);
|
||||
}
|
||||
|
||||
json ToJson(storage::Property property, const database::GraphDbAccessor &dba) {
|
||||
return dba.PropertyName(property);
|
||||
}
|
||||
|
||||
json ToJson(NamedExpression *nexpr) {
|
||||
json json;
|
||||
json["expression"] = ToJson(nexpr->expression_);
|
||||
json["name"] = nexpr->name_;
|
||||
return json;
|
||||
}
|
||||
|
||||
json ToJson(
|
||||
const std::vector<std::pair<storage::Property, Expression *>> &properties,
|
||||
const database::GraphDbAccessor &dba) {
|
||||
json json;
|
||||
for (const auto &prop_pair : properties) {
|
||||
json.emplace(ToJson(prop_pair.first, dba), ToJson(prop_pair.second));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
json ToJson(const NodeCreationInfo &node_info,
|
||||
const database::GraphDbAccessor &dba) {
|
||||
json self;
|
||||
self["symbol"] = ToJson(node_info.symbol);
|
||||
self["labels"] = ToJson(node_info.labels, dba);
|
||||
self["properties"] = ToJson(node_info.properties, dba);
|
||||
return self;
|
||||
}
|
||||
|
||||
json ToJson(const EdgeCreationInfo &edge_info,
|
||||
const database::GraphDbAccessor &dba) {
|
||||
json self;
|
||||
self["symbol"] = ToJson(edge_info.symbol);
|
||||
self["properties"] = ToJson(edge_info.properties, dba);
|
||||
self["edge_type"] = ToJson(edge_info.edge_type, dba);
|
||||
self["direction"] = ToString(edge_info.direction);
|
||||
return self;
|
||||
}
|
||||
|
||||
json ToJson(const Aggregate::Element &elem) {
|
||||
json json;
|
||||
json["value"] = ToJson(elem.value);
|
||||
if (elem.key) {
|
||||
json["key"] = ToJson(elem.key);
|
||||
}
|
||||
json["op"] = utils::ToLowerCase(Aggregation::OpToString(elem.op));
|
||||
json["output_symbol"] = ToJson(elem.output_sym);
|
||||
return json;
|
||||
}
|
||||
////////////////////////// END HELPER FUNCTIONS ////////////////////////////////
|
||||
|
||||
bool PlanToJsonVisitor::Visit(Once &) {
|
||||
json self;
|
||||
self["name"] = "Once";
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAll &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAll";
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllByLabel &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllByLabel";
|
||||
self["label"] = ToJson(op.label_, *dba_);
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllByLabelPropertyRange &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllByLabelPropertyRange";
|
||||
self["label"] = ToJson(op.label_, *dba_);
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["lower_bound"] = op.lower_bound_ ? ToJson(*op.lower_bound_) : json();
|
||||
self["upper_bound"] = op.upper_bound_ ? ToJson(*op.upper_bound_) : json();
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllByLabelPropertyValue &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllByLabelPropertyValue";
|
||||
self["label"] = ToJson(op.label_, *dba_);
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["expression"] = ToJson(op.expression_);
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(CreateNode &op) {
|
||||
json self;
|
||||
self["name"] = "CreateNode";
|
||||
self["node_info"] = ToJson(op.node_info_, *dba_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(CreateExpand &op) {
|
||||
json self;
|
||||
self["name"] = "CreateExpand";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_info"] = ToJson(op.node_info_, *dba_);
|
||||
self["edge_info"] = ToJson(op.edge_info_, *dba_);
|
||||
self["existing_node"] = op.existing_node_;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Expand &op) {
|
||||
json self;
|
||||
self["name"] = "Expand";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_symbol"] = ToJson(op.common_.node_symbol);
|
||||
self["edge_symbol"] = ToJson(op.common_.edge_symbol);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *dba_);
|
||||
self["direction"] = ToString(op.common_.direction);
|
||||
self["existing_node"] = op.common_.existing_node;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(ExpandVariable &op) {
|
||||
json self;
|
||||
self["name"] = "ExpandVariable";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_symbol"] = ToJson(op.common_.node_symbol);
|
||||
self["edge_symbol"] = ToJson(op.common_.edge_symbol);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *dba_);
|
||||
self["direction"] = ToString(op.common_.direction);
|
||||
self["type"] = ToString(op.type_);
|
||||
self["is_reverse"] = op.is_reverse_;
|
||||
self["lower_bound"] = op.lower_bound_ ? ToJson(op.lower_bound_) : json();
|
||||
self["upper_bound"] = op.upper_bound_ ? ToJson(op.upper_bound_) : json();
|
||||
self["existing_node"] = op.common_.existing_node;
|
||||
|
||||
self["filter_lambda"] = op.filter_lambda_.expression
|
||||
? ToJson(op.filter_lambda_.expression)
|
||||
: json();
|
||||
|
||||
if (op.type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) {
|
||||
self["weight_lambda"] = ToJson(op.weight_lambda_->expression);
|
||||
self["total_weight_symbol"] = ToJson(*op.total_weight_);
|
||||
}
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(ConstructNamedPath &op) {
|
||||
json self;
|
||||
self["name"] = "ConstructNamedPath";
|
||||
self["path_symbol"] = ToJson(op.path_symbol_);
|
||||
self["path_elements"] = ToJson(op.path_elements_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Filter &op) {
|
||||
json self;
|
||||
self["name"] = "Filter";
|
||||
self["expression"] = ToJson(op.expression_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Produce &op) {
|
||||
json self;
|
||||
self["name"] = "Produce";
|
||||
self["named_expressions"] = ToJson(op.named_expressions_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Delete &op) {
|
||||
json self;
|
||||
self["name"] = "Delete";
|
||||
self["expressions"] = ToJson(op.expressions_);
|
||||
self["detach"] = op.detach_;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(SetProperty &op) {
|
||||
json self;
|
||||
self["name"] = "SetProperty";
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["lhs"] = ToJson(op.lhs_);
|
||||
self["rhs"] = ToJson(op.rhs_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(SetProperties &op) {
|
||||
json self;
|
||||
self["name"] = "SetProperties";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["rhs"] = ToJson(op.rhs_);
|
||||
|
||||
switch (op.op_) {
|
||||
case SetProperties::Op::UPDATE:
|
||||
self["op"] = "update";
|
||||
break;
|
||||
case SetProperties::Op::REPLACE:
|
||||
self["op"] = "replace";
|
||||
break;
|
||||
}
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(SetLabels &op) {
|
||||
json self;
|
||||
self["name"] = "SetLabels";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["labels"] = ToJson(op.labels_, *dba_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(RemoveProperty &op) {
|
||||
json self;
|
||||
self["name"] = "RemoveProperty";
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["lhs"] = ToJson(op.lhs_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(RemoveLabels &op) {
|
||||
json self;
|
||||
self["name"] = "RemoveLabels";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["labels"] = ToJson(op.labels_, *dba_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(EdgeUniquenessFilter &op) {
|
||||
json self;
|
||||
self["name"] = "EdgeUniquenessFilter";
|
||||
self["expand_symbol"] = ToJson(op.expand_symbol_);
|
||||
self["previous_symbols"] = ToJson(op.previous_symbols_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Accumulate &op) {
|
||||
json self;
|
||||
self["name"] = "Accumulate";
|
||||
self["symbols"] = ToJson(op.symbols_);
|
||||
self["advance_command"] = op.advance_command_;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Aggregate &op) {
|
||||
json self;
|
||||
self["name"] = "Aggregate";
|
||||
self["aggregations"] = ToJson(op.aggregations_);
|
||||
self["group_by"] = ToJson(op.group_by_);
|
||||
self["remember"] = ToJson(op.remember_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Skip &op) {
|
||||
json self;
|
||||
self["name"] = "Skip";
|
||||
self["expression"] = ToJson(op.expression_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Limit &op) {
|
||||
json self;
|
||||
self["name"] = "Limit";
|
||||
self["expression"] = ToJson(op.expression_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(OrderBy &op) {
|
||||
json self;
|
||||
self["name"] = "OrderBy";
|
||||
|
||||
for (auto i = 0; i < op.order_by_.size(); ++i) {
|
||||
json json;
|
||||
json["ordering"] = ToString(op.compare_.ordering_[i]);
|
||||
json["expression"] = ToJson(op.order_by_[i]);
|
||||
self["order_by"].push_back(json);
|
||||
}
|
||||
self["output_symbols"] = ToJson(op.output_symbols_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Merge &op) {
|
||||
json self;
|
||||
self["name"] = "Merge";
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
op.merge_match_->Accept(*this);
|
||||
self["merge_match"] = PopOutput();
|
||||
|
||||
op.merge_create_->Accept(*this);
|
||||
self["merge_create"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Optional &op) {
|
||||
json self;
|
||||
self["name"] = "Optional";
|
||||
self["optional_symbols"] = ToJson(op.optional_symbols_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
op.optional_->Accept(*this);
|
||||
self["optional"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Unwind &op) {
|
||||
json self;
|
||||
self["name"] = "Unwind";
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
self["input_expression"] = ToJson(op.input_expression_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Distinct &op) {
|
||||
json self;
|
||||
self["name"] = "Distinct";
|
||||
self["value_symbols"] = ToJson(op.value_symbols_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Union &op) {
|
||||
json self;
|
||||
self["name"] = "Union";
|
||||
self["union_symbols"] = ToJson(op.union_symbols_);
|
||||
self["left_symbols"] = ToJson(op.left_symbols_);
|
||||
self["right_symbols"] = ToJson(op.right_symbols_);
|
||||
|
||||
op.left_op_->Accept(*this);
|
||||
self["left_op"] = PopOutput();
|
||||
|
||||
op.right_op_->Accept(*this);
|
||||
self["right_op"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Cartesian &op) {
|
||||
json self;
|
||||
self["name"] = "Cartesian";
|
||||
self["left_symbols"] = ToJson(op.left_symbols_);
|
||||
self["right_symbols"] = ToJson(op.right_symbols_);
|
||||
|
||||
op.left_op_->Accept(*this);
|
||||
self["left_op"] = PopOutput();
|
||||
|
||||
op.right_op_->Accept(*this);
|
||||
self["right_op"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace impl
|
||||
|
||||
} // namespace query::plan
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "query/plan/operator.hpp"
|
||||
|
||||
namespace database {
|
||||
@ -26,6 +28,11 @@ inline void PrettyPrint(const database::GraphDbAccessor &dba,
|
||||
PrettyPrint(dba, plan_root, &std::cout);
|
||||
}
|
||||
|
||||
/// Convert a `LogicalOperator` plan to a JSON representation.
|
||||
/// GraphDbAccessor is needed for resolving label and property names.
|
||||
nlohmann::json PlanToJson(const database::GraphDbAccessor &dba,
|
||||
const LogicalOperator *plan_root);
|
||||
|
||||
class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
@ -98,4 +105,114 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
std::ostream *out_{nullptr};
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
std::string ToString(EdgeAtom::Direction dir);
|
||||
|
||||
std::string ToString(EdgeAtom::Type type);
|
||||
|
||||
std::string ToString(Ordering ord);
|
||||
|
||||
nlohmann::json ToJson(Expression *expression);
|
||||
|
||||
nlohmann::json ToJson(const utils::Bound<Expression *> &bound);
|
||||
|
||||
nlohmann::json ToJson(const Symbol &symbol);
|
||||
|
||||
nlohmann::json ToJson(storage::EdgeType edge_type,
|
||||
const database::GraphDbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(storage::Label label,
|
||||
const database::GraphDbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(storage::Property property,
|
||||
const database::GraphDbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(NamedExpression *nexpr);
|
||||
|
||||
nlohmann::json ToJson(
|
||||
const std::vector<std::pair<storage::Property, Expression *>> &properties,
|
||||
const database::GraphDbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(const NodeCreationInfo &node_info,
|
||||
const database::GraphDbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(const EdgeCreationInfo &edge_info,
|
||||
const database::GraphDbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(const Aggregate::Element &elem);
|
||||
|
||||
template <class T, class... Args>
|
||||
nlohmann::json ToJson(const std::vector<T> &items, Args &&... args) {
|
||||
nlohmann::json json;
|
||||
for (const auto &item : items) {
|
||||
json.emplace_back(ToJson(item, std::forward<Args>(args)...));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
PlanToJsonVisitor(const database::GraphDbAccessor *dba) : dba_(dba) {}
|
||||
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::Visit;
|
||||
|
||||
bool PreVisit(CreateNode &) override;
|
||||
bool PreVisit(CreateExpand &) override;
|
||||
bool PreVisit(Delete &) override;
|
||||
|
||||
bool PreVisit(SetProperty &) override;
|
||||
bool PreVisit(SetProperties &) override;
|
||||
bool PreVisit(SetLabels &) override;
|
||||
|
||||
bool PreVisit(RemoveProperty &) override;
|
||||
bool PreVisit(RemoveLabels &) override;
|
||||
|
||||
bool PreVisit(Expand &) override;
|
||||
bool PreVisit(ExpandVariable &) override;
|
||||
|
||||
bool PreVisit(ConstructNamedPath &) override;
|
||||
|
||||
bool PreVisit(Merge &) override;
|
||||
bool PreVisit(Optional &) override;
|
||||
|
||||
bool PreVisit(Filter &) override;
|
||||
bool PreVisit(EdgeUniquenessFilter &) override;
|
||||
bool PreVisit(Cartesian &) override;
|
||||
|
||||
bool PreVisit(ScanAll &) override;
|
||||
bool PreVisit(ScanAllByLabel &) override;
|
||||
bool PreVisit(ScanAllByLabelPropertyRange &) override;
|
||||
bool PreVisit(ScanAllByLabelPropertyValue &) override;
|
||||
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
bool PreVisit(Aggregate &) override;
|
||||
bool PreVisit(Skip &) override;
|
||||
bool PreVisit(Limit &) override;
|
||||
bool PreVisit(OrderBy &) override;
|
||||
bool PreVisit(Distinct &) override;
|
||||
bool PreVisit(Union &) override;
|
||||
|
||||
bool PreVisit(Unwind &) override;
|
||||
|
||||
bool Visit(Once &) override;
|
||||
|
||||
nlohmann::json output() { return output_; }
|
||||
|
||||
protected:
|
||||
nlohmann::json output_;
|
||||
const database::GraphDbAccessor *dba_;
|
||||
|
||||
nlohmann::json PopOutput() {
|
||||
nlohmann::json tmp;
|
||||
tmp.swap(output_);
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
} // namespace query::plan
|
||||
|
@ -85,6 +85,9 @@ target_link_libraries(${test_prefix}distributed_graph_db mg-distributed kvstore_
|
||||
add_unit_test(distributed_interpretation.cpp)
|
||||
target_link_libraries(${test_prefix}distributed_interpretation mg-distributed kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(distributed_plan_pretty_print.cpp)
|
||||
target_link_libraries(${test_prefix}distributed_plan_pretty_print mg-distributed kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(distributed_query_plan.cpp)
|
||||
target_link_libraries(${test_prefix}distributed_query_plan mg-distributed kvstore_dummy_lib)
|
||||
|
||||
@ -173,6 +176,9 @@ target_link_libraries(${test_prefix}query_cost_estimator mg-single-node kvstore_
|
||||
add_unit_test(query_expression_evaluator.cpp)
|
||||
target_link_libraries(${test_prefix}query_expression_evaluator mg-single-node kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(plan_pretty_print.cpp)
|
||||
target_link_libraries(${test_prefix}plan_pretty_print mg-single-node kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(query_pretty_print.cpp)
|
||||
target_link_libraries(${test_prefix}query_pretty_print mg-single-node kvstore_dummy_lib)
|
||||
|
||||
|
289
tests/unit/distributed_plan_pretty_print.cpp
Normal file
289
tests/unit/distributed_plan_pretty_print.cpp
Normal file
@ -0,0 +1,289 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "database/distributed/graph_db.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/plan/distributed_ops.hpp"
|
||||
#include "query/plan/distributed_pretty_print.hpp"
|
||||
|
||||
#include "distributed_common.hpp"
|
||||
#include "query_common.hpp"
|
||||
|
||||
using namespace query;
|
||||
using namespace query::plan;
|
||||
|
||||
// The JSON formatted plan is consumed (or will be) by Memgraph Lab, and
|
||||
// therefore should not be changed before synchronizing with whoever is
|
||||
// maintaining Memgraph Lab. Hopefully, one day integration tests will exist and
|
||||
// there will be no need to be super careful.
|
||||
|
||||
// This is a hack to prevent Googletest from crashing when outputing JSONs.
|
||||
namespace nlohmann {
|
||||
void PrintTo(const json &json, std::ostream *os) {
|
||||
*os << std::endl << json.dump(1);
|
||||
}
|
||||
} // namespace nlohmann
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
class PrintToJsonTest : public ::testing::Test {
|
||||
public:
|
||||
static void SetUpTestCase() {
|
||||
cluster_ = std::make_unique<Cluster>(0, "distributed_plan_to_json");
|
||||
}
|
||||
static void TearDownTestCase() { cluster_ = nullptr; }
|
||||
|
||||
protected:
|
||||
PrintToJsonTest() : dba_ptr(cluster_->master()->Access()), dba(*dba_ptr) {}
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
std::unique_ptr<database::GraphDbAccessor> dba_ptr;
|
||||
database::GraphDbAccessor &dba;
|
||||
|
||||
Symbol GetSymbol(std::string name) {
|
||||
return symbol_table.CreateSymbol(name, true);
|
||||
}
|
||||
|
||||
void Check(LogicalOperator *root, std::string expected) {
|
||||
EXPECT_EQ(DistributedPlanToJson(dba, root), json::parse(expected));
|
||||
}
|
||||
|
||||
static std::unique_ptr<Cluster> cluster_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Cluster> PrintToJsonTest::cluster_{nullptr};
|
||||
|
||||
TEST_F(PrintToJsonTest, PullRemote) {
|
||||
Symbol node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node"));
|
||||
last_op =
|
||||
std::make_shared<PullRemote>(last_op, 1, std::vector<Symbol>{node_sym});
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "PullRemote",
|
||||
"symbols" : ["node"],
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Synchronize) {
|
||||
storage::Property prop = dba.Property("prop");
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::SetProperty>(
|
||||
last_op, prop, PROPERTY_LOOKUP("node", prop),
|
||||
ADD(PROPERTY_LOOKUP("node", prop), LITERAL(1)));
|
||||
auto pull_remote =
|
||||
std::make_shared<PullRemote>(last_op, 1, std::vector<Symbol>{node_sym});
|
||||
last_op = std::make_shared<Synchronize>(std::make_shared<Once>(), pull_remote,
|
||||
true);
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Synchronize",
|
||||
"advance_command" : true,
|
||||
"input" : { "name" : "Once" },
|
||||
"pull_remote" : {
|
||||
"name" : "PullRemote",
|
||||
"symbols" : ["node"],
|
||||
"input" : {
|
||||
"name" : "SetProperty",
|
||||
"property" : "prop",
|
||||
"lhs" : "(PropertyLookup (Identifier \"node\") \"prop\")",
|
||||
"rhs" : "(+ (PropertyLookup (Identifier \"node\") \"prop\") 1)",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
}
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, SynchronizeNoPullRemote) {
|
||||
auto last_op =
|
||||
std::make_shared<Synchronize>(std::make_shared<Once>(), nullptr, false);
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Synchronize",
|
||||
"advance_command" : false,
|
||||
"input" : { "name" : "Once" },
|
||||
"pull_remote" : null
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, PullRemoteOrderBy) {
|
||||
Symbol node_sym = GetSymbol("node");
|
||||
storage::Property value = dba.Property("value");
|
||||
storage::Property color = dba.Property("color");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<PullRemoteOrderBy>(
|
||||
last_op, 1,
|
||||
std::vector<SortItem>{{Ordering::ASC, PROPERTY_LOOKUP("node", value)},
|
||||
{Ordering::DESC, PROPERTY_LOOKUP("node", color)}},
|
||||
std::vector<Symbol>{node_sym});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "PullRemoteOrderBy",
|
||||
"order_by" : [
|
||||
{
|
||||
"ordering" : "asc",
|
||||
"expression" : "(PropertyLookup (Identifier \"node\") \"value\")"
|
||||
},
|
||||
{
|
||||
"ordering" : "desc",
|
||||
"expression" : "(PropertyLookup (Identifier \"node\") \"color\")"
|
||||
}
|
||||
],
|
||||
"symbols" : ["node"],
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Expand) {
|
||||
auto node1_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<DistributedExpand>(
|
||||
last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"),
|
||||
EdgeAtom::Direction::BOTH,
|
||||
std::vector<storage::EdgeType>{dba.EdgeType("EdgeType1"),
|
||||
dba.EdgeType("EdgeType2")},
|
||||
false, GraphView::OLD);
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "DistributedExpand",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge",
|
||||
"direction" : "both",
|
||||
"edge_types" : ["EdgeType1", "EdgeType2"],
|
||||
"existing_node" : false,
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, DistributedExpandBfs) {
|
||||
auto node1_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<DistributedExpandBfs>(
|
||||
last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"),
|
||||
EdgeAtom::Direction::OUT,
|
||||
std::vector<storage::EdgeType>{dba.EdgeType("EdgeType1"),
|
||||
dba.EdgeType("EdgeType2")},
|
||||
false, LITERAL(2), LITERAL(5),
|
||||
ExpansionLambda{
|
||||
GetSymbol("inner_node"), GetSymbol("inner_edge"),
|
||||
PROPERTY_LOOKUP("inner_node", dba.Property("unblocked"))});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "DistributedExpandBfs",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge",
|
||||
"direction" : "out",
|
||||
"edge_types" : ["EdgeType1", "EdgeType2"],
|
||||
"existing_node" : false,
|
||||
"lower_bound" : "2",
|
||||
"upper_bound" : "5",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"filter_lambda" : "(PropertyLookup (Identifier \"inner_node\") \"unblocked\")"
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, DistributedCreateNode) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<DistributedCreateNode>(
|
||||
std::make_shared<Once>(),
|
||||
NodeCreationInfo{GetSymbol("node"),
|
||||
{dba.Label("Label1"), dba.Label("Label2")},
|
||||
{{dba.Property("prop1"), LITERAL(5)},
|
||||
{dba.Property("prop2"), LITERAL("some cool stuff")}}},
|
||||
true);
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "DistributedCreateNode",
|
||||
"on_random_worker" : true,
|
||||
"node_info" : {
|
||||
"symbol" : "node",
|
||||
"labels" : ["Label1", "Label2"],
|
||||
"properties" : {
|
||||
"prop1" : "5",
|
||||
"prop2" : "\"some cool stuff\""
|
||||
}
|
||||
},
|
||||
"input" : { "name" : "Once" }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, DistributedCreateExpand) {
|
||||
Symbol node1_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, GetSymbol("node1"));
|
||||
last_op = std::make_shared<DistributedCreateExpand>(
|
||||
NodeCreationInfo{GetSymbol("node2"),
|
||||
{dba.Label("Label1"), dba.Label("Label2")},
|
||||
{{dba.Property("prop1"), LITERAL(5)},
|
||||
{dba.Property("prop2"), LITERAL("some cool stuff")}}},
|
||||
EdgeCreationInfo{GetSymbol("edge"),
|
||||
{{dba.Property("weight"), LITERAL(5.32)}},
|
||||
dba.EdgeType("edge_type"),
|
||||
EdgeAtom::Direction::OUT},
|
||||
last_op, node1_sym, false);
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "DistributedCreateExpand",
|
||||
"node_info" : {
|
||||
"symbol" : "node2",
|
||||
"labels" : ["Label1", "Label2"],
|
||||
"properties" : {
|
||||
"prop1" : "5",
|
||||
"prop2" : "\"some cool stuff\""
|
||||
}
|
||||
},
|
||||
"edge_info" : {
|
||||
"symbol" : "edge",
|
||||
"properties" : {
|
||||
"weight" : "5.32"
|
||||
},
|
||||
"edge_type" : "edge_type",
|
||||
"direction" : "out"
|
||||
},
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"input_symbol" : "node1",
|
||||
"existing_node" : false
|
||||
})");
|
||||
}
|
941
tests/unit/plan_pretty_print.cpp
Normal file
941
tests/unit/plan_pretty_print.cpp
Normal file
@ -0,0 +1,941 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "database/graph_db.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
|
||||
#include "query_common.hpp"
|
||||
|
||||
using namespace query;
|
||||
using namespace query::plan;
|
||||
|
||||
// The JSON formatted plan is consumed (or will be) by Memgraph Lab, and
|
||||
// therefore should not be changed before synchronizing with whoever is
|
||||
// maintaining Memgraph Lab. Hopefully, one day integration tests will exist and
|
||||
// there will be no need to be super careful.
|
||||
|
||||
// This is a hack to prevent Googletest from crashing when outputing JSONs.
|
||||
namespace nlohmann {
|
||||
void PrintTo(const json &json, std::ostream *os) {
|
||||
*os << std::endl << json.dump(1);
|
||||
}
|
||||
} // namespace nlohmann
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
class PrintToJsonTest : public ::testing::Test {
|
||||
protected:
|
||||
PrintToJsonTest() : db(), dba_ptr(db.Access()), dba(*dba_ptr) {}
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
database::GraphDb db;
|
||||
std::unique_ptr<database::GraphDbAccessor> dba_ptr;
|
||||
database::GraphDbAccessor &dba;
|
||||
|
||||
Symbol GetSymbol(std::string name) {
|
||||
return symbol_table.CreateSymbol(name, true);
|
||||
}
|
||||
|
||||
void Check(LogicalOperator *root, std::string expected) {
|
||||
EXPECT_EQ(PlanToJson(dba, root), json::parse(expected));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PrintToJsonTest, Once) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<Once>();
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "Once"
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, ScanAll) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node"));
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, ScanAllByLabel) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabel>(nullptr, GetSymbol("node"),
|
||||
dba.Label("Label"));
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "ScanAllByLabel",
|
||||
"label" : "Label",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, ScanAllByLabelPropertyRange) {
|
||||
{
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabelPropertyRange>(
|
||||
nullptr, GetSymbol("node"), dba.Label("Label"), dba.Property("prop"),
|
||||
"prop", utils::MakeBoundInclusive<Expression *>(LITERAL(1)),
|
||||
utils::MakeBoundExclusive<Expression *>(LITERAL(20)));
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "ScanAllByLabelPropertyRange",
|
||||
"label" : "Label",
|
||||
"property" : "prop",
|
||||
"lower_bound" : {
|
||||
"value" : "1",
|
||||
"type" : "inclusive"
|
||||
},
|
||||
"upper_bound" : {
|
||||
"value" : "20",
|
||||
"type" : "exclusive"
|
||||
},
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
})");
|
||||
}
|
||||
{
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabelPropertyRange>(
|
||||
nullptr, GetSymbol("node"), dba.Label("Label"), dba.Property("prop"),
|
||||
"prop", std::experimental::nullopt,
|
||||
utils::MakeBoundExclusive<Expression *>(LITERAL(20)));
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "ScanAllByLabelPropertyRange",
|
||||
"label" : "Label",
|
||||
"property" : "prop",
|
||||
"lower_bound" : null,
|
||||
"upper_bound" : {
|
||||
"value" : "20",
|
||||
"type" : "exclusive"
|
||||
},
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
})");
|
||||
}
|
||||
{
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabelPropertyRange>(
|
||||
nullptr, GetSymbol("node"), dba.Label("Label"), dba.Property("prop"),
|
||||
"prop", utils::MakeBoundInclusive<Expression *>(LITERAL(1)),
|
||||
std::experimental::nullopt);
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "ScanAllByLabelPropertyRange",
|
||||
"label" : "Label",
|
||||
"property" : "prop",
|
||||
"lower_bound" : {
|
||||
"value" : "1",
|
||||
"type" : "inclusive"
|
||||
},
|
||||
"upper_bound" : null,
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
})");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, ScanAllByLabelPropertyValue) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabelPropertyValue>(
|
||||
nullptr, GetSymbol("node"), dba.Label("Label"), dba.Property("prop"),
|
||||
"prop", ADD(LITERAL(21), LITERAL(21)));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "ScanAllByLabelPropertyValue",
|
||||
"label" : "Label",
|
||||
"property" : "prop",
|
||||
"expression" : "(+ 21 21)",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, CreateNode) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<CreateNode>(
|
||||
nullptr,
|
||||
NodeCreationInfo{GetSymbol("node"),
|
||||
{dba.Label("Label1"), dba.Label("Label2")},
|
||||
{{dba.Property("prop1"), LITERAL(5)},
|
||||
{dba.Property("prop2"), LITERAL("some cool stuff")}}});
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "CreateNode",
|
||||
"node_info" : {
|
||||
"symbol" : "node",
|
||||
"labels" : ["Label1", "Label2"],
|
||||
"properties" : {
|
||||
"prop1" : "5",
|
||||
"prop2" : "\"some cool stuff\""
|
||||
}
|
||||
},
|
||||
"input" : { "name" : "Once" }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, CreateExpand) {
|
||||
Symbol node1_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, GetSymbol("node1"));
|
||||
last_op = std::make_shared<CreateExpand>(
|
||||
NodeCreationInfo{GetSymbol("node2"),
|
||||
{dba.Label("Label1"), dba.Label("Label2")},
|
||||
{{dba.Property("prop1"), LITERAL(5)},
|
||||
{dba.Property("prop2"), LITERAL("some cool stuff")}}},
|
||||
EdgeCreationInfo{GetSymbol("edge"),
|
||||
{{dba.Property("weight"), LITERAL(5.32)}},
|
||||
dba.EdgeType("edge_type"),
|
||||
EdgeAtom::Direction::OUT},
|
||||
last_op, node1_sym, false);
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "CreateExpand",
|
||||
"node_info" : {
|
||||
"symbol" : "node2",
|
||||
"labels" : ["Label1", "Label2"],
|
||||
"properties" : {
|
||||
"prop1" : "5",
|
||||
"prop2" : "\"some cool stuff\""
|
||||
}
|
||||
},
|
||||
"edge_info" : {
|
||||
"symbol" : "edge",
|
||||
"properties" : {
|
||||
"weight" : "5.32"
|
||||
},
|
||||
"edge_type" : "edge_type",
|
||||
"direction" : "out"
|
||||
},
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"input_symbol" : "node1",
|
||||
"existing_node" : false
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Expand) {
|
||||
auto node1_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<Expand>(
|
||||
last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"),
|
||||
EdgeAtom::Direction::BOTH,
|
||||
std::vector<storage::EdgeType>{dba.EdgeType("EdgeType1"),
|
||||
dba.EdgeType("EdgeType2")},
|
||||
false, GraphView::OLD);
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "Expand",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge",
|
||||
"direction" : "both",
|
||||
"edge_types" : ["EdgeType1", "EdgeType2"],
|
||||
"existing_node" : false,
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, ExpandVariable) {
|
||||
auto node1_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<ExpandVariable>(
|
||||
last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"),
|
||||
EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT,
|
||||
std::vector<storage::EdgeType>{dba.EdgeType("EdgeType1"),
|
||||
dba.EdgeType("EdgeType2")},
|
||||
false, LITERAL(2), LITERAL(5), false,
|
||||
ExpansionLambda{GetSymbol("inner_node"), GetSymbol("inner_edge"),
|
||||
PROPERTY_LOOKUP("inner_node", dba.Property("unblocked"))},
|
||||
std::experimental::nullopt, std::experimental::nullopt);
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "ExpandVariable",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge",
|
||||
"direction" : "out",
|
||||
"edge_types" : ["EdgeType1", "EdgeType2"],
|
||||
"existing_node" : false,
|
||||
"type" : "bfs",
|
||||
"is_reverse" : false,
|
||||
"lower_bound" : "2",
|
||||
"upper_bound" : "5",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"filter_lambda" : "(PropertyLookup (Identifier \"inner_node\") \"unblocked\")"
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, ExpandVariableWsp) {
|
||||
auto node1_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<ExpandVariable>(
|
||||
last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"),
|
||||
EdgeAtom::Type::WEIGHTED_SHORTEST_PATH, EdgeAtom::Direction::OUT,
|
||||
std::vector<storage::EdgeType>{dba.EdgeType("EdgeType1"),
|
||||
dba.EdgeType("EdgeType2")},
|
||||
false, LITERAL(2), LITERAL(5), false,
|
||||
ExpansionLambda{GetSymbol("inner_node"), GetSymbol("inner_edge"),
|
||||
nullptr},
|
||||
ExpansionLambda{GetSymbol("inner_node"), GetSymbol("inner_edge"),
|
||||
PROPERTY_LOOKUP("inner_edge", dba.Property("weight"))},
|
||||
GetSymbol("total"));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "ExpandVariable",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge",
|
||||
"direction" : "out",
|
||||
"edge_types" : ["EdgeType1", "EdgeType2"],
|
||||
"existing_node" : false,
|
||||
"type" : "wsp",
|
||||
"is_reverse" : false,
|
||||
"lower_bound" : "2",
|
||||
"upper_bound" : "5",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"filter_lambda" : null,
|
||||
"weight_lambda" : "(PropertyLookup (Identifier \"inner_edge\") \"weight\")",
|
||||
"total_weight_symbol" : "total"
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, ConstructNamedPath) {
|
||||
auto node1_sym = GetSymbol("node1");
|
||||
auto edge1_sym = GetSymbol("edge1");
|
||||
auto node2_sym = GetSymbol("node2");
|
||||
auto edge2_sym = GetSymbol("edge2");
|
||||
auto node3_sym = GetSymbol("node3");
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<Expand>(
|
||||
last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<storage::EdgeType>{}, false, GraphView::OLD);
|
||||
last_op = std::make_shared<Expand>(
|
||||
last_op, node2_sym, node3_sym, edge2_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<storage::EdgeType>{}, false, GraphView::OLD);
|
||||
last_op = std::make_shared<ConstructNamedPath>(
|
||||
last_op, GetSymbol("path"),
|
||||
std::vector<Symbol>{node1_sym, edge1_sym, node2_sym, edge2_sym,
|
||||
node3_sym});
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "ConstructNamedPath",
|
||||
"path_symbol" : "path",
|
||||
"path_elements" : ["node1", "edge1", "node2", "edge2", "node3"],
|
||||
"input" : {
|
||||
"name" : "Expand",
|
||||
"input_symbol" : "node2",
|
||||
"node_symbol" : "node3",
|
||||
"edge_symbol" : "edge2",
|
||||
"direction" : "out",
|
||||
"edge_types" : null,
|
||||
"existing_node" : false,
|
||||
"input" : {
|
||||
"name" : "Expand",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol": "edge1",
|
||||
"direction" : "out",
|
||||
"edge_types" : null,
|
||||
"existing_node" : false,
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
}
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Filter) {
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, GetSymbol("node1"));
|
||||
last_op = std::make_shared<Filter>(
|
||||
last_op, EQ(PROPERTY_LOOKUP("node1", dba.Property("prop")), LITERAL(5)));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Filter",
|
||||
"expression" : "(== (PropertyLookup (Identifier \"node1\") \"prop\") 5)",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Produce) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Produce>(
|
||||
nullptr,
|
||||
std::vector<NamedExpression *>{NEXPR("pet", LITERAL(5)),
|
||||
NEXPR("string", LITERAL("string"))});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Produce",
|
||||
"named_expressions" : [
|
||||
{
|
||||
"expression" : "5",
|
||||
"name" : "pet"
|
||||
},
|
||||
{
|
||||
"expression" : "\"string\"",
|
||||
"name" : "string"
|
||||
}
|
||||
],
|
||||
"input" : { "name" : "Once" }
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Delete) {
|
||||
auto node_sym = GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<Expand>(
|
||||
last_op, node_sym, GetSymbol("node2"), GetSymbol("edge"),
|
||||
EdgeAtom::Direction::BOTH, std::vector<storage::EdgeType>{}, false,
|
||||
GraphView::OLD);
|
||||
last_op = std::make_shared<plan::Delete>(
|
||||
last_op, std::vector<Expression *>{IDENT("node2")}, true);
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Delete",
|
||||
"expressions" : [ "(Identifier \"node2\")" ],
|
||||
"detach" : true,
|
||||
"input" : {
|
||||
"name" : "Expand",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge",
|
||||
"direction" : "both",
|
||||
"edge_types" : null,
|
||||
"existing_node" : false,
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, SetProperty) {
|
||||
storage::Property prop = dba.Property("prop");
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, GetSymbol("node"));
|
||||
last_op = std::make_shared<plan::SetProperty>(
|
||||
last_op, prop, PROPERTY_LOOKUP("node", prop),
|
||||
ADD(PROPERTY_LOOKUP("node", prop), LITERAL(1)));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "SetProperty",
|
||||
"property" : "prop",
|
||||
"lhs" : "(PropertyLookup (Identifier \"node\") \"prop\")",
|
||||
"rhs" : "(+ (PropertyLookup (Identifier \"node\") \"prop\") 1)",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, SetProperties) {
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::SetProperties>(
|
||||
last_op, node_sym,
|
||||
MAP({{storage.GetPropertyIx("prop1"), LITERAL(1)},
|
||||
{storage.GetPropertyIx("prop2"), LITERAL("propko")}}),
|
||||
plan::SetProperties::Op::REPLACE);
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "SetProperties",
|
||||
"input_symbol" : "node",
|
||||
"rhs" : "{\"prop1\": 1, \"prop2\": \"propko\"}",
|
||||
"op" : "replace",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, SetLabels) {
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::SetLabels>(
|
||||
last_op, node_sym,
|
||||
std::vector<storage::Label>{dba.Label("label1"), dba.Label("label2")});
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "SetLabels",
|
||||
"input_symbol" : "node",
|
||||
"labels" : ["label1", "label2"],
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, RemoveProperty) {
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::RemoveProperty>(
|
||||
last_op, dba.Property("prop"),
|
||||
PROPERTY_LOOKUP("node", dba.Property("prop")));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "RemoveProperty",
|
||||
"lhs" : "(PropertyLookup (Identifier \"node\") \"prop\")",
|
||||
"property" : "prop",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, RemoveLabels) {
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::RemoveLabels>(
|
||||
last_op, node_sym,
|
||||
std::vector<storage::Label>{dba.Label("label1"), dba.Label("label2")});
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "RemoveLabels",
|
||||
"input_symbol" : "node",
|
||||
"labels" : ["label1", "label2"],
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, EdgeUniquenessFilter) {
|
||||
auto node1_sym = GetSymbol("node1");
|
||||
auto node2_sym = GetSymbol("node2");
|
||||
auto node3_sym = GetSymbol("node3");
|
||||
auto node4_sym = GetSymbol("node4");
|
||||
|
||||
auto edge1_sym = GetSymbol("edge1");
|
||||
auto edge2_sym = GetSymbol("edge2");
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<Expand>(
|
||||
last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::IN,
|
||||
std::vector<storage::EdgeType>{}, false, GraphView::OLD);
|
||||
last_op = std::make_shared<ScanAll>(last_op, node3_sym);
|
||||
last_op = std::make_shared<Expand>(
|
||||
last_op, node3_sym, node4_sym, edge2_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<storage::EdgeType>{}, false, GraphView::OLD);
|
||||
last_op = std::make_shared<EdgeUniquenessFilter>(
|
||||
last_op, edge2_sym, std::vector<Symbol>{edge1_sym});
|
||||
|
||||
Check(last_op.get(), R"(
|
||||
{
|
||||
"name" : "EdgeUniquenessFilter",
|
||||
"expand_symbol" : "edge2",
|
||||
"previous_symbols" : ["edge1"],
|
||||
"input" : {
|
||||
"name" : "Expand",
|
||||
"input_symbol" : "node3",
|
||||
"node_symbol" : "node4",
|
||||
"edge_symbol" : "edge2",
|
||||
"direction" : "out",
|
||||
"edge_types" : null,
|
||||
"existing_node" : false,
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node3",
|
||||
"input" : {
|
||||
"name" : "Expand",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge1",
|
||||
"direction" : "in",
|
||||
"edge_types" : null,
|
||||
"existing_node" : false,
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Accumulate) {
|
||||
storage::Property prop = dba.Property("prop");
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::SetProperty>(
|
||||
last_op, prop, PROPERTY_LOOKUP("node", prop),
|
||||
ADD(PROPERTY_LOOKUP("node", prop), LITERAL(1)));
|
||||
last_op = std::make_shared<plan::Accumulate>(
|
||||
last_op, std::vector<Symbol>{node_sym}, true);
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Accumulate",
|
||||
"symbols" : ["node"],
|
||||
"advance_command" : true,
|
||||
"input" : {
|
||||
"name" : "SetProperty",
|
||||
"property" : "prop",
|
||||
"lhs" : "(PropertyLookup (Identifier \"node\") \"prop\")",
|
||||
"rhs" : "(+ (PropertyLookup (Identifier \"node\") \"prop\") 1)",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Aggregate) {
|
||||
storage::Property value = dba.Property("value");
|
||||
storage::Property color = dba.Property("color");
|
||||
storage::Property type = dba.Property("type");
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::Aggregate>(
|
||||
last_op,
|
||||
std::vector<Aggregate::Element>{
|
||||
{PROPERTY_LOOKUP("node", value), nullptr, Aggregation::Op::SUM,
|
||||
GetSymbol("sum")},
|
||||
{PROPERTY_LOOKUP("node", value), PROPERTY_LOOKUP("node", color),
|
||||
Aggregation::Op::COLLECT_MAP, GetSymbol("map")}},
|
||||
std::vector<Expression *>{PROPERTY_LOOKUP("node", type)},
|
||||
std::vector<Symbol>{node_sym});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Aggregate",
|
||||
"aggregations" : [
|
||||
{
|
||||
"value" : "(PropertyLookup (Identifier \"node\") \"value\")",
|
||||
"op" : "sum",
|
||||
"output_symbol" : "sum"
|
||||
},
|
||||
{
|
||||
"value" : "(PropertyLookup (Identifier \"node\") \"value\")",
|
||||
"key" : "(PropertyLookup (Identifier \"node\") \"color\")",
|
||||
"op" : "collect",
|
||||
"output_symbol" : "map"
|
||||
}
|
||||
],
|
||||
"group_by" : [
|
||||
"(PropertyLookup (Identifier \"node\") \"type\")"
|
||||
],
|
||||
"remember" : ["node"],
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Skip) {
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, GetSymbol("node"));
|
||||
last_op = std::make_shared<Skip>(last_op, LITERAL(42));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Skip",
|
||||
"expression" : "42",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Limit) {
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, GetSymbol("node"));
|
||||
last_op = std::make_shared<Limit>(last_op, LITERAL(42));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Limit",
|
||||
"expression" : "42",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, OrderBy) {
|
||||
Symbol node_sym = GetSymbol("node");
|
||||
storage::Property value = dba.Property("value");
|
||||
storage::Property color = dba.Property("color");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<OrderBy>(
|
||||
last_op,
|
||||
std::vector<SortItem>{{Ordering::ASC, PROPERTY_LOOKUP("node", value)},
|
||||
{Ordering::DESC, PROPERTY_LOOKUP("node", color)}},
|
||||
std::vector<Symbol>{node_sym});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "OrderBy",
|
||||
"order_by" : [
|
||||
{
|
||||
"ordering" : "asc",
|
||||
"expression" : "(PropertyLookup (Identifier \"node\") \"value\")"
|
||||
},
|
||||
{
|
||||
"ordering" : "desc",
|
||||
"expression" : "(PropertyLookup (Identifier \"node\") \"color\")"
|
||||
}
|
||||
],
|
||||
"output_symbols" : ["node"],
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Merge) {
|
||||
Symbol node_sym = GetSymbol("node");
|
||||
storage::Label label = dba.Label("label");
|
||||
|
||||
std::shared_ptr<LogicalOperator> match =
|
||||
std::make_shared<ScanAllByLabel>(nullptr, node_sym, label);
|
||||
|
||||
std::shared_ptr<LogicalOperator> create = std::make_shared<CreateNode>(
|
||||
nullptr, NodeCreationInfo{node_sym, {label}, {}});
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<plan::Merge>(nullptr, match, create);
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Merge",
|
||||
"input" : { "name" : "Once" },
|
||||
"merge_match" : {
|
||||
"name" : "ScanAllByLabel",
|
||||
"label" : "label",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"merge_create" : {
|
||||
"name" : "CreateNode",
|
||||
"node_info" : {
|
||||
"symbol" : "node",
|
||||
"labels" : ["label"],
|
||||
"properties" : null
|
||||
},
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Optional) {
|
||||
Symbol node1_sym = GetSymbol("node1");
|
||||
Symbol node2_sym = GetSymbol("node2");
|
||||
Symbol edge_sym = GetSymbol("edge");
|
||||
|
||||
std::shared_ptr<LogicalOperator> input =
|
||||
std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
|
||||
std::shared_ptr<LogicalOperator> expand = std::make_shared<Expand>(
|
||||
nullptr, node1_sym, node2_sym, edge_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<storage::EdgeType>{}, false, GraphView::OLD);
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Optional>(
|
||||
input, expand, std::vector<Symbol>{node2_sym, edge_sym});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Optional",
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node1",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"optional" : {
|
||||
"name" : "Expand",
|
||||
"input_symbol" : "node1",
|
||||
"node_symbol" : "node2",
|
||||
"edge_symbol" : "edge",
|
||||
"direction" : "out",
|
||||
"edge_types" : null,
|
||||
"existing_node" : false,
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"optional_symbols" : ["node2", "edge"]
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Unwind) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<plan::Unwind>(
|
||||
nullptr, LIST(LITERAL(1), LITERAL(2), LITERAL(3)), GetSymbol("x"));
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Unwind",
|
||||
"output_symbol" : "x",
|
||||
"input_expression" : "(ListLiteral [1, 2, 3])",
|
||||
"input" : { "name" : "Once" }
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Distinct) {
|
||||
Symbol x = GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<plan::Unwind>(
|
||||
nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x);
|
||||
last_op = std::make_shared<Distinct>(last_op, std::vector<Symbol>{x});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Distinct",
|
||||
"value_symbols" : ["x"],
|
||||
"input" : {
|
||||
"name" : "Unwind",
|
||||
"output_symbol" : "x",
|
||||
"input_expression" : "(ListLiteral [2, 3, 2])",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Union) {
|
||||
Symbol x = GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> lhs = std::make_shared<plan::Unwind>(
|
||||
nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x);
|
||||
|
||||
Symbol node = GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> rhs =
|
||||
std::make_shared<ScanAll>(nullptr, node);
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Union>(
|
||||
lhs, rhs, std::vector<Symbol>{GetSymbol("x")}, std::vector<Symbol>{x},
|
||||
std::vector<Symbol>{node});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Union",
|
||||
"union_symbols" : ["x"],
|
||||
"left_symbols" : ["x"],
|
||||
"right_symbols" : ["x"],
|
||||
"left_op" : {
|
||||
"name" : "Unwind",
|
||||
"output_symbol" : "x",
|
||||
"input_expression" : "(ListLiteral [2, 3, 2])",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"right_op" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "x",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, Cartesian) {
|
||||
Symbol x = GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> lhs = std::make_shared<plan::Unwind>(
|
||||
nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x);
|
||||
|
||||
Symbol node = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> rhs =
|
||||
std::make_shared<ScanAll>(nullptr, node);
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Cartesian>(
|
||||
lhs, std::vector<Symbol>{x}, rhs, std::vector<Symbol>{node});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Cartesian",
|
||||
"left_symbols" : ["x"],
|
||||
"right_symbols" : ["node"],
|
||||
"left_op" : {
|
||||
"name" : "Unwind",
|
||||
"output_symbol" : "x",
|
||||
"input_expression" : "(ListLiteral [2, 3, 2])",
|
||||
"input" : { "name" : "Once" }
|
||||
},
|
||||
"right_op" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
Loading…
Reference in New Issue
Block a user