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:
Marin Tomic 2019-01-25 15:45:13 +01:00
parent a871212675
commit 7542f5b0ba
12 changed files with 2124 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
})");
}

View 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");
}