Implement ast generation by cypher visitor
This commit is contained in:
parent
cfd2eae9d0
commit
026e0e6fbd
@ -349,6 +349,7 @@ set(memgraph_src_files
|
||||
${src_dir}/database/graph_db_accessor.cpp
|
||||
${src_dir}/query/stripper.cpp
|
||||
${src_dir}/query/frontend/ast/cypher_main_visitor.cpp
|
||||
${src_dir}/query/context.cpp
|
||||
${src_dir}/query/backend/cpp/typed_value.cpp
|
||||
${src_dir}/query/frontend/ast/ast.cpp
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include "antlr4-runtime.h"
|
||||
#include "query/backend/cpp/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
@ -13,8 +13,8 @@ namespace cpp {
|
||||
using namespace antlr4;
|
||||
|
||||
class GeneratorException : public BasicException {
|
||||
public:
|
||||
using BasicException::BasicException;
|
||||
public:
|
||||
using BasicException::BasicException;
|
||||
GeneratorException() : BasicException("") {}
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ class GeneratorException : public BasicException {
|
||||
* C++.
|
||||
*/
|
||||
class Generator {
|
||||
public:
|
||||
public:
|
||||
/**
|
||||
* Generates cpp code inside file on the path.
|
||||
*/
|
||||
|
11
src/query/context.cpp
Normal file
11
src/query/context.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "query/context.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
void HighLevelAstConversion::Apply(Context &ctx,
|
||||
antlr4::tree::ParseTree *tree) {
|
||||
query::frontend::CypherMainVisitor visitor(ctx);
|
||||
visitor.visit(tree);
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@
|
||||
|
||||
#include "antlr4-runtime.h"
|
||||
#include "database/graph_db_accessor.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
class TypedcheckedTree {};
|
||||
|
||||
@ -11,8 +12,8 @@ class LogicalPlan {};
|
||||
class Context;
|
||||
|
||||
class LogicalPlanGenerator {
|
||||
public:
|
||||
std::vector<LogicalPlan> Generate(TypedcheckedTree&, Context&) {
|
||||
public:
|
||||
std::vector<LogicalPlan> Generate(TypedcheckedTree &, Context &) {
|
||||
return {LogicalPlan()};
|
||||
}
|
||||
};
|
||||
@ -22,32 +23,31 @@ struct Config {
|
||||
};
|
||||
|
||||
class Context {
|
||||
public:
|
||||
int uid_counter;
|
||||
Context(Config config, GraphDbAccessor& db_accessor)
|
||||
: config(config), db_accessor(db_accessor) {}
|
||||
public:
|
||||
Context(Config config, GraphDbAccessor &db_accessor)
|
||||
: config_(config), db_accessor_(db_accessor) {}
|
||||
int next_uid() { return uid_counter_++; }
|
||||
|
||||
Config config;
|
||||
GraphDbAccessor& db_accessor;
|
||||
Config config_;
|
||||
GraphDbAccessor &db_accessor_;
|
||||
int uid_counter_ = 0;
|
||||
};
|
||||
|
||||
class LogicalPlanner {
|
||||
public:
|
||||
public:
|
||||
LogicalPlanner(Context ctx) : ctx_(ctx) {}
|
||||
|
||||
LogicalPlan Apply(TypedcheckedTree typedchecked_tree) {
|
||||
return ctx_.config.logical_plan_generator.Generate(typedchecked_tree,
|
||||
ctx_)[0];
|
||||
return ctx_.config_.logical_plan_generator.Generate(typedchecked_tree,
|
||||
ctx_)[0];
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
Context ctx_;
|
||||
};
|
||||
|
||||
class HighLevelAstConversion {
|
||||
public:
|
||||
void Apply(const Context& ctx, antlr4::tree::ParseTree* tree) {
|
||||
query::frontend::CypherMainVisitor visitor(ctx);
|
||||
visitor.visit(tree);
|
||||
}
|
||||
public:
|
||||
void Apply(Context &ctx, antlr4::tree::ParseTree *tree);
|
||||
};
|
||||
}
|
||||
|
@ -26,12 +26,11 @@ namespace fs = std::experimental::filesystem;
|
||||
* the results should be returned (more optimal then just return
|
||||
* the whole result set)
|
||||
*/
|
||||
template <typename Stream>
|
||||
class QueryEngine : public Loggable {
|
||||
private:
|
||||
template <typename Stream> class QueryEngine : public Loggable {
|
||||
private:
|
||||
using QueryPlanLib = DynamicLib<QueryPlanTrait<Stream>>;
|
||||
|
||||
public:
|
||||
public:
|
||||
QueryEngine() : Loggable("QueryEngine") {}
|
||||
|
||||
/**
|
||||
@ -104,12 +103,12 @@ class QueryEngine : public Loggable {
|
||||
*
|
||||
* @return size_t the number of loaded query plans
|
||||
*/
|
||||
auto Size() { // TODO: const once whan ConcurrentMap::Accessor becomes const
|
||||
auto Size() { // TODO: const once whan ConcurrentMap::Accessor becomes const
|
||||
return query_plans.access().size();
|
||||
}
|
||||
// return query_plans.access().size(); }
|
||||
|
||||
private:
|
||||
private:
|
||||
/**
|
||||
* Loads query plan eather from hardcoded folder or from the file that is
|
||||
* generated in this method.
|
||||
@ -176,7 +175,7 @@ class QueryEngine : public Loggable {
|
||||
// TODO: underlying object has to be live during query execution
|
||||
// fix that when Antler will be introduced into the database
|
||||
|
||||
auto query_plan_instance = query_plan->instance(); // because of move
|
||||
auto query_plan_instance = query_plan->instance(); // because of move
|
||||
plans_accessor.insert(hash, std::move(query_plan));
|
||||
|
||||
// return an instance of runnable code (PlanInterface)
|
||||
|
@ -3,11 +3,11 @@
|
||||
|
||||
namespace query {
|
||||
|
||||
TypedValue Ident::Evaluate(Frame& frame, SymbolTable& symbol_table) {
|
||||
TypedValue Ident::Evaluate(Frame &frame, SymbolTable &symbol_table) {
|
||||
return frame[symbol_table[*this].position_];
|
||||
}
|
||||
|
||||
void NamedExpr::Evaluate(Frame& frame, SymbolTable& symbol_table) {
|
||||
void NamedExpr::Evaluate(Frame &frame, SymbolTable &symbol_table) {
|
||||
frame[symbol_table[*ident_].position_] = expr_->Evaluate(frame, symbol_table);
|
||||
}
|
||||
}
|
||||
|
@ -22,139 +22,161 @@ class NodePart;
|
||||
class EdgePart;
|
||||
|
||||
class TreeVisitorBase {
|
||||
public:
|
||||
public:
|
||||
// Start of the tree is a Query.
|
||||
virtual void PreVisit(Query& query) {}
|
||||
virtual void Visit(Query& query) = 0;
|
||||
virtual void PostVisit(Query& query) {}
|
||||
virtual void PreVisit(Query &) {}
|
||||
virtual void Visit(Query &query) = 0;
|
||||
virtual void PostVisit(Query &) {}
|
||||
// Expressions
|
||||
virtual void PreVisit(NamedExpr&) {}
|
||||
virtual void Visit(NamedExpr&) = 0;
|
||||
virtual void PostVisit(NamedExpr&) {}
|
||||
virtual void PreVisit(Ident& ident) {}
|
||||
virtual void Visit(Ident& ident) = 0;
|
||||
virtual void PostVisit(Ident& ident) {}
|
||||
virtual void PreVisit(NamedExpr &) {}
|
||||
virtual void Visit(NamedExpr &) = 0;
|
||||
virtual void PostVisit(NamedExpr &) {}
|
||||
virtual void PreVisit(Ident &) {}
|
||||
virtual void Visit(Ident &ident) = 0;
|
||||
virtual void PostVisit(Ident &) {}
|
||||
// Clauses
|
||||
virtual void PreVisit(Match& match) {}
|
||||
virtual void Visit(Match& match) = 0;
|
||||
virtual void PostVisit(Match& match) {}
|
||||
virtual void PreVisit(Return& ret) {}
|
||||
virtual void Visit(Return& ret) = 0;
|
||||
virtual void PostVisit(Return& ret) {}
|
||||
virtual void PreVisit(Match &) {}
|
||||
virtual void Visit(Match &match) = 0;
|
||||
virtual void PostVisit(Match &) {}
|
||||
virtual void PreVisit(Return &) {}
|
||||
virtual void Visit(Return &ret) = 0;
|
||||
virtual void PostVisit(Return &) {}
|
||||
// Pattern and its subparts.
|
||||
virtual void PreVisit(Pattern& pattern) {}
|
||||
virtual void Visit(Pattern& pattern) = 0;
|
||||
virtual void PostVisit(Pattern& pattern) {}
|
||||
virtual void PreVisit(NodePart& node_part) {}
|
||||
virtual void Visit(NodePart& node_part) = 0;
|
||||
virtual void PostVisit(NodePart& node_part) {}
|
||||
virtual void PreVisit(EdgePart& edge_part) {}
|
||||
virtual void Visit(EdgePart& edge_part) = 0;
|
||||
virtual void PostVisit(EdgePart& edge_part) {}
|
||||
virtual void PreVisit(Pattern &) {}
|
||||
virtual void Visit(Pattern &pattern) = 0;
|
||||
virtual void PostVisit(Pattern &) {}
|
||||
virtual void PreVisit(NodePart &) {}
|
||||
virtual void Visit(NodePart &node_part) = 0;
|
||||
virtual void PostVisit(NodePart &) {}
|
||||
virtual void PreVisit(EdgePart &) {}
|
||||
virtual void Visit(EdgePart &edge_part) = 0;
|
||||
virtual void PostVisit(EdgePart &) {}
|
||||
};
|
||||
|
||||
class Tree {
|
||||
public:
|
||||
Tree(const int uid) : uid_(uid) {}
|
||||
public:
|
||||
Tree(int uid) : uid_(uid) {}
|
||||
int uid() const { return uid_; }
|
||||
virtual void Accept(TreeVisitorBase& visitor) = 0;
|
||||
virtual void Accept(TreeVisitorBase &visitor) = 0;
|
||||
|
||||
private:
|
||||
private:
|
||||
const int uid_;
|
||||
};
|
||||
|
||||
class Expr : public Tree {
|
||||
public:
|
||||
virtual TypedValue Evaluate(Frame&, SymbolTable&) = 0;
|
||||
public:
|
||||
Expr(int uid) : Tree(uid) {}
|
||||
virtual TypedValue Evaluate(Frame &, SymbolTable &) = 0;
|
||||
};
|
||||
|
||||
class Ident : public Expr {
|
||||
public:
|
||||
std::string identifier_;
|
||||
TypedValue Evaluate(Frame& frame, SymbolTable& symbol_table) override;
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
public:
|
||||
Ident(int) = delete;
|
||||
Ident(int uid, const std::string &identifier)
|
||||
: Expr(uid), identifier_(identifier) {}
|
||||
|
||||
TypedValue Evaluate(Frame &frame, SymbolTable &symbol_table) override;
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
visitor.Visit(*this);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
std::string identifier_;
|
||||
};
|
||||
|
||||
class Part : public Tree {};
|
||||
class Part : public Tree {
|
||||
public:
|
||||
Part(int uid) : Tree(uid) {}
|
||||
};
|
||||
|
||||
class NamedExpr : public Tree {
|
||||
public:
|
||||
std::shared_ptr<Ident> ident_;
|
||||
std::shared_ptr<Expr> expr_;
|
||||
void Evaluate(Frame& frame, SymbolTable& symbol_table);
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
public:
|
||||
NamedExpr(int uid) : Tree(uid) {}
|
||||
void Evaluate(Frame &frame, SymbolTable &symbol_table);
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
ident_->Accept(visitor);
|
||||
expr_->Accept(visitor);
|
||||
visitor.Visit(*this);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<Ident> ident_;
|
||||
std::shared_ptr<Expr> expr_;
|
||||
};
|
||||
|
||||
class NodePart : public Part {
|
||||
public:
|
||||
Ident identifier_;
|
||||
// TODO: Mislav call GraphDb::label(label_name) to populate labels_!
|
||||
std::vector<GraphDb::Label> labels_;
|
||||
// TODO: properties
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
public:
|
||||
NodePart(int uid) : Part(uid) {}
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
identifier_.Accept(visitor);
|
||||
identifier_->Accept(visitor);
|
||||
visitor.Visit(*this);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<Ident> identifier_;
|
||||
std::vector<GraphDb::Label> labels_;
|
||||
};
|
||||
|
||||
class EdgePart : public Part {
|
||||
public:
|
||||
Ident identifier_;
|
||||
// TODO: finish this: properties, types...
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
public:
|
||||
enum class Direction { LEFT, RIGHT, BOTH };
|
||||
|
||||
EdgePart(int uid) : Part(uid) {}
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
identifier_.Accept(visitor);
|
||||
identifier_->Accept(visitor);
|
||||
visitor.Visit(*this);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
Direction direction = Direction::BOTH;
|
||||
std::shared_ptr<Ident> identifier_;
|
||||
};
|
||||
|
||||
class Clause : public Tree {};
|
||||
class Clause : public Tree {
|
||||
public:
|
||||
Clause(int uid) : Tree(uid) {}
|
||||
};
|
||||
|
||||
class Pattern : public Tree {
|
||||
public:
|
||||
std::vector<std::shared_ptr<NodePart>> node_parts_;
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
public:
|
||||
Pattern(int uid) : Tree(uid) {}
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
for (auto& node_part : node_parts_) {
|
||||
node_part->Accept(visitor);
|
||||
for (auto &part : parts_) {
|
||||
part->Accept(visitor);
|
||||
}
|
||||
visitor.Visit(*this);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
std::shared_ptr<Ident> identifier_;
|
||||
std::vector<std::shared_ptr<Part>> parts_;
|
||||
};
|
||||
|
||||
class Query : public Tree {
|
||||
public:
|
||||
std::vector<std::unique_ptr<Clause>> clauses_;
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
public:
|
||||
Query(int uid) : Tree(uid) {}
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
for (auto& clause : clauses_) {
|
||||
for (auto &clause : clauses_) {
|
||||
clause->Accept(visitor);
|
||||
}
|
||||
visitor.Visit(*this);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
std::vector<std::shared_ptr<Clause>> clauses_;
|
||||
};
|
||||
|
||||
class Match : public Clause {
|
||||
public:
|
||||
std::vector<std::unique_ptr<Pattern>> patterns_;
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
public:
|
||||
Match(int uid) : Clause(uid) {}
|
||||
std::vector<std::shared_ptr<Pattern>> patterns_;
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
for (auto& pattern : patterns_) {
|
||||
for (auto &pattern : patterns_) {
|
||||
pattern->Accept(visitor);
|
||||
}
|
||||
visitor.Visit(*this);
|
||||
@ -163,15 +185,19 @@ class Match : public Clause {
|
||||
};
|
||||
|
||||
class Return : public Clause {
|
||||
public:
|
||||
public:
|
||||
Return(int uid) : Clause(uid) {}
|
||||
std::vector<std::shared_ptr<NamedExpr>> exprs_;
|
||||
void Accept(TreeVisitorBase& visitor) override {
|
||||
void Accept(TreeVisitorBase &visitor) override {
|
||||
visitor.PreVisit(*this);
|
||||
for (auto& expr : exprs_) {
|
||||
for (auto &expr : exprs_) {
|
||||
expr->Accept(visitor);
|
||||
}
|
||||
visitor.Visit(*this);
|
||||
visitor.PostVisit(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<Ident> identifier_;
|
||||
std::vector<std::shared_ptr<NamedExpr>> named_exprs_;
|
||||
};
|
||||
}
|
||||
|
@ -6,103 +6,132 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "query/backend/cpp/compiler_structures.hpp"
|
||||
#include "database/graph_db.hpp"
|
||||
#include "query/frontend/ast/named_antlr_tokens.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
|
||||
namespace backend {
|
||||
namespace cpp {
|
||||
namespace query {
|
||||
namespace frontend {
|
||||
|
||||
namespace {
|
||||
// Map children tokens of antlr node to Function enum.
|
||||
std::vector<Function> MapTokensToOperators(
|
||||
antlr4::ParserRuleContext *node,
|
||||
const std::unordered_map<size_t, Function> token_to_operator) {
|
||||
std::vector<antlr4::tree::TerminalNode *> tokens;
|
||||
for (const auto &x : token_to_operator) {
|
||||
tokens.insert(tokens.end(), node->getTokens(x.first).begin(),
|
||||
node->getTokens(x.first).end());
|
||||
}
|
||||
sort(tokens.begin(), tokens.end(), [](antlr4::tree::TerminalNode *a,
|
||||
antlr4::tree::TerminalNode *b) {
|
||||
return a->getSourceInterval().startsBeforeDisjoint(b->getSourceInterval());
|
||||
});
|
||||
std::vector<Function> ops;
|
||||
for (auto *token : tokens) {
|
||||
auto it = token_to_operator.find(token->getSymbol()->getType());
|
||||
debug_assert(it != token_to_operator.end(),
|
||||
"Wrong mapping sent to function.");
|
||||
ops.push_back(it->second);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
// std::vector<Function> MapTokensToOperators(
|
||||
// antlr4::ParserRuleContext *node,
|
||||
// const std::unordered_map<size_t, Function> token_to_operator) {
|
||||
// std::vector<antlr4::tree::TerminalNode *> tokens;
|
||||
// for (const auto &x : token_to_operator) {
|
||||
// tokens.insert(tokens.end(), node->getTokens(x.first).begin(),
|
||||
// node->getTokens(x.first).end());
|
||||
// }
|
||||
// sort(tokens.begin(), tokens.end(), [](antlr4::tree::TerminalNode *a,
|
||||
// antlr4::tree::TerminalNode *b) {
|
||||
// return
|
||||
// a->getSourceInterval().startsBeforeDisjoint(b->getSourceInterval());
|
||||
// });
|
||||
// std::vector<Function> ops;
|
||||
// for (auto *token : tokens) {
|
||||
// auto it = token_to_operator.find(token->getSymbol()->getType());
|
||||
// debug_assert(it != token_to_operator.end(),
|
||||
// "Wrong mapping sent to function.");
|
||||
// ops.push_back(it->second);
|
||||
// }
|
||||
// return ops;
|
||||
//}
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitNodePattern(
|
||||
CypherParser::NodePatternContext *ctx) {
|
||||
bool new_node = true;
|
||||
Node node;
|
||||
if (ctx->variable()) {
|
||||
auto variable = ctx->variable()->accept(this).as<std::string>();
|
||||
auto &curr_id_map = ids_map_.back();
|
||||
if (curr_id_map.find(variable) != curr_id_map.end()) {
|
||||
if (!symbol_table_[curr_id_map[variable]].is<Node>()) {
|
||||
throw SemanticException();
|
||||
}
|
||||
new_node = false;
|
||||
node = symbol_table_[curr_id_map[variable]].as<Node>();
|
||||
} else {
|
||||
node.output_id = new_id();
|
||||
curr_id_map[variable] = node.output_id;
|
||||
}
|
||||
} else {
|
||||
node.output_id = new_id();
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitSingleQuery(CypherParser::SingleQueryContext *ctx) {
|
||||
auto children = ctx->children;
|
||||
query_ = std::make_shared<Query>(ctx_.next_uid());
|
||||
for (auto *child : children) {
|
||||
query_->clauses_.push_back(child->accept(this));
|
||||
}
|
||||
if (!new_node && (ctx->nodeLabels() || ctx->properties())) {
|
||||
// If variable is already declared, we cannot list properties or labels.
|
||||
// This is slightly incompatible with neo4j. In neo4j it is valid to write
|
||||
// MATCH (n {a: 5})--(n {b: 10}) which is equivalent to MATCH(n {a:5, b:
|
||||
// 10})--(n). Neo4j also allows MATCH (n) RETURN (n {x: 5}) which is
|
||||
// equivalent to MATCH (n) RETURN ({x: 5}).
|
||||
// TODO: The way in which we are storing nodes is not suitable for optional
|
||||
// match. For example: MATCH (n {a: 5}) OPTIONAL MATCH (n {b: 10}) RETURN
|
||||
// n.a, n.b. would not work. Think more about that case.
|
||||
throw SemanticException();
|
||||
return query_;
|
||||
}
|
||||
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitCypherMatch(CypherParser::CypherMatchContext *ctx) {
|
||||
auto match = std::make_shared<Match>(ctx_.next_uid());
|
||||
if (ctx->OPTIONAL() || ctx->where()) {
|
||||
throw std::exception();
|
||||
}
|
||||
match->patterns_ =
|
||||
ctx->pattern()->accept(this).as<std::vector<std::shared_ptr<Pattern>>>();
|
||||
return match;
|
||||
}
|
||||
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitReturnItems(CypherParser::ReturnItemsContext *ctx) {
|
||||
auto return_clause = std::make_shared<Return>(ctx_.next_uid());
|
||||
for (auto *item : ctx->returnItem()) {
|
||||
return_clause->named_exprs_.push_back(item->accept(this));
|
||||
}
|
||||
return return_clause;
|
||||
}
|
||||
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitReturnItem(CypherParser::ReturnItemContext *ctx) {
|
||||
auto named_expr = std::make_shared<NamedExpr>(ctx_.next_uid());
|
||||
if (ctx->variable()) {
|
||||
named_expr->ident_ =
|
||||
std::make_shared<Ident>(ctx_.next_uid(), ctx->variable()->accept(this));
|
||||
} else {
|
||||
named_expr->ident_ =
|
||||
std::make_shared<Ident>(ctx_.next_uid(), ctx->getText());
|
||||
}
|
||||
named_expr->expr_ = ctx->expression()->accept(this);
|
||||
return named_expr;
|
||||
}
|
||||
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitNodePattern(CypherParser::NodePatternContext *ctx) {
|
||||
auto node = new NodePart(ctx_.next_uid());
|
||||
if (ctx->variable()) {
|
||||
std::string variable = ctx->variable()->accept(this);
|
||||
node->identifier_ =
|
||||
std::make_shared<Ident>(ctx_.next_uid(), kUserIdentPrefix + variable);
|
||||
} else {
|
||||
node->identifier_ = std::make_shared<Ident>(
|
||||
ctx_.next_uid(), kAnonIdentPrefix + std::to_string(next_ident_id_++));
|
||||
}
|
||||
if (ctx->nodeLabels()) {
|
||||
node.labels =
|
||||
ctx->nodeLabels()->accept(this).as<std::vector<std::string>>();
|
||||
std::vector<std::string> labels = ctx->nodeLabels()->accept(this);
|
||||
for (const auto &label : labels) {
|
||||
node->labels_.push_back(ctx_.db_accessor_.label(label));
|
||||
}
|
||||
}
|
||||
if (ctx->properties()) {
|
||||
node.properties = ctx->properties()
|
||||
->accept(this)
|
||||
.as<std::unordered_map<std::string, std::string>>();
|
||||
throw std::exception();
|
||||
// node.properties = ctx->properties()
|
||||
// ->accept(this)
|
||||
// .as<std::unordered_map<std::string,
|
||||
// std::string>>();
|
||||
}
|
||||
symbol_table_[node.output_id] = node;
|
||||
return node.output_id;
|
||||
return (Part *)node;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitNodeLabels(
|
||||
CypherParser::NodeLabelsContext *ctx) {
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitNodeLabels(CypherParser::NodeLabelsContext *ctx) {
|
||||
std::vector<std::string> labels;
|
||||
for (auto *node_label : ctx->nodeLabel()) {
|
||||
labels.push_back(node_label->accept(this).as<std::string>());
|
||||
labels.push_back(node_label->accept(this));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitProperties(
|
||||
CypherParser::PropertiesContext *ctx) {
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitProperties(CypherParser::PropertiesContext *ctx) {
|
||||
if (!ctx->mapLiteral()) {
|
||||
// If child is not mapLiteral that means child is params. At the moment
|
||||
// memgraph doesn't support params.
|
||||
throw SemanticException();
|
||||
throw std::exception();
|
||||
}
|
||||
return ctx->mapLiteral()->accept(this);
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitMapLiteral(
|
||||
CypherParser::MapLiteralContext *ctx) {
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitMapLiteral(CypherParser::MapLiteralContext *ctx) {
|
||||
throw std::exception();
|
||||
std::unordered_map<std::string, std::string> map;
|
||||
for (int i = 0; i < (int)ctx->propertyKeyName().size(); ++i) {
|
||||
map[ctx->propertyKeyName()[i]->accept(this).as<std::string>()] =
|
||||
@ -111,40 +140,38 @@ antlrcpp::Any CypherMainVisitor::visitMapLiteral(
|
||||
return map;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitSymbolicName(
|
||||
CypherParser::SymbolicNameContext *ctx) {
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitSymbolicName(CypherParser::SymbolicNameContext *ctx) {
|
||||
if (ctx->EscapedSymbolicName()) {
|
||||
// We don't allow at this point for variable to be EscapedSymbolicName
|
||||
// because we would have t ofigure out how escaping works since same
|
||||
// variable can be referenced in two ways: escaped and unescaped.
|
||||
throw SemanticException();
|
||||
throw std::exception();
|
||||
}
|
||||
return std::string(ctx->getText());
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPattern(
|
||||
CypherParser::PatternContext *ctx) {
|
||||
std::vector<std::string> pattern;
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitPattern(CypherParser::PatternContext *ctx) {
|
||||
std::vector<std::shared_ptr<Pattern>> patterns;
|
||||
for (auto *pattern_part : ctx->patternPart()) {
|
||||
pattern.push_back(pattern_part->accept(this).as<std::string>());
|
||||
patterns.push_back(pattern_part->accept(this));
|
||||
}
|
||||
return pattern;
|
||||
return patterns;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPatternPart(
|
||||
CypherParser::PatternPartContext *ctx) {
|
||||
PatternPart pattern_part =
|
||||
ctx->anonymousPatternPart()->accept(this).as<PatternPart>();
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitPatternPart(CypherParser::PatternPartContext *ctx) {
|
||||
std::shared_ptr<Pattern> pattern = ctx->anonymousPatternPart()->accept(this);
|
||||
if (ctx->variable()) {
|
||||
std::string variable = ctx->variable()->accept(this).as<std::string>();
|
||||
auto &curr_id_map = ids_map_.back();
|
||||
if (curr_id_map.find(variable) != curr_id_map.end()) {
|
||||
throw SemanticException();
|
||||
}
|
||||
curr_id_map[variable] = pattern_part.output_id;
|
||||
std::string variable = ctx->variable()->accept(this);
|
||||
pattern->identifier_ =
|
||||
std::make_shared<Ident>(ctx_.next_uid(), kUserIdentPrefix + variable);
|
||||
} else {
|
||||
pattern->identifier_ = std::make_shared<Ident>(
|
||||
ctx_.next_uid(), kAnonIdentPrefix + std::to_string(next_ident_id_++));
|
||||
}
|
||||
symbol_table_[pattern_part.output_id] = pattern_part;
|
||||
return pattern_part.output_id;
|
||||
return pattern;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPatternElement(
|
||||
@ -152,88 +179,67 @@ antlrcpp::Any CypherMainVisitor::visitPatternElement(
|
||||
if (ctx->patternElement()) {
|
||||
return ctx->patternElement()->accept(this);
|
||||
}
|
||||
PatternPart pattern_part;
|
||||
pattern_part.output_id = new_id();
|
||||
pattern_part.nodes.push_back(
|
||||
ctx->nodePattern()->accept(this).as<std::string>());
|
||||
auto pattern = std::shared_ptr<Pattern>();
|
||||
pattern->parts_.push_back(
|
||||
ctx->nodePattern()->accept(this).as<std::shared_ptr<Part>>());
|
||||
for (auto *pattern_element_chain : ctx->patternElementChain()) {
|
||||
auto element = pattern_element_chain->accept(this)
|
||||
.as<std::pair<std::string, std::string>>();
|
||||
pattern_part.relationships.push_back(element.first);
|
||||
pattern_part.nodes.push_back(element.second);
|
||||
auto element =
|
||||
pattern_element_chain->accept(this)
|
||||
.as<std::pair<std::shared_ptr<Part>, std::shared_ptr<Part>>>();
|
||||
pattern->parts_.push_back(element.first);
|
||||
pattern->parts_.push_back(element.second);
|
||||
}
|
||||
return pattern_part;
|
||||
return pattern;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPatternElementChain(
|
||||
CypherParser::PatternElementChainContext *ctx) {
|
||||
return std::pair<std::string, std::string>(
|
||||
ctx->relationshipPattern()->accept(this).as<std::string>(),
|
||||
ctx->nodePattern()->accept(this).as<std::string>());
|
||||
return std::pair<std::shared_ptr<Part>, std::shared_ptr<Part>>(
|
||||
ctx->relationshipPattern()->accept(this).as<std::shared_ptr<Part>>(),
|
||||
ctx->nodePattern()->accept(this).as<std::shared_ptr<Part>>());
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(
|
||||
CypherParser::RelationshipPatternContext *ctx) {
|
||||
bool new_relationship = true;
|
||||
Relationship relationship;
|
||||
auto edge = std::make_shared<EdgePart>(ctx_.next_uid());
|
||||
if (ctx->relationshipDetail()) {
|
||||
if (ctx->relationshipDetail()->variable()) {
|
||||
auto variable =
|
||||
ctx->relationshipDetail()->variable()->accept(this).as<std::string>();
|
||||
auto &curr_id_map = ids_map_.back();
|
||||
if (curr_id_map.find(variable) != curr_id_map.end()) {
|
||||
if (!symbol_table_[curr_id_map[variable]].is<Relationship>()) {
|
||||
throw SemanticException();
|
||||
}
|
||||
new_relationship = false;
|
||||
relationship = symbol_table_[curr_id_map[variable]].as<Relationship>();
|
||||
} else {
|
||||
relationship.output_id = new_id();
|
||||
curr_id_map[variable] = relationship.output_id;
|
||||
}
|
||||
}
|
||||
if (!new_relationship && (ctx->relationshipDetail()->relationshipTypes() ||
|
||||
ctx->relationshipDetail()->properties() ||
|
||||
ctx->relationshipDetail()->rangeLiteral())) {
|
||||
// Neo4j doesn't allow multiple edges with same variable name, but if we
|
||||
// are going to support different types of morphisms then there is no
|
||||
// reason to disallow that.
|
||||
throw SemanticException();
|
||||
}
|
||||
if (ctx->relationshipDetail()->relationshipTypes()) {
|
||||
relationship.types = ctx->relationshipDetail()
|
||||
->relationshipTypes()
|
||||
->accept(this)
|
||||
.as<std::vector<std::string>>();
|
||||
}
|
||||
if (ctx->relationshipDetail()->properties()) {
|
||||
relationship.properties =
|
||||
ctx->relationshipDetail()
|
||||
->properties()
|
||||
->accept(this)
|
||||
.as<std::unordered_map<std::string, std::string>>();
|
||||
}
|
||||
if (ctx->relationshipDetail()->rangeLiteral()) {
|
||||
relationship.has_range = true;
|
||||
auto range = ctx->relationshipDetail()
|
||||
->rangeLiteral()
|
||||
->accept(this)
|
||||
.as<std::pair<int64_t, int64_t>>();
|
||||
relationship.lower_bound = range.first;
|
||||
relationship.upper_bound = range.second;
|
||||
std::string variable =
|
||||
ctx->relationshipDetail()->variable()->accept(this);
|
||||
edge->identifier_ =
|
||||
std::make_shared<Ident>(ctx_.next_uid(), kUserIdentPrefix + variable);
|
||||
} else {
|
||||
edge->identifier_ = std::make_shared<Ident>(
|
||||
ctx_.next_uid(), kAnonIdentPrefix + std::to_string(next_ident_id_++));
|
||||
}
|
||||
}
|
||||
if (ctx->relationshipDetail()->relationshipTypes()) {
|
||||
throw std::exception();
|
||||
}
|
||||
if (ctx->relationshipDetail()->properties()) {
|
||||
throw std::exception();
|
||||
}
|
||||
if (ctx->relationshipDetail()->rangeLiteral()) {
|
||||
throw std::exception();
|
||||
// relationship.has_range = true;
|
||||
// auto range = ctx->relationshipDetail()
|
||||
// ->rangeLiteral()
|
||||
// ->accept(this)
|
||||
// .as<std::pair<int64_t, int64_t>>();
|
||||
// relationship.lower_bound = range.first;
|
||||
// relationship.upper_bound = range.second;
|
||||
}
|
||||
|
||||
if (ctx->leftArrowHead() && !ctx->rightArrowHead()) {
|
||||
relationship.direction = Relationship::Direction::LEFT;
|
||||
edge->direction = EdgePart::Direction::LEFT;
|
||||
} else if (!ctx->leftArrowHead() && ctx->rightArrowHead()) {
|
||||
relationship.direction = Relationship::Direction::RIGHT;
|
||||
edge->direction = EdgePart::Direction::RIGHT;
|
||||
} else {
|
||||
// <-[]-> and -[]- is the same thing as far as we understand openCypher
|
||||
// grammar.
|
||||
relationship.direction = Relationship::Direction::BOTH;
|
||||
edge->direction = EdgePart::Direction::BOTH;
|
||||
}
|
||||
symbol_table_[relationship.output_id] = relationship;
|
||||
return relationship.output_id;
|
||||
return std::shared_ptr<Part>(edge);
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitRelationshipDetail(
|
||||
@ -251,8 +257,8 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipTypes(
|
||||
return types;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitRangeLiteral(
|
||||
CypherParser::RangeLiteralContext *ctx) {
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitRangeLiteral(CypherParser::RangeLiteralContext *ctx) {
|
||||
if (ctx->integerLiteral().size() == 0U) {
|
||||
// -[*]-
|
||||
return std::pair<int64_t, int64_t>(1LL, LLONG_MAX);
|
||||
@ -279,194 +285,201 @@ antlrcpp::Any CypherMainVisitor::visitRangeLiteral(
|
||||
}
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression(
|
||||
CypherParser::ExpressionContext *ctx) {
|
||||
antlrcpp::Any
|
||||
CypherMainVisitor::visitExpression(CypherParser::ExpressionContext *ctx) {
|
||||
return visitChildren(ctx);
|
||||
}
|
||||
|
||||
// OR.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression12(
|
||||
CypherParser::Expression12Context *ctx) {
|
||||
return LeftAssociativeOperatorExpression(ctx->expression11(),
|
||||
Function::LOGICAL_OR);
|
||||
}
|
||||
|
||||
// XOR.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression11(
|
||||
CypherParser::Expression11Context *ctx) {
|
||||
return LeftAssociativeOperatorExpression(ctx->expression10(),
|
||||
Function::LOGICAL_XOR);
|
||||
}
|
||||
|
||||
// AND.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression10(
|
||||
CypherParser::Expression10Context *ctx) {
|
||||
return LeftAssociativeOperatorExpression(ctx->expression9(),
|
||||
Function::LOGICAL_AND);
|
||||
}
|
||||
|
||||
// NOT.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression9(
|
||||
CypherParser::Expression9Context *ctx) {
|
||||
// TODO: make template similar to LeftAssociativeOperatorExpression for unary
|
||||
// expresssions.
|
||||
auto operand = ctx->expression8()->accept(this).as<std::string>();
|
||||
for (int i = 0; i < (int)ctx->NOT().size(); ++i) {
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] = SimpleExpression{Function::LOGICAL_NOT, {operand}};
|
||||
operand = lhs_id;
|
||||
}
|
||||
return operand;
|
||||
}
|
||||
|
||||
// Comparisons.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression8(
|
||||
CypherParser::Expression8Context *ctx) {
|
||||
if (!ctx->partialComparisonExpression().size()) {
|
||||
// There is no comparison operators. We generate expression7.
|
||||
return ctx->expression7()->accept(this);
|
||||
}
|
||||
|
||||
// There is at least one comparison. We need to generate code for each of
|
||||
// them. We don't call visitPartialComparisonExpression but do everything in
|
||||
// this function and call expression7-s directly. Since every expression7
|
||||
// can be generated twice (because it can appear in two comparisons) code
|
||||
// generated by whole subtree of expression7 must not have any sideeffects.
|
||||
// We handle chained comparisons as defined by mathematics, neo4j handles
|
||||
// them in a very interesting, illogical and incomprehensible way. For
|
||||
// example in neo4j:
|
||||
// 1 < 2 < 3 -> true,
|
||||
// 1 < 2 < 3 < 4 -> false,
|
||||
// 5 > 3 < 5 > 3 -> true,
|
||||
// 4 <= 5 < 7 > 6 -> false
|
||||
// All of those comparisons evaluate to true in memgraph.
|
||||
std::vector<std::string> children_ids;
|
||||
children_ids.push_back(ctx->expression7()->accept(this).as<std::string>());
|
||||
auto partial_comparison_expressions = ctx->partialComparisonExpression();
|
||||
for (auto *child : partial_comparison_expressions) {
|
||||
children_ids.push_back(child->accept(this).as<std::string>());
|
||||
}
|
||||
|
||||
// Make all comparisons.
|
||||
std::string first_operand = children_ids[0];
|
||||
std::vector<std::string> comparison_ids;
|
||||
for (int i = 0; i < (int)partial_comparison_expressions.size(); ++i) {
|
||||
auto *expr = partial_comparison_expressions[i];
|
||||
auto op = [](CypherParser::PartialComparisonExpressionContext *expr) {
|
||||
if (expr->getToken(kEqTokenId, 0)) {
|
||||
return Function::EQ;
|
||||
} else if (expr->getToken(kNeTokenId1, 0) ||
|
||||
expr->getToken(kNeTokenId2, 0)) {
|
||||
return Function::NE;
|
||||
} else if (expr->getToken(kLtTokenId, 0)) {
|
||||
return Function::LT;
|
||||
} else if (expr->getToken(kGtTokenId, 0)) {
|
||||
return Function::GT;
|
||||
} else if (expr->getToken(kLeTokenId, 0)) {
|
||||
return Function::LE;
|
||||
} else if (expr->getToken(kGeTokenId, 0)) {
|
||||
return Function::GE;
|
||||
}
|
||||
assert(false);
|
||||
return Function::GE;
|
||||
}(expr);
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] =
|
||||
SimpleExpression{op, {first_operand, children_ids[i + 1]}};
|
||||
first_operand = lhs_id;
|
||||
comparison_ids.push_back(lhs_id);
|
||||
}
|
||||
|
||||
first_operand = comparison_ids[0];
|
||||
// Calculate logical and of results of comparisons.
|
||||
for (int i = 1; i < (int)comparison_ids.size(); ++i) {
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] = SimpleExpression{
|
||||
Function::LOGICAL_AND, {first_operand, comparison_ids[i]}};
|
||||
first_operand = lhs_id;
|
||||
}
|
||||
return first_operand;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPartialComparisonExpression(
|
||||
CypherParser::PartialComparisonExpressionContext *) {
|
||||
debug_assert(false, "Should never be called. See documentation in hpp.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Addition and subtraction.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression7(
|
||||
CypherParser::Expression7Context *ctx) {
|
||||
return LeftAssociativeOperatorExpression(
|
||||
ctx->expression6(),
|
||||
MapTokensToOperators(ctx, {{kPlusTokenId, Function::ADDITION},
|
||||
{kMinusTokenId, Function::SUBTRACTION}}));
|
||||
}
|
||||
|
||||
// Multiplication, division, modding.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression6(
|
||||
CypherParser::Expression6Context *ctx) {
|
||||
return LeftAssociativeOperatorExpression(
|
||||
ctx->expression5(),
|
||||
MapTokensToOperators(ctx, {{kMultTokenId, Function::MULTIPLICATION},
|
||||
{kDivTokenId, Function::DIVISION},
|
||||
{kModTokenId, Function::MODULO}}));
|
||||
}
|
||||
|
||||
// Power.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression5(
|
||||
CypherParser::Expression5Context *ctx) {
|
||||
if (ctx->expression4().size() > 1u) {
|
||||
// TODO: implement power operator. In neo4j power is right associative and
|
||||
// int^int -> float.
|
||||
throw SemanticException();
|
||||
}
|
||||
return visitChildren(ctx);
|
||||
}
|
||||
|
||||
// Unary minus and plus.
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression4(
|
||||
CypherParser::Expression4Context *ctx) {
|
||||
auto ops =
|
||||
MapTokensToOperators(ctx, {{kUnaryPlusTokenId, Function::UNARY_PLUS},
|
||||
{kUnaryMinusTokenId, Function::UNARY_MINUS}});
|
||||
auto operand = ctx->expression3()->accept(this).as<std::string>();
|
||||
for (int i = 0; i < (int)ops.size(); ++i) {
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] = SimpleExpression{ops[i], {operand}};
|
||||
operand = lhs_id;
|
||||
}
|
||||
return operand;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression3(
|
||||
CypherParser::Expression3Context *ctx) {
|
||||
// If there is only one child we don't need to generate any code in this since
|
||||
// that child is expression2. Other operations are not implemented at the
|
||||
// moment.
|
||||
// TODO: implement this.
|
||||
if (ctx->children.size() > 1u) {
|
||||
throw SemanticException();
|
||||
}
|
||||
return visitChildren(ctx);
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitExpression2(
|
||||
CypherParser::Expression2Context *ctx) {
|
||||
if (ctx->nodeLabels().size()) {
|
||||
// TODO: Implement this. We don't currently support label checking in
|
||||
// expresssion.
|
||||
throw SemanticException();
|
||||
}
|
||||
auto operand = ctx->atom()->accept(this).as<std::string>();
|
||||
for (int i = 0; i < (int)ctx->propertyLookup().size(); ++i) {
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] =
|
||||
SimpleExpression{Function::PROPERTY_GETTER, {operand}};
|
||||
operand = lhs_id;
|
||||
}
|
||||
return operand;
|
||||
}
|
||||
//// OR.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression12(CypherParser::Expression12Context *ctx)
|
||||
// {
|
||||
// return LeftAssociativeOperatorExpression(ctx->expression11(),
|
||||
// Function::LOGICAL_OR);
|
||||
//}
|
||||
//
|
||||
//// XOR.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression11(CypherParser::Expression11Context *ctx)
|
||||
// {
|
||||
// return LeftAssociativeOperatorExpression(ctx->expression10(),
|
||||
// Function::LOGICAL_XOR);
|
||||
//}
|
||||
//
|
||||
//// AND.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression10(CypherParser::Expression10Context *ctx)
|
||||
// {
|
||||
// return LeftAssociativeOperatorExpression(ctx->expression9(),
|
||||
// Function::LOGICAL_AND);
|
||||
//}
|
||||
//
|
||||
//// NOT.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression9(CypherParser::Expression9Context *ctx) {
|
||||
// // TODO: make template similar to LeftAssociativeOperatorExpression for
|
||||
// unary
|
||||
// // expresssions.
|
||||
// auto operand = ctx->expression8()->accept(this).as<std::string>();
|
||||
// for (int i = 0; i < (int)ctx->NOT().size(); ++i) {
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] = SimpleExpression{Function::LOGICAL_NOT,
|
||||
// {operand}};
|
||||
// operand = lhs_id;
|
||||
// }
|
||||
// return operand;
|
||||
//}
|
||||
//
|
||||
//// Comparisons.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression8(CypherParser::Expression8Context *ctx) {
|
||||
// if (!ctx->partialComparisonExpression().size()) {
|
||||
// // There is no comparison operators. We generate expression7.
|
||||
// return ctx->expression7()->accept(this);
|
||||
// }
|
||||
//
|
||||
// // There is at least one comparison. We need to generate code for each of
|
||||
// // them. We don't call visitPartialComparisonExpression but do everything in
|
||||
// // this function and call expression7-s directly. Since every expression7
|
||||
// // can be generated twice (because it can appear in two comparisons) code
|
||||
// // generated by whole subtree of expression7 must not have any sideeffects.
|
||||
// // We handle chained comparisons as defined by mathematics, neo4j handles
|
||||
// // them in a very interesting, illogical and incomprehensible way. For
|
||||
// // example in neo4j:
|
||||
// // 1 < 2 < 3 -> true,
|
||||
// // 1 < 2 < 3 < 4 -> false,
|
||||
// // 5 > 3 < 5 > 3 -> true,
|
||||
// // 4 <= 5 < 7 > 6 -> false
|
||||
// // All of those comparisons evaluate to true in memgraph.
|
||||
// std::vector<std::string> children_ids;
|
||||
// children_ids.push_back(ctx->expression7()->accept(this).as<std::string>());
|
||||
// auto partial_comparison_expressions = ctx->partialComparisonExpression();
|
||||
// for (auto *child : partial_comparison_expressions) {
|
||||
// children_ids.push_back(child->accept(this).as<std::string>());
|
||||
// }
|
||||
//
|
||||
// // Make all comparisons.
|
||||
// std::string first_operand = children_ids[0];
|
||||
// std::vector<std::string> comparison_ids;
|
||||
// for (int i = 0; i < (int)partial_comparison_expressions.size(); ++i) {
|
||||
// auto *expr = partial_comparison_expressions[i];
|
||||
// auto op = [](CypherParser::PartialComparisonExpressionContext *expr) {
|
||||
// if (expr->getToken(kEqTokenId, 0)) {
|
||||
// return Function::EQ;
|
||||
// } else if (expr->getToken(kNeTokenId1, 0) ||
|
||||
// expr->getToken(kNeTokenId2, 0)) {
|
||||
// return Function::NE;
|
||||
// } else if (expr->getToken(kLtTokenId, 0)) {
|
||||
// return Function::LT;
|
||||
// } else if (expr->getToken(kGtTokenId, 0)) {
|
||||
// return Function::GT;
|
||||
// } else if (expr->getToken(kLeTokenId, 0)) {
|
||||
// return Function::LE;
|
||||
// } else if (expr->getToken(kGeTokenId, 0)) {
|
||||
// return Function::GE;
|
||||
// }
|
||||
// assert(false);
|
||||
// return Function::GE;
|
||||
// }(expr);
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] =
|
||||
// SimpleExpression{op, {first_operand, children_ids[i + 1]}};
|
||||
// first_operand = lhs_id;
|
||||
// comparison_ids.push_back(lhs_id);
|
||||
// }
|
||||
//
|
||||
// first_operand = comparison_ids[0];
|
||||
// // Calculate logical and of results of comparisons.
|
||||
// for (int i = 1; i < (int)comparison_ids.size(); ++i) {
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] = SimpleExpression{
|
||||
// Function::LOGICAL_AND, {first_operand, comparison_ids[i]}};
|
||||
// first_operand = lhs_id;
|
||||
// }
|
||||
// return first_operand;
|
||||
//}
|
||||
//
|
||||
// antlrcpp::Any CypherMainVisitor::visitPartialComparisonExpression(
|
||||
// CypherParser::PartialComparisonExpressionContext *) {
|
||||
// debug_assert(false, "Should never be called. See documentation in hpp.");
|
||||
// return 0;
|
||||
//}
|
||||
//
|
||||
//// Addition and subtraction.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression7(CypherParser::Expression7Context *ctx) {
|
||||
// return LeftAssociativeOperatorExpression(
|
||||
// ctx->expression6(),
|
||||
// MapTokensToOperators(ctx, {{kPlusTokenId, Function::ADDITION},
|
||||
// {kMinusTokenId, Function::SUBTRACTION}}));
|
||||
//}
|
||||
//
|
||||
//// Multiplication, division, modding.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression6(CypherParser::Expression6Context *ctx) {
|
||||
// return LeftAssociativeOperatorExpression(
|
||||
// ctx->expression5(),
|
||||
// MapTokensToOperators(ctx, {{kMultTokenId, Function::MULTIPLICATION},
|
||||
// {kDivTokenId, Function::DIVISION},
|
||||
// {kModTokenId, Function::MODULO}}));
|
||||
//}
|
||||
//
|
||||
//// Power.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression5(CypherParser::Expression5Context *ctx) {
|
||||
// if (ctx->expression4().size() > 1u) {
|
||||
// // TODO: implement power operator. In neo4j power is right associative and
|
||||
// // int^int -> float.
|
||||
// throw SemanticException();
|
||||
// }
|
||||
// return visitChildren(ctx);
|
||||
//}
|
||||
//
|
||||
//// Unary minus and plus.
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression4(CypherParser::Expression4Context *ctx) {
|
||||
// auto ops =
|
||||
// MapTokensToOperators(ctx, {{kUnaryPlusTokenId, Function::UNARY_PLUS},
|
||||
// {kUnaryMinusTokenId,
|
||||
// Function::UNARY_MINUS}});
|
||||
// auto operand = ctx->expression3()->accept(this).as<std::string>();
|
||||
// for (int i = 0; i < (int)ops.size(); ++i) {
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] = SimpleExpression{ops[i], {operand}};
|
||||
// operand = lhs_id;
|
||||
// }
|
||||
// return operand;
|
||||
//}
|
||||
//
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression3(CypherParser::Expression3Context *ctx) {
|
||||
// // If there is only one child we don't need to generate any code in this
|
||||
// since
|
||||
// // that child is expression2. Other operations are not implemented at the
|
||||
// // moment.
|
||||
// // TODO: implement this.
|
||||
// if (ctx->children.size() > 1u) {
|
||||
// throw SemanticException();
|
||||
// }
|
||||
// return visitChildren(ctx);
|
||||
//}
|
||||
//
|
||||
// antlrcpp::Any
|
||||
// CypherMainVisitor::visitExpression2(CypherParser::Expression2Context *ctx) {
|
||||
// if (ctx->nodeLabels().size()) {
|
||||
// // TODO: Implement this. We don't currently support label checking in
|
||||
// // expresssion.
|
||||
// throw SemanticException();
|
||||
// }
|
||||
// auto operand = ctx->atom()->accept(this).as<std::string>();
|
||||
// for (int i = 0; i < (int)ctx->propertyLookup().size(); ++i) {
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] =
|
||||
// SimpleExpression{Function::PROPERTY_GETTER, {operand}};
|
||||
// operand = lhs_id;
|
||||
// }
|
||||
// return operand;
|
||||
//}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitAtom(CypherParser::AtomContext *ctx) {
|
||||
if (ctx->literal()) {
|
||||
@ -479,10 +492,11 @@ antlrcpp::Any CypherMainVisitor::visitAtom(CypherParser::AtomContext *ctx) {
|
||||
// appear only in tests and real queries will be stripped.
|
||||
// TODO: Either parse it correctly or raise exception. If exception is
|
||||
// raised it tests should also use params instead of literals.
|
||||
auto text = ctx->literal()->getText();
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] = SimpleExpression{Function::LITERAL, {text}};
|
||||
return lhs_id;
|
||||
// auto text = ctx->literal()->getText();
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] = SimpleExpression{Function::LITERAL, {text}};
|
||||
// return lhs_id;
|
||||
throw std::exception();
|
||||
} else if (ctx->parameter()) {
|
||||
// This is once again potential security risk. We shouldn't output text
|
||||
// given in user query as parameter name directly to the code. Stripper
|
||||
@ -490,25 +504,20 @@ antlrcpp::Any CypherMainVisitor::visitAtom(CypherParser::AtomContext *ctx) {
|
||||
// allow only parameters with numeric names. At the moment this is not a
|
||||
// problem since we don't accept user's parameters but only ours.
|
||||
// TODO: revise this.
|
||||
auto text = ctx->literal()->getText();
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] = SimpleExpression{Function::PARAMETER, {text}};
|
||||
return lhs_id;
|
||||
// auto text = ctx->literal()->getText();
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] = SimpleExpression{Function::PARAMETER, {text}};
|
||||
throw std::exception();
|
||||
// return lhs_id;
|
||||
} else if (ctx->parenthesizedExpression()) {
|
||||
return ctx->parenthesizedExpression()->accept(this);
|
||||
} else if (ctx->variable()) {
|
||||
// TODO: revise this. Is it possible in some atom to use not declared
|
||||
// variable. Is it correct to always use last ids_map?
|
||||
auto &curr_id_map = ids_map_.back();
|
||||
auto variable = ctx->variable()->accept(this).as<std::string>();
|
||||
if (curr_id_map.find(variable) == curr_id_map.end()) {
|
||||
throw SemanticException();
|
||||
}
|
||||
return curr_id_map[variable];
|
||||
std::string variable = ctx->variable()->accept(this);
|
||||
return std::make_shared<Ident>(ctx_.next_uid(), variable);
|
||||
}
|
||||
// TODO: Implement this. We don't support comprehensions, functions,
|
||||
// filtering... at the moment.
|
||||
throw SemanticException();
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitIntegerLiteral(
|
||||
@ -517,7 +526,7 @@ antlrcpp::Any CypherMainVisitor::visitIntegerLiteral(
|
||||
try {
|
||||
t = std::stoll(ctx->getText(), 0, 0);
|
||||
} catch (std::out_of_range) {
|
||||
throw SemanticException();
|
||||
throw std::exception();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
@ -1,53 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "antlr4-runtime.h"
|
||||
#include "query/backend/cpp/compiler_structures.hpp"
|
||||
#include "query/frontend/opencypher/generated/CypherBaseVisitor.h"
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "antlr4-runtime.h"
|
||||
#include "query/context.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/opencypher/generated/CypherBaseVisitor.h"
|
||||
|
||||
namespace query {
|
||||
namespace frontend {
|
||||
|
||||
using query::Context;
|
||||
using antlropencypher::CypherParser;
|
||||
|
||||
class CypherMainVisitor : public antlropencypher::CypherBaseVisitor {
|
||||
public:
|
||||
CypherMainVisitor(Context &ctx) : ctx_(ctx) {}
|
||||
|
||||
private:
|
||||
// Return new output code id.
|
||||
// TODO: Should we generate ids with more readable names: node_1,
|
||||
// relationship_5, temporary_2...?
|
||||
std::string new_id() const {
|
||||
static int next_id = 0;
|
||||
return "id" + std::to_string(next_id++);
|
||||
}
|
||||
// template <typename TExpression>
|
||||
// antlrcpp::Any
|
||||
// LeftAssociativeOperatorExpression(std::vector<TExpression *> children,
|
||||
// std::vector<Function> ops) {
|
||||
// assert(children.size());
|
||||
// std::vector<std::string> children_ids;
|
||||
//
|
||||
// for (auto *child : children) {
|
||||
// children_ids.push_back(child->accept(this).template
|
||||
// as<std::string>());
|
||||
// }
|
||||
//
|
||||
// std::string first_operand = children_ids[0];
|
||||
// for (int i = 0; i < (int)ops.size(); ++i) {
|
||||
// auto lhs_id = new_id();
|
||||
// symbol_table_[lhs_id] =
|
||||
// SimpleExpression{ops[i], {first_operand, children_ids[i + 1]}};
|
||||
// first_operand = lhs_id;
|
||||
// }
|
||||
// return first_operand;
|
||||
// }
|
||||
//
|
||||
// template <typename TExpression>
|
||||
// antlrcpp::Any
|
||||
// LeftAssociativeOperatorExpression(std::vector<TExpression *> children,
|
||||
// Function op) {
|
||||
// return LeftAssociativeOperatorExpression(
|
||||
// children, std::vector<Function>((int)children.size() - 1, op));
|
||||
// }
|
||||
|
||||
template <typename TExpression>
|
||||
antlrcpp::Any
|
||||
LeftAssociativeOperatorExpression(std::vector<TExpression *> children,
|
||||
std::vector<Function> ops) {
|
||||
assert(children.size());
|
||||
std::vector<std::string> children_ids;
|
||||
visitSingleQuery(CypherParser::SingleQueryContext *ctx) override;
|
||||
|
||||
for (auto *child : children) {
|
||||
children_ids.push_back(child->accept(this).template as<std::string>());
|
||||
}
|
||||
|
||||
std::string first_operand = children_ids[0];
|
||||
for (int i = 0; i < (int)ops.size(); ++i) {
|
||||
auto lhs_id = new_id();
|
||||
symbol_table_[lhs_id] =
|
||||
SimpleExpression{ops[i], {first_operand, children_ids[i + 1]}};
|
||||
first_operand = lhs_id;
|
||||
}
|
||||
return first_operand;
|
||||
}
|
||||
|
||||
template <typename TExpression>
|
||||
antlrcpp::Any
|
||||
LeftAssociativeOperatorExpression(std::vector<TExpression *> children,
|
||||
Function op) {
|
||||
return LeftAssociativeOperatorExpression(
|
||||
children, std::vector<Function>((int)children.size() - 1, op));
|
||||
}
|
||||
visitCypherMatch(CypherParser::CypherMatchContext *ctx) override;
|
||||
|
||||
antlrcpp::Any
|
||||
visitReturnItems(CypherParser::ReturnItemsContext *ctx) override;
|
||||
|
||||
antlrcpp::Any visitReturnItem(CypherParser::ReturnItemContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Creates Node and stores it in symbol_table_. If variable is defined it is
|
||||
@ -143,100 +154,100 @@ private:
|
||||
*/
|
||||
antlrcpp::Any visitExpression(CypherParser::ExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* OR.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression12(CypherParser::Expression12Context *ctx) override;
|
||||
///**
|
||||
//* OR.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression12(CypherParser::Expression12Context *ctx) override;
|
||||
|
||||
/**
|
||||
* XOR.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression11(CypherParser::Expression11Context *ctx) override;
|
||||
///**
|
||||
//* XOR.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression11(CypherParser::Expression11Context *ctx) override;
|
||||
|
||||
/**
|
||||
* AND.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression10(CypherParser::Expression10Context *ctx) override;
|
||||
///**
|
||||
//* AND.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression10(CypherParser::Expression10Context *ctx) override;
|
||||
|
||||
/**
|
||||
* NOT.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression9(CypherParser::Expression9Context *ctx) override;
|
||||
///**
|
||||
//* NOT.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression9(CypherParser::Expression9Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Comparisons.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression8(CypherParser::Expression8Context *ctx) override;
|
||||
///**
|
||||
//* Comparisons.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression8(CypherParser::Expression8Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Never call this. Everything related to generating code for comparison
|
||||
* operators should be done in visitExpression8.
|
||||
*/
|
||||
antlrcpp::Any visitPartialComparisonExpression(
|
||||
CypherParser::PartialComparisonExpressionContext *ctx) override;
|
||||
///**
|
||||
//* Never call this. Everything related to generating code for comparison
|
||||
//* operators should be done in visitExpression8.
|
||||
//*/
|
||||
// antlrcpp::Any visitPartialComparisonExpression(
|
||||
// CypherParser::PartialComparisonExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Addition and subtraction.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression7(CypherParser::Expression7Context *ctx) override;
|
||||
///**
|
||||
//* Addition and subtraction.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression7(CypherParser::Expression7Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Multiplication, division, modding.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression6(CypherParser::Expression6Context *ctx) override;
|
||||
///**
|
||||
//* Multiplication, division, modding.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression6(CypherParser::Expression6Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Power.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression5(CypherParser::Expression5Context *ctx) override;
|
||||
///**
|
||||
//* Power.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression5(CypherParser::Expression5Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Unary minus and plus.
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression4(CypherParser::Expression4Context *ctx) override;
|
||||
///**
|
||||
//* Unary minus and plus.
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression4(CypherParser::Expression4Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Element of a list, range of a list...
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression3(CypherParser::Expression3Context *ctx) override;
|
||||
///**
|
||||
//* Element of a list, range of a list...
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression3(CypherParser::Expression3Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Property lookup, test for node labels existence...
|
||||
*
|
||||
* @return string - expression id.
|
||||
*/
|
||||
antlrcpp::Any
|
||||
visitExpression2(CypherParser::Expression2Context *ctx) override;
|
||||
///**
|
||||
//* Property lookup, test for node labels existence...
|
||||
//*
|
||||
//* @return string - expression id.
|
||||
//*/
|
||||
// antlrcpp::Any
|
||||
// visitExpression2(CypherParser::Expression2Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Literals, params, list comprehension...
|
||||
@ -270,25 +281,12 @@ private:
|
||||
antlrcpp::Any
|
||||
visitIntegerLiteral(CypherParser::IntegerLiteralContext *ctx) override;
|
||||
|
||||
public:
|
||||
// TODO: These temporary getters should eventually be replaced with
|
||||
// something
|
||||
// else once we figure out where and how those strctures will be used.
|
||||
// Currently there are needed for testing. cypher_main_visitor test should
|
||||
// be
|
||||
// refactored once these getters are deleted.
|
||||
const auto &ids_map() const { return ids_map_; }
|
||||
const auto &symbol_table() const { return symbol_table_; }
|
||||
|
||||
private:
|
||||
// Mapping of ids (nodes, relationships, values, lists ...) from
|
||||
// query
|
||||
// code to id that is used in generated code;
|
||||
std::vector<std::unordered_map<std::string, std::string>> ids_map_{1};
|
||||
|
||||
// Mapping of output (generated) code ids to appropriate parser
|
||||
// structure.
|
||||
std::unordered_map<std::string, antlrcpp::Any> symbol_table_;
|
||||
Context &ctx_;
|
||||
int next_ident_id_;
|
||||
const std::string kUserIdentPrefix = "u_";
|
||||
const std::string kAnonIdentPrefix = "a_";
|
||||
std::shared_ptr<Query> query_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,22 @@
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "antlr4-runtime.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "query/backend/cpp/cypher_main_visitor.cpp"
|
||||
#include "query/context.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/opencypher/parser.hpp"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace backend::cpp;
|
||||
using query::Context;
|
||||
using namespace query::frontend;
|
||||
|
||||
class ParserTables {
|
||||
template <typename T>
|
||||
@ -27,7 +30,7 @@ class ParserTables {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public:
|
||||
public:
|
||||
ParserTables(const std::string &query) {
|
||||
frontend::opencypher::Parser parser(query);
|
||||
auto *tree = parser.tree();
|
||||
@ -86,7 +89,8 @@ void CompareRelationships(
|
||||
relationship_property_keys,
|
||||
UnorderedElementsAreArray(property_keys.begin(), property_keys.end()));
|
||||
ASSERT_EQ(relationship.has_range, has_range);
|
||||
if (!has_range) return;
|
||||
if (!has_range)
|
||||
return;
|
||||
ASSERT_EQ(relationship.lower_bound, lower_bound);
|
||||
ASSERT_EQ(relationship.upper_bound, upper_bound);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user