Implement UNION query combinator

Summary:
Union query combinator implementation consists of:
 * adjustments to the AST and `cypher_main_visitor`
 * enabling `QueryStripper` to parse multiple `return` statements (not stopping after first)
 * symbol generation for union results
 * union logical operator
 * query plan generator adjustments

Reviewers: teon.banek, mislav.bradac

Reviewed By: teon.banek

Subscribers: pullbot, buda

Differential Revision: https://phabricator.memgraph.io/D1038
This commit is contained in:
Matija Santl 2017-11-29 13:55:02 +01:00
parent dae79413bf
commit 9ad7d82a54
32 changed files with 1837 additions and 690 deletions

View File

@ -11,6 +11,7 @@
* Write-ahead log added.
* `nodes` and `relationships` functions added.
* `UNION` and `UNION ALL` is implemented
### Bug Fixes and Other Changes

View File

@ -23,6 +23,7 @@ database. For that purpose, the following clauses are offered:
* `WHERE`, for filtering the matched data and
* `RETURN`, for defining what will be presented to the user in the result
set.
* `UNION` and `UNION ALL` for combining results from multiple queries.
#### MATCH
@ -216,6 +217,29 @@ Click
[here](https://neo4j.com/docs/developer-manual/current/cypher/functions/aggregating/)
for additional details on how aggregations work.
#### UNION and UNION ALL
openCypher supports combining results from multiple queries into a single result
set. That result will contain rows that belong to queries in the union
respecting the union type.
Using `UNION` will contain only distinct rows while `UNION ALL` will keep all
rows from all given queries.
Restrictions when using `UNION` or `UNION ALL`:
* The number and the names of columns returned by queries must be the same
for all of them.
* There can be only one union type between single queries, ie. a query can't
contain both `UNION` and `UNION ALL`.
Example, get distinct names that are shared between persons and movies:
MATCH(n: Person) RETURN n.name as name UNION MATCH(n: Movie) RETURN n.name as name
Example, get all names that are shared between persons and movies (including duplicates):
MATCH(n: Person) RETURN n.name as name UNION ALL MATCH(n: Movie) RETURN n.name as name
### Writing New Data
For adding new data, you can use the following clauses.
@ -621,7 +645,6 @@ here (especially subtle semantic ones).
#### Unsupported Constructs
* Data importing. Memgraph doesn't support Cypher's CSV importing capabilities.
* The `UNION` keyword for merging query results.
* The `FOREACH` language construct for performing an operation on every list element.
* The `CALL` construct for a standalone function call. This can be expressed using
`RETURN functioncall()`. For example, with Memgraph you can get information about

View File

@ -10,6 +10,7 @@
#include "database/graph_db.hpp"
#include "database/graph_db_datatypes.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/frontend/semantic/symbol.hpp"
#include "query/parameters.hpp"
#include "query/typed_value.hpp"
@ -32,14 +33,14 @@ namespace query {
#define CLONE_BINARY_EXPRESSION \
auto Clone(AstTreeStorage &storage) const->std::remove_const< \
std::remove_pointer<decltype(this)>::type>::type *override { \
std::remove_pointer<decltype(this)>::type>::type * override { \
return storage.Create< \
std::remove_cv<std::remove_reference<decltype(*this)>::type>::type>( \
expression1_->Clone(storage), expression2_->Clone(storage)); \
}
#define CLONE_UNARY_EXPRESSION \
auto Clone(AstTreeStorage &storage) const->std::remove_const< \
std::remove_pointer<decltype(this)>::type>::type *override { \
std::remove_pointer<decltype(this)>::type>::type * override { \
return storage.Create< \
std::remove_cv<std::remove_reference<decltype(*this)>::type>::type>( \
expression_->Clone(storage)); \
@ -1097,7 +1098,7 @@ class Pattern : public Tree {
explicit Pattern(int uid) : Tree(uid) {}
};
// Clauses
// Clause
class Clause : public Tree {
friend class AstTreeStorage;
@ -1108,7 +1109,9 @@ class Clause : public Tree {
Clause *Clone(AstTreeStorage &storage) const override = 0;
};
class Query : public Tree {
// SingleQuery
class SingleQuery : public Tree {
friend class AstTreeStorage;
public:
@ -1122,21 +1125,92 @@ class Query : public Tree {
return visitor.PostVisit(*this);
}
// Creates deep copy of whole ast.
Query *Clone(AstTreeStorage &storage) const override {
auto *query = storage.query();
SingleQuery *Clone(AstTreeStorage &storage) const override {
auto *single_query = storage.Create<SingleQuery>();
for (auto *clause : clauses_) {
query->clauses_.push_back(clause->Clone(storage));
single_query->clauses_.push_back(clause->Clone(storage));
}
return query;
return single_query;
}
std::vector<Clause *> clauses_;
protected:
explicit SingleQuery(int uid) : Tree(uid) {}
};
// CypherUnion
class CypherUnion : public Tree {
friend class AstTreeStorage;
public:
DEFVISITABLE(TreeVisitor<TypedValue>);
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
single_query_->Accept(visitor);
}
return visitor.PostVisit(*this);
}
CypherUnion *Clone(AstTreeStorage &storage) const override {
auto cypher_union = storage.Create<CypherUnion>(distinct_);
cypher_union->single_query_ = single_query_->Clone(storage);
cypher_union->union_symbols_ = union_symbols_;
return cypher_union;
}
SingleQuery *single_query_ = nullptr;
bool distinct_ = false;
/**
* @brief Holds symbols that are created during symbol generation phase.
* These symbols are used when UNION/UNION ALL combines single query results.
*/
std::vector<Symbol> union_symbols_;
protected:
explicit CypherUnion(int uid) : Tree(uid) {}
CypherUnion(int uid, bool distinct) : Tree(uid), distinct_(distinct) {}
};
// Queries
class Query : public Tree {
friend class AstTreeStorage;
public:
DEFVISITABLE(TreeVisitor<TypedValue>);
bool Accept(HierarchicalTreeVisitor &visitor) override {
if (visitor.PreVisit(*this)) {
bool should_continue = single_query_->Accept(visitor);
for (auto *cypher_union : cypher_unions_) {
if (should_continue) {
should_continue = cypher_union->Accept(visitor);
}
}
}
return visitor.PostVisit(*this);
}
// Creates deep copy of whole ast.
Query *Clone(AstTreeStorage &storage) const override {
auto *query = storage.query();
query->single_query_ = single_query_->Clone(storage);
for (auto *cypher_union : cypher_unions_) {
query->cypher_unions_.push_back(cypher_union->Clone(storage));
}
return query;
}
SingleQuery *single_query_ = nullptr;
std::vector<CypherUnion *> cypher_unions_;
protected:
explicit Query(int uid) : Tree(uid) {}
};
// Clauses
class Create : public Clause {
friend class AstTreeStorage;

View File

@ -6,6 +6,8 @@ namespace query {
// Forward declares for Tree visitors.
class Query;
class SingleQuery;
class CypherUnion;
class NamedExpression;
class Identifier;
class PropertyLookup;
@ -58,16 +60,16 @@ class Unwind;
class CreateIndex;
using TreeCompositeVisitor = ::utils::CompositeVisitor<
Query, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator,
AdditionOperator, SubtractionOperator, MultiplicationOperator,
DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator,
InListOperator, ListMapIndexingOperator, ListSlicingOperator, IfOperator,
UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, All, Create,
Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where,
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge,
Unwind>;
Query, SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator,
AndOperator, NotOperator, AdditionOperator, SubtractionOperator,
MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator,
EqualOperator, LessOperator, GreaterOperator, LessEqualOperator,
GreaterEqualOperator, InListOperator, ListMapIndexingOperator,
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator,
IsNullOperator, ListLiteral, MapLiteral, PropertyLookup, LabelsTest,
Aggregation, Function, All, Create, Match, Return, With, Pattern, NodeAtom,
EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
RemoveProperty, RemoveLabels, Merge, Unwind>;
using TreeLeafVisitor = ::utils::LeafVisitor<Identifier, PrimitiveLiteral,
ParameterLookup, CreateIndex>;
@ -83,15 +85,16 @@ class HierarchicalTreeVisitor : public TreeCompositeVisitor,
template <typename TResult>
using TreeVisitor = ::utils::Visitor<
TResult, Query, NamedExpression, OrOperator, XorOperator, AndOperator,
NotOperator, AdditionOperator, SubtractionOperator, MultiplicationOperator,
DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator,
InListOperator, ListMapIndexingOperator, ListSlicingOperator, IfOperator,
UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, All,
ParameterLookup, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom,
Delete, Where, SetProperty, SetProperties, SetLabels, RemoveProperty,
RemoveLabels, Merge, Unwind, Identifier, PrimitiveLiteral, CreateIndex>;
TResult, Query, SingleQuery, CypherUnion, NamedExpression, OrOperator,
XorOperator, AndOperator, NotOperator, AdditionOperator,
SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator,
NotEqualOperator, EqualOperator, LessOperator, GreaterOperator,
LessEqualOperator, GreaterEqualOperator, InListOperator,
ListMapIndexingOperator, ListSlicingOperator, IfOperator, UnaryPlusOperator,
UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral, PropertyLookup,
LabelsTest, Aggregation, Function, All, ParameterLookup, Create, Match,
Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty,
SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind,
Identifier, PrimitiveLiteral, CreateIndex>;
} // namespace query

View File

@ -24,17 +24,51 @@ namespace query::frontend {
const std::string CypherMainVisitor::kAnonPrefix = "anon";
antlrcpp::Any CypherMainVisitor::visitRegularQuery(
CypherParser::RegularQueryContext *ctx) {
query_ = storage_.query();
DCHECK(ctx->singleQuery()) << "Expected single query.";
query_->single_query_ = ctx->singleQuery()->accept(this).as<SingleQuery *>();
// Check that union and union all dont mix
bool has_union = false;
bool has_union_all = false;
for (auto *child : ctx->cypherUnion()) {
if (child->ALL()) {
has_union_all = true;
} else {
has_union = true;
}
if (has_union && has_union_all) {
throw SemanticException("Invalid combination of UNION and UNION ALL.");
}
query_->cypher_unions_.push_back(child->accept(this).as<CypherUnion *>());
}
return query_;
}
antlrcpp::Any CypherMainVisitor::visitCypherUnion(
CypherParser::CypherUnionContext *ctx) {
bool distinct = !ctx->ALL();
auto *cypher_union = storage_.Create<CypherUnion>(distinct);
DCHECK(ctx->singleQuery()) << "Expected single query.";
cypher_union->single_query_ =
ctx->singleQuery()->accept(this).as<SingleQuery *>();
return cypher_union;
}
antlrcpp::Any CypherMainVisitor::visitSingleQuery(
CypherParser::SingleQueryContext *ctx) {
query_ = storage_.query();
auto *single_query = storage_.Create<SingleQuery>();
for (auto *child : ctx->clause()) {
antlrcpp::Any got = child->accept(this);
if (got.is<Clause *>()) {
query_->clauses_.push_back(got.as<Clause *>());
single_query->clauses_.push_back(got.as<Clause *>());
} else {
auto child_clauses = got.as<std::vector<Clause *>>();
query_->clauses_.insert(query_->clauses_.end(), child_clauses.begin(),
child_clauses.end());
single_query->clauses_.insert(single_query->clauses_.end(),
child_clauses.begin(), child_clauses.end());
}
}
@ -47,7 +81,7 @@ antlrcpp::Any CypherMainVisitor::visitSingleQuery(
bool has_return = false;
bool has_optional_match = false;
bool has_create_index = false;
for (Clause *clause : query_->clauses_) {
for (Clause *clause : single_query->clauses_) {
if (dynamic_cast<Unwind *>(clause)) {
if (has_update || has_return) {
throw SemanticException(
@ -86,7 +120,7 @@ antlrcpp::Any CypherMainVisitor::visitSingleQuery(
has_update = has_return = has_optional_match = false;
} else if (dynamic_cast<CreateIndex *>(clause)) {
// If there is CreateIndex clause then there shouldn't be anything else.
if (query_->clauses_.size() != 1U) {
if (single_query->clauses_.size() != 1U) {
throw SemanticException(
"CreateIndex must be only clause in the query.");
}
@ -112,7 +146,7 @@ antlrcpp::Any CypherMainVisitor::visitSingleQuery(
}
}
}
return query_;
return single_query;
}
antlrcpp::Any CypherMainVisitor::visitClause(CypherParser::ClauseContext *ctx) {

View File

@ -133,14 +133,21 @@ class CypherMainVisitor : public antlropencypher::CypherBaseVisitor {
return expression;
}
antlrcpp::Any visitCypherUnion(
CypherParser::CypherUnionContext *ctx) override {
throw utils::NotYetImplemented("UNION");
}
/**
* @return Query*
*/
antlrcpp::Any visitRegularQuery(
CypherParser::RegularQueryContext *ctx) override;
/**
* @return CypherUnion*
*/
antlrcpp::Any visitCypherUnion(
CypherParser::CypherUnionContext *ctx) override;
/**
* @return SingleQuery*
*/
antlrcpp::Any visitSingleQuery(
CypherParser::SingleQueryContext *ctx) override;

View File

@ -0,0 +1,64 @@
#pragma once
#include <string>
namespace query {
class Symbol {
public:
// This is similar to TypedValue::Type, but this has `Any` type.
// TODO: Make a better Type structure which can store a generic List.
enum class Type { Any, Vertex, Edge, Path, Number, EdgeList };
static std::string TypeToString(Type type) {
const char *enum_string[] = {"Any", "Vertex", "Edge",
"Path", "Number", "EdgeList"};
return enum_string[static_cast<int>(type)];
}
Symbol() {}
Symbol(const std::string &name, int position, bool user_declared,
Type type = Type::Any, int token_position = -1)
: name_(name),
position_(position),
user_declared_(user_declared),
type_(type),
token_position_(token_position) {}
bool operator==(const Symbol &other) const {
return position_ == other.position_ && name_ == other.name_ &&
type_ == other.type_;
}
bool operator!=(const Symbol &other) const { return !operator==(other); }
const auto &name() const { return name_; }
int position() const { return position_; }
Type type() const { return type_; }
bool user_declared() const { return user_declared_; }
int token_position() const { return token_position_; }
private:
std::string name_;
int position_;
bool user_declared_ = true;
Type type_ = Type::Any;
int token_position_ = -1;
};
} // namespace query
namespace std {
template <>
struct hash<query::Symbol> {
size_t operator()(const query::Symbol &symbol) const {
size_t prime = 265443599u;
size_t hash = std::hash<int>{}(symbol.position());
hash ^= prime * std::hash<std::string>{}(symbol.name());
hash ^= prime * std::hash<bool>{}(symbol.user_declared());
hash ^= prime * std::hash<int>{}(static_cast<int>(symbol.type()));
return hash;
}
};
} // namespace std

View File

@ -114,6 +114,36 @@ void SymbolGenerator::VisitReturnBody(ReturnBody &body, Where *where) {
scope_.has_aggregation = false;
}
// Query
bool SymbolGenerator::PreVisit(SingleQuery &) {
prev_return_names_ = curr_return_names_;
curr_return_names_.clear();
return true;
}
// Union
bool SymbolGenerator::PreVisit(CypherUnion &) {
scope_ = Scope();
return true;
}
bool SymbolGenerator::PostVisit(CypherUnion &cypher_union) {
if (prev_return_names_ != curr_return_names_) {
throw SemanticException(
"All sub queries in an UNION must have the same column names");
}
// create new symbols for the result of the union
for (const auto &name : curr_return_names_) {
auto symbol = CreateSymbol(name, false);
cypher_union.union_symbols_.push_back(symbol);
}
return true;
}
// Clauses
bool SymbolGenerator::PreVisit(Create &) {
@ -132,6 +162,12 @@ bool SymbolGenerator::PreVisit(Return &ret) {
return false; // We handled the traversal ourselves.
}
bool SymbolGenerator::PostVisit(Return &) {
for (const auto &name_symbol : scope_.symbols)
curr_return_names_.insert(name_symbol.first);
return true;
}
bool SymbolGenerator::PreVisit(With &with) {
scope_.in_with = true;
VisitReturnBody(with.body_, with.where_);

View File

@ -26,10 +26,18 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
using HierarchicalTreeVisitor::Visit;
using typename HierarchicalTreeVisitor::ReturnType;
// Query
bool PreVisit(SingleQuery &) override;
// Union
bool PreVisit(CypherUnion &) override;
bool PostVisit(CypherUnion &) override;
// Clauses
bool PreVisit(Create &) override;
bool PostVisit(Create &) override;
bool PreVisit(Return &) override;
bool PostVisit(Return &) override;
bool PreVisit(With &) override;
bool PreVisit(Where &) override;
bool PostVisit(Where &) override;
@ -119,6 +127,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
SymbolTable &symbol_table_;
Scope scope_;
std::unordered_set<std::string> prev_return_names_;
std::unordered_set<std::string> curr_return_names_;
};
} // namespace query

View File

@ -4,50 +4,10 @@
#include <string>
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol.hpp"
namespace query {
class Symbol {
public:
// This is similar to TypedValue::Type, but this has `Any` type.
// TODO: Make a better Type structure which can store a generic List.
enum class Type { Any, Vertex, Edge, Path, Number, EdgeList };
static std::string TypeToString(Type type) {
const char *enum_string[] = {"Any", "Vertex", "Edge",
"Path", "Number", "EdgeList"};
return enum_string[static_cast<int>(type)];
}
Symbol() {}
Symbol(const std::string &name, int position, bool user_declared,
Type type = Type::Any, int token_position = -1)
: name_(name),
position_(position),
user_declared_(user_declared),
type_(type),
token_position_(token_position) {}
bool operator==(const Symbol &other) const {
return position_ == other.position_ && name_ == other.name_ &&
type_ == other.type_;
}
bool operator!=(const Symbol &other) const { return !operator==(other); }
const auto &name() const { return name_; }
int position() const { return position_; }
Type type() const { return type_; }
bool user_declared() const { return user_declared_; }
int token_position() const { return token_position_; }
private:
std::string name_;
int position_;
bool user_declared_ = true;
Type type_ = Type::Any;
int token_position_ = -1;
};
class SymbolTable {
public:
SymbolTable() {}
@ -73,19 +33,3 @@ class SymbolTable {
};
} // namespace query
namespace std {
template <>
struct hash<query::Symbol> {
size_t operator()(const query::Symbol &symbol) const {
size_t prime = 265443599u;
size_t hash = std::hash<int>{}(symbol.position());
hash ^= prime * std::hash<std::string>{}(symbol.name());
hash ^= prime * std::hash<bool>{}(symbol.user_declared());
hash ^= prime * std::hash<int>{}(static_cast<int>(symbol.type()));
return hash;
}
};
} // namespace std

View File

@ -65,9 +65,8 @@ StrippedQuery::StrippedQuery(const std::string &query) : original_(query) {
// A helper function that stores literal and its token position in a
// literals_. In stripped query text literal is replaced with a new_value.
// new_value can be any value that is lexed as a literal.
auto replace_stripped = [this, &token_strings](int position,
const TypedValue &value,
const std::string &new_value) {
auto replace_stripped = [this, &token_strings](
int position, const TypedValue &value, const std::string &new_value) {
literals_.Add(position, value);
token_strings.push_back(new_value);
};
@ -137,83 +136,85 @@ StrippedQuery::StrippedQuery(const std::string &query) : original_(query) {
query_ = utils::Join(token_strings, " ");
hash_ = fnv(query_);
// Store nonaliased named expressions in returns in named_exprs_.
auto it = std::find_if(tokens.begin(), tokens.end(),
[](const std::pair<Token, std::string> &a) {
return a.second == "return";
});
// There is no RETURN so there is nothing to do here.
if (it == tokens.end()) return;
// Skip RETURN;
++it;
// Now we need to parse cypherReturn production from opencypher grammar.
// Skip leading whitespaces and DISTINCT statemant if there is one.
while (it != tokens.end() && it->first == Token::SPACE) {
auto it = tokens.begin();
while (it != tokens.end()) {
// Store nonaliased named expressions in returns in named_exprs_.
it = std::find_if(it, tokens.end(),
[](const std::pair<Token, std::string> &a) {
return a.second == "return";
});
// There is no RETURN so there is nothing to do here.
if (it == tokens.end()) return;
// Skip RETURN;
++it;
}
if (it != tokens.end() && it->second == "distinct") {
++it;
}
// We assume there is only one return statement and that return statement is
// the last one. Otherwise, query is invalid and either antlr parser or
// cypher_main_visitor will report an error.
// TODO: we shouldn't rely on the fact that those checks will be done
// after this step. We should do them here.
while (it < tokens.end()) {
// Disregard leading whitespace
// Now we need to parse cypherReturn production from opencypher grammar.
// Skip leading whitespaces and DISTINCT statemant if there is one.
while (it != tokens.end() && it->first == Token::SPACE) {
++it;
}
// There is only whitespace, nothing to do...
if (it == tokens.end()) break;
bool has_as = false;
auto last_non_space = it;
auto jt = it;
// We should track number of opened braces and parantheses so that we can
// recognize if comma is a named expression separator or part of the
// list literal / function call.
int num_open_braces = 0;
int num_open_parantheses = 0;
for (;
jt != tokens.end() &&
(jt->second != "," || num_open_braces || num_open_parantheses) &&
jt->second != "order" && jt->second != "skip" && jt->second != "limit";
++jt) {
if (jt->second == "(") {
++num_open_parantheses;
} else if (jt->second == ")") {
--num_open_parantheses;
} else if (jt->second == "[") {
++num_open_braces;
} else if (jt->second == "]") {
--num_open_braces;
}
has_as |= jt->second == "as";
if (jt->first != Token::SPACE) {
last_non_space = jt;
}
if (it != tokens.end() && it->second == "distinct") {
++it;
}
if (!has_as) {
// Named expression is not aliased. Save string disregarding leading and
// trailing whitespaces. Use original_tokens in which case of the keywords
// is not lowercased.
std::string s;
auto begin_token = it - tokens.begin() + original_tokens.begin();
auto end_token =
last_non_space - tokens.begin() + original_tokens.begin() + 1;
for (auto kt = begin_token; kt != end_token; ++kt) {
s += kt->second;
// If the query is invalid, either antlr parser or cypher_main_visitor will
// report an error.
// TODO: we shouldn't rely on the fact that those checks will be done
// after this step. We should do them here.
while (it < tokens.end()) {
// Disregard leading whitespace
while (it != tokens.end() && it->first == Token::SPACE) {
++it;
}
// There is only whitespace, nothing to do...
if (it == tokens.end()) break;
bool has_as = false;
auto last_non_space = it;
auto jt = it;
// We should track number of opened braces and parantheses so that we can
// recognize if comma is a named expression separator or part of the
// list literal / function call.
int num_open_braces = 0;
int num_open_parantheses = 0;
for (; jt != tokens.end() &&
(jt->second != "," || num_open_braces || num_open_parantheses) &&
jt->second != "order" && jt->second != "skip" &&
jt->second != "limit" && jt->second != "union";
++jt) {
if (jt->second == "(") {
++num_open_parantheses;
} else if (jt->second == ")") {
--num_open_parantheses;
} else if (jt->second == "[") {
++num_open_braces;
} else if (jt->second == "]") {
--num_open_braces;
}
has_as |= jt->second == "as";
if (jt->first != Token::SPACE) {
last_non_space = jt;
}
}
if (!has_as) {
// Named expression is not aliased. Save string disregarding leading and
// trailing whitespaces. Use original_tokens in which case of the
// keywords is not lowercased.
std::string s;
auto begin_token = it - tokens.begin() + original_tokens.begin();
auto end_token =
last_non_space - tokens.begin() + original_tokens.begin() + 1;
for (auto kt = begin_token; kt != end_token; ++kt) {
s += kt->second;
}
named_exprs_[position_mapping[it - tokens.begin()]] = s;
}
if (jt != tokens.end() && jt->second == ",") {
// There are more named expressions.
it = jt + 1;
} else {
// We're done with this return statement
break;
}
named_exprs_[position_mapping[it - tokens.begin()]] = s;
}
if (jt != tokens.end() && jt->second == ",") {
// There are more named expressions.
it = jt + 1;
} else {
// We hit ORDER, SKIP or LIMIT -> we are done.
break;
}
}
}

View File

@ -36,6 +36,8 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
}
BLOCK_VISIT(Query);
BLOCK_VISIT(CypherUnion);
BLOCK_VISIT(SingleQuery);
BLOCK_VISIT(Create);
BLOCK_VISIT(Match);
BLOCK_VISIT(Return);

View File

@ -3,7 +3,6 @@
#include <glog/logging.h>
#include "query/exceptions.hpp"
#include "query/plan/cost_estimator.hpp"
#include "query/plan/planner.hpp"
#include "query/plan/vertex_count_cache.hpp"
#include "utils/flag_validation.hpp"
@ -72,29 +71,11 @@ Interpreter::MakeLogicalPlan(AstTreeStorage &ast_storage,
const GraphDbAccessor &db_accessor,
Context &context) {
std::unique_ptr<plan::LogicalOperator> logical_plan;
double min_cost = std::numeric_limits<double>::max();
auto vertex_counts = plan::MakeVertexCountCache(db_accessor);
auto planning_context = plan::MakePlanningContext(
ast_storage, context.symbol_table_, vertex_counts);
if (FLAGS_query_cost_planner) {
auto plans =
plan::MakeLogicalPlan<plan::VariableStartPlanner>(planning_context);
for (auto plan : plans) {
auto cost = EstimatePlanCost(vertex_counts, context.parameters_, *plan);
if (!logical_plan || cost < min_cost) {
// Plans are generated lazily and the current plan will disappear, so
// it's ok to move it.
logical_plan = std::move(plan);
min_cost = cost;
}
}
} else {
logical_plan =
plan::MakeLogicalPlan<plan::RuleBasedPlanner>(planning_context);
min_cost =
EstimatePlanCost(vertex_counts, context.parameters_, *logical_plan);
}
return {std::move(logical_plan), min_cost};
return plan::MakeLogicalPlan(planning_context, context.parameters_,
FLAGS_query_cost_planner);
};
} // namespace query

View File

@ -1,3 +1,5 @@
#pragma once
#include "query/frontend/ast/ast.hpp"
#include "query/parameters.hpp"
#include "query/plan/operator.hpp"

View File

@ -305,10 +305,10 @@ std::unique_ptr<Cursor> ScanAllByLabelPropertyRange::MakeCursor(
context.symbol_table_, db, graph_view_);
auto convert = [&evaluator](const auto &bound)
-> std::experimental::optional<utils::Bound<PropertyValue>> {
if (!bound) return std::experimental::nullopt;
return std::experimental::make_optional(utils::Bound<PropertyValue>(
bound.value().value()->Accept(evaluator), bound.value().type()));
};
if (!bound) return std::experimental::nullopt;
return std::experimental::make_optional(utils::Bound<PropertyValue>(
bound.value().value()->Accept(evaluator), bound.value().type()));
};
return db.Vertices(label_, property_, convert(lower_bound()),
convert(upper_bound()), graph_view_ == GraphView::NEW);
};
@ -2416,4 +2416,65 @@ std::unique_ptr<Cursor> CreateIndex::MakeCursor(GraphDbAccessor &db) const {
return std::make_unique<CreateIndexCursor>(*this, db);
}
Union::Union(const std::shared_ptr<LogicalOperator> &left_op,
const std::shared_ptr<LogicalOperator> &right_op,
const std::vector<Symbol> &union_symbols,
const std::vector<Symbol> &left_symbols,
const std::vector<Symbol> &right_symbols)
: left_op_(left_op),
right_op_(right_op),
union_symbols_(union_symbols),
left_symbols_(left_symbols),
right_symbols_(right_symbols) {}
std::unique_ptr<Cursor> Union::MakeCursor(GraphDbAccessor &db) const {
return std::make_unique<Union::UnionCursor>(*this, db);
}
bool Union::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
if (visitor.PreVisit(*this)) {
if (left_op_->Accept(visitor)) {
right_op_->Accept(visitor);
}
}
return visitor.PostVisit(*this);
}
std::vector<Symbol> Union::OutputSymbols(const SymbolTable &) const {
return union_symbols_;
}
Union::UnionCursor::UnionCursor(const Union &self, GraphDbAccessor &db)
: self_(self),
left_cursor_(self.left_op_->MakeCursor(db)),
right_cursor_(self.right_op_->MakeCursor(db)) {}
bool Union::UnionCursor::Pull(Frame &frame, Context &context) {
std::unordered_map<std::string, TypedValue> results;
if (left_cursor_->Pull(frame, context)) {
// collect values from the left child
for (const auto &output_symbol : self_.left_symbols_) {
results[output_symbol.name()] = frame[output_symbol];
}
} else if (right_cursor_->Pull(frame, context)) {
// collect values from the right child
for (const auto &output_symbol : self_.right_symbols_) {
results[output_symbol.name()] = frame[output_symbol];
}
} else {
return false;
}
// put collected values on frame under union symbols
for (const auto &symbol : self_.union_symbols_) {
frame[symbol] = results[symbol.name()];
}
return true;
}
void Union::UnionCursor::Reset() {
left_cursor_->Reset();
right_cursor_->Reset();
}
} // namespace query::plan

View File

@ -90,6 +90,7 @@ class Optional;
class Unwind;
class Distinct;
class CreateIndex;
class Union;
using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
@ -98,7 +99,7 @@ using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor<
SetProperties, SetLabels, RemoveProperty, RemoveLabels,
ExpandUniquenessFilter<VertexAccessor>,
ExpandUniquenessFilter<EdgeAccessor>, Accumulate, AdvanceCommand, Aggregate,
Skip, Limit, OrderBy, Merge, Optional, Unwind, Distinct>;
Skip, Limit, OrderBy, Merge, Optional, Unwind, Distinct, Union>;
using LogicalOperatorLeafVisitor = ::utils::LeafVisitor<Once, CreateIndex>;
@ -1549,5 +1550,39 @@ class CreateIndex : public LogicalOperator {
GraphDbTypes::Property property_;
};
/**
* A logical operator that applies UNION operator on inputs and places the
* result on the frame.
*
* This operator takes two inputs, a vector of symbols for the result, and
* vectors of symbols used by each of the inputs.
*/
class Union : public LogicalOperator {
public:
Union(const std::shared_ptr<LogicalOperator> &left_op,
const std::shared_ptr<LogicalOperator> &right_op,
const std::vector<Symbol> &union_symbols,
const std::vector<Symbol> &left_symbols,
const std::vector<Symbol> &right_symbols);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) const override;
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
private:
const std::shared_ptr<LogicalOperator> left_op_, right_op_;
const std::vector<Symbol> union_symbols_, left_symbols_, right_symbols_;
class UnionCursor : public Cursor {
public:
UnionCursor(const Union &self, GraphDbAccessor &db);
bool Pull(Frame &, Context &) override;
void Reset() override;
private:
const Union &self_;
const std::unique_ptr<Cursor> left_cursor_, right_cursor_;
};
};
} // namespace plan
} // namespace query

View File

@ -4,9 +4,11 @@
#pragma once
#include "query/plan/cost_estimator.hpp"
#include "query/plan/preprocess.hpp"
#include "query/plan/rule_based_planner.hpp"
#include "query/plan/variable_start_planner.hpp"
#include "query/plan/vertex_count_cache.hpp"
namespace query {
@ -15,10 +17,12 @@ class SymbolTable;
namespace plan {
/// @brief Generates the LogicalOperator tree and returns the resulting plan.
/// @brief Generates the LogicalOperator tree for a single query and returns the
/// resulting plan.
///
/// @tparam TPlanner Type of the planner used for generation.
/// @tparam TDbAccessor Type of the database accessor used for generation.
/// @param vector of @c SingleQueryPart from the single query
/// @param context PlanningContext used for generating plans.
/// @return @c PlanResult which depends on the @c TPlanner used.
///
@ -26,11 +30,73 @@ namespace plan {
/// @sa RuleBasedPlanner
/// @sa VariableStartPlanner
template <template <class> class TPlanner, class TDbAccessor>
auto MakeLogicalPlan(PlanningContext<TDbAccessor> &context) {
auto MakeLogicalPlanForSingleQuery(
std::vector<SingleQueryPart> single_query_parts,
PlanningContext<TDbAccessor> &context) {
context.bound_symbols.clear();
return TPlanner<decltype(context)>(context).Plan(single_query_parts);
}
/// Generates the LogicalOperator tree and returns the resulting plan.
///
/// @tparam TPlanningContext Type of the context used.
/// @param context PlanningContext used for generating plans.
/// @param parameters Parameters used in query .
/// @param boolean flag use_variable_planner to choose which planner to use
/// @return pair consisting of the plan's first logical operator @c
/// LogicalOperator and the estimated cost of that plan
template <class TPlanningContext>
auto MakeLogicalPlan(TPlanningContext &context, const Parameters &parameters,
bool use_variable_planner) {
auto query_parts =
CollectQueryParts(context.symbol_table, context.ast_storage);
return TPlanner<decltype(context)>(context).Plan(query_parts);
auto &vertex_counts = context.db;
double total_cost = 0;
std::unique_ptr<LogicalOperator> last_op;
for (const auto &query_part : query_parts.query_parts) {
std::unique_ptr<LogicalOperator> op;
double min_cost = std::numeric_limits<double>::max();
if (use_variable_planner) {
auto plans = MakeLogicalPlanForSingleQuery<VariableStartPlanner>(
query_part.single_query_parts, context);
for (auto plan : plans) {
auto cost = EstimatePlanCost(vertex_counts, parameters, *plan);
if (!op || cost < min_cost) {
// Plans are generated lazily and the current plan will disappear, so
// it's ok to move it.
op = std::move(plan);
min_cost = cost;
}
}
} else {
op = MakeLogicalPlanForSingleQuery<RuleBasedPlanner>(
query_part.single_query_parts, context);
min_cost = EstimatePlanCost(vertex_counts, parameters, *op);
}
total_cost += min_cost;
if (auto *union_ =
dynamic_cast<CypherUnion *>(query_part.query_combinator)) {
std::shared_ptr<LogicalOperator> curr_op(std::move(op));
std::shared_ptr<LogicalOperator> prev_op(std::move(last_op));
last_op = std::unique_ptr<LogicalOperator>(
impl::GenUnion(*union_, prev_op, curr_op, context.symbol_table));
} else if (query_part.query_combinator) {
throw utils::NotYetImplemented("query combinator");
} else {
last_op = std::move(op);
}
}
if (query_parts.distinct) {
std::shared_ptr<LogicalOperator> prev_op(std::move(last_op));
last_op = std::make_unique<Distinct>(
prev_op, prev_op->OutputSymbols(context.symbol_table));
}
return std::make_pair(std::move(last_op), total_cost);
}
} // namespace plan

View File

@ -463,12 +463,12 @@ void Filters::AnalyzeAndStoreFilter(Expression *expr,
// Converts a Query to multiple QueryParts. In the process new Ast nodes may be
// created, e.g. filter expressions.
std::vector<QueryPart> CollectQueryParts(SymbolTable &symbol_table,
AstTreeStorage &storage) {
auto query = storage.query();
std::vector<QueryPart> query_parts(1);
std::vector<SingleQueryPart> CollectSingleQueryParts(
SymbolTable &symbol_table, AstTreeStorage &storage,
SingleQuery *single_query) {
std::vector<SingleQueryPart> query_parts(1);
auto *query_part = &query_parts.back();
for (auto &clause : query->clauses_) {
for (auto &clause : single_query->clauses_) {
if (auto *match = dynamic_cast<Match *>(clause)) {
if (match->optional_) {
query_part->optional_matching.emplace_back(Matching{});
@ -488,10 +488,9 @@ std::vector<QueryPart> CollectQueryParts(SymbolTable &symbol_table,
} else if (dynamic_cast<With *>(clause) ||
dynamic_cast<query::Unwind *>(clause)) {
// This query part is done, continue with a new one.
query_parts.emplace_back(QueryPart{});
query_parts.emplace_back(SingleQueryPart{});
query_part = &query_parts.back();
} else if (dynamic_cast<Return *>(clause)) {
// TODO: Support RETURN UNION ...
return query_parts;
}
}
@ -499,4 +498,29 @@ std::vector<QueryPart> CollectQueryParts(SymbolTable &symbol_table,
return query_parts;
}
QueryParts CollectQueryParts(SymbolTable &symbol_table,
AstTreeStorage &storage) {
auto query = storage.query();
std::vector<QueryPart> query_parts;
bool distinct = false;
if (auto *single_query = query->single_query_) {
query_parts.push_back(QueryPart{
CollectSingleQueryParts(symbol_table, storage, single_query)});
}
for (auto *cypher_union : query->cypher_unions_) {
if (cypher_union->distinct_) {
distinct = true;
}
if (auto *single_query = cypher_union->single_query_) {
query_parts.push_back(QueryPart{
CollectSingleQueryParts(symbol_table, storage, single_query),
cypher_union});
}
}
return QueryParts{query_parts, distinct};
};
} // namespace query::plan

View File

@ -190,14 +190,14 @@ struct Matching {
/// * any of the write clauses.
///
/// For a query `MATCH (n) MERGE (n) -[e]- (m) SET n.x = 42 MERGE (l)` the
/// generated QueryPart will have `matching` generated for the `MATCH`.
/// generated SingleQueryPart will have `matching` generated for the `MATCH`.
/// `remaining_clauses` will contain `Merge`, `SetProperty` and `Merge` clauses
/// in that exact order. The pattern inside the first `MERGE` will be used to
/// generate the first `merge_matching` element, and the second `MERGE` pattern
/// will produce the second `merge_matching` element. This way, if someone
/// traverses `remaining_clauses`, the order of appearance of `Merge` clauses is
/// in the same order as their respective `merge_matching` elements.
struct QueryPart {
struct SingleQueryPart {
/// @brief All `MATCH` clauses merged into one @c Matching.
Matching matching;
/// @brief Each `OPTIONAL MATCH` converted to @c Matching.
@ -216,12 +216,28 @@ struct QueryPart {
std::vector<Clause *> remaining_clauses{};
};
/// Holds query parts of a single query together with the optional information
/// about the combinator used between this single query and the previous one.
struct QueryPart {
std::vector<SingleQueryPart> single_query_parts = {};
/// Optional AST query combinator node
Tree *query_combinator = nullptr;
};
/// Holds query parts of all single queries together with the information
/// whether or not the resulting set should contain distinct elements.
struct QueryParts {
std::vector<QueryPart> query_parts = {};
/// Distinct flag, determined by the query combinator
bool distinct = false;
};
/// @brief Convert the AST to multiple @c QueryParts.
///
/// This function will normalize patterns inside @c Match and @c Merge clauses
/// and do some other preprocessing in order to generate multiple @c QueryPart
/// structures. @c AstTreeStorage and @c SymbolTable may be used to create new
/// AST nodes.
std::vector<QueryPart> CollectQueryParts(SymbolTable &, AstTreeStorage &);
QueryParts CollectQueryParts(SymbolTable &, AstTreeStorage &);
} // namespace query::plan

View File

@ -641,6 +641,15 @@ LogicalOperator *GenWith(With &with, LogicalOperator *input_op,
return last_op;
}
LogicalOperator *GenUnion(CypherUnion &cypher_union,
std::shared_ptr<LogicalOperator> left_op,
std::shared_ptr<LogicalOperator> right_op,
SymbolTable &symbol_table) {
return new Union(left_op, right_op, cypher_union.union_symbols_,
left_op->OutputSymbols(symbol_table),
right_op->OutputSymbols(symbol_table));
}
} // namespace impl
} // namespace query::plan

View File

@ -29,7 +29,6 @@ struct PlanningContext {
/// long enough for the plan generation to finish.
const TDbAccessor &db;
/// @brief Symbol set is used to differentiate cycles in pattern matching.
///
/// During planning, symbols will be added as each operator produces values
/// for them. This way, the operator can be correctly initialized whether to
/// read a symbol or write it. E.g. `MATCH (n) -[r]- (n)` would bind (and
@ -101,6 +100,11 @@ LogicalOperator *GenWith(With &with, LogicalOperator *input_op,
std::unordered_set<Symbol> &bound_symbols,
AstTreeStorage &storage);
LogicalOperator *GenUnion(CypherUnion &cypher_union,
std::shared_ptr<LogicalOperator> left_op,
std::shared_ptr<LogicalOperator> right_op,
SymbolTable &symbol_table);
template <class TBoolOperator>
Expression *BoolJoin(AstTreeStorage &storage, Expression *expr1,
Expression *expr2) {
@ -124,7 +128,7 @@ class RuleBasedPlanner {
/// tree.
using PlanResult = std::unique_ptr<LogicalOperator>;
/// @brief Generates the operator tree based on explicitly set rules.
PlanResult Plan(const std::vector<QueryPart> &query_parts) {
PlanResult Plan(const std::vector<SingleQueryPart> &query_parts) {
auto &context = context_;
LogicalOperator *input_op = nullptr;
// Set to true if a query command writes to the database.

View File

@ -216,7 +216,7 @@ CartesianProduct<VaryMatchingStart> VaryMultiMatchingStarts(
return MakeCartesianProduct(std::move(variants));
}
VaryQueryPartMatching::VaryQueryPartMatching(QueryPart query_part,
VaryQueryPartMatching::VaryQueryPartMatching(SingleQueryPart query_part,
const SymbolTable &symbol_table)
: query_part_(std::move(query_part)),
matchings_(VaryMatchingStart(query_part_.matching, symbol_table)),
@ -226,7 +226,8 @@ VaryQueryPartMatching::VaryQueryPartMatching(QueryPart query_part,
VaryMultiMatchingStarts(query_part_.merge_matching, symbol_table)) {}
VaryQueryPartMatching::iterator::iterator(
const QueryPart &query_part, VaryMatchingStart::iterator matchings_begin,
const SingleQueryPart &query_part,
VaryMatchingStart::iterator matchings_begin,
VaryMatchingStart::iterator matchings_end,
CartesianProduct<VaryMatchingStart>::iterator optional_begin,
CartesianProduct<VaryMatchingStart>::iterator optional_end,

View File

@ -237,17 +237,17 @@ CartesianProduct<VaryMatchingStart> VaryMultiMatchingStarts(
// graph matching is done.
class VaryQueryPartMatching {
public:
VaryQueryPartMatching(QueryPart, const SymbolTable &);
VaryQueryPartMatching(SingleQueryPart, const SymbolTable &);
class iterator {
public:
typedef std::input_iterator_tag iterator_category;
typedef QueryPart value_type;
typedef SingleQueryPart value_type;
typedef long difference_type;
typedef const QueryPart &reference;
typedef const QueryPart *pointer;
typedef const SingleQueryPart &reference;
typedef const SingleQueryPart *pointer;
iterator(const QueryPart &, VaryMatchingStart::iterator,
iterator(const SingleQueryPart &, VaryMatchingStart::iterator,
VaryMatchingStart::iterator,
CartesianProduct<VaryMatchingStart>::iterator,
CartesianProduct<VaryMatchingStart>::iterator,
@ -263,7 +263,7 @@ class VaryQueryPartMatching {
private:
void SetCurrentQueryPart();
QueryPart current_query_part_;
SingleQueryPart current_query_part_;
VaryMatchingStart::iterator matchings_it_;
VaryMatchingStart::iterator matchings_end_;
CartesianProduct<VaryMatchingStart>::iterator optional_it_;
@ -286,7 +286,7 @@ class VaryQueryPartMatching {
}
private:
QueryPart query_part_;
SingleQueryPart query_part_;
// Multiple regular matchings, each starting from different node.
VaryMatchingStart matchings_;
// Multiple optional matchings, where each combination has different starting
@ -312,7 +312,7 @@ class VariableStartPlanner {
// Generates different, equivalent query parts by taking different graph
// matching routes for each query part.
auto VaryQueryMatching(const std::vector<QueryPart> &query_parts,
auto VaryQueryMatching(const std::vector<SingleQueryPart> &query_parts,
const SymbolTable &symbol_table) {
std::vector<impl::VaryQueryPartMatching> alternative_query_parts;
alternative_query_parts.reserve(query_parts.size());
@ -329,7 +329,7 @@ class VariableStartPlanner {
: context_(context) {}
/// @brief Generate multiple plans by varying the order of graph traversal.
auto Plan(const std::vector<QueryPart> &query_parts) {
auto Plan(const std::vector<SingleQueryPart> &query_parts) {
return iter::imap(
[context = &context_](const auto &alternative_query_parts) {
RuleBasedPlanner<TPlanningContext> rule_planner(*context);
@ -343,7 +343,8 @@ class VariableStartPlanner {
/// generated operator trees.
using PlanResult = typename std::result_of<decltype (
&VariableStartPlanner<TPlanningContext>::Plan)(
VariableStartPlanner<TPlanningContext>, std::vector<QueryPart> &)>::type;
VariableStartPlanner<TPlanningContext>,
std::vector<SingleQueryPart> &)>::type;
};
} // namespace query::plan

View File

@ -12,6 +12,7 @@ static void AddChainedMatches(int num_matches, query::AstTreeStorage &storage) {
for (int i = 0; i < num_matches; ++i) {
auto *match = storage.Create<query::Match>();
auto *pattern = storage.Create<query::Pattern>();
auto *single_query = storage.Create<query::SingleQuery>();
pattern->identifier_ = storage.Create<query::Identifier>("path");
match->patterns_.emplace_back(pattern);
std::string node1_name = "node" + std::to_string(i - 1);
@ -22,7 +23,8 @@ static void AddChainedMatches(int num_matches, query::AstTreeStorage &storage) {
query::EdgeAtom::Type::SINGLE, query::EdgeAtom::Direction::BOTH));
pattern->atoms_.emplace_back(storage.Create<query::NodeAtom>(
storage.Create<query::Identifier>("node" + std::to_string(i))));
storage.query()->clauses_.emplace_back(match);
single_query->clauses_.emplace_back(match);
storage.query()->single_query_ = single_query;
}
}
@ -39,12 +41,16 @@ static void BM_PlanChainedMatches(benchmark::State &state) {
storage.query()->Accept(symbol_generator);
auto ctx = query::plan::MakePlanningContext(storage, symbol_table, dba);
state.ResumeTiming();
query::plan::LogicalOperator *current_plan;
auto plans =
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(ctx);
auto query_parts = query::plan::CollectQueryParts(symbol_table, storage);
if (query_parts.query_parts.size() == 0) {
std::exit(EXIT_FAILURE);
}
auto single_query_parts = query_parts.query_parts.at(0).single_query_parts;
auto plans = query::plan::MakeLogicalPlanForSingleQuery<
query::plan::VariableStartPlanner>(single_query_parts, ctx);
for (const auto &plan : plans) {
// Exhaust through all generated plans, since they are lazily generated.
benchmark::DoNotOptimize(current_plan = plan.get());
benchmark::DoNotOptimize(plan.get());
}
}
}
@ -61,6 +67,7 @@ static void AddIndexedMatches(
for (int i = 0; i < num_matches; ++i) {
auto *match = storage.Create<query::Match>();
auto *pattern = storage.Create<query::Pattern>();
auto *single_query = storage.Create<query::SingleQuery>();
pattern->identifier_ = storage.Create<query::Identifier>("path");
match->patterns_.emplace_back(pattern);
std::string node1_name = "node" + std::to_string(i - 1);
@ -69,7 +76,8 @@ static void AddIndexedMatches(
node->labels_.emplace_back(label);
node->properties_[property] = storage.Create<query::PrimitiveLiteral>(i);
pattern->atoms_.emplace_back(node);
storage.query()->clauses_.emplace_back(match);
single_query->clauses_.emplace_back(match);
storage.query()->single_query_ = single_query;
}
}
@ -109,8 +117,13 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
storage.query()->Accept(symbol_generator);
state.ResumeTiming();
auto ctx = query::plan::MakePlanningContext(storage, symbol_table, dba);
auto plans =
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(ctx);
auto query_parts = query::plan::CollectQueryParts(symbol_table, storage);
if (query_parts.query_parts.size() == 0) {
std::exit(EXIT_FAILURE);
}
auto single_query_parts = query_parts.query_parts.at(0).single_query_parts;
auto plans = query::plan::MakeLogicalPlanForSingleQuery<
query::plan::VariableStartPlanner>(single_query_parts, ctx);
for (auto plan : plans) {
query::plan::EstimatePlanCost(dba, parameters, *plan);
}
@ -139,8 +152,13 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(
state.ResumeTiming();
auto ctx =
query::plan::MakePlanningContext(storage, symbol_table, vertex_counts);
auto plans =
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(ctx);
auto query_parts = query::plan::CollectQueryParts(symbol_table, storage);
if (query_parts.query_parts.size() == 0) {
std::exit(EXIT_FAILURE);
}
auto single_query_parts = query_parts.query_parts.at(0).single_query_parts;
auto plans = query::plan::MakeLogicalPlanForSingleQuery<
query::plan::VariableStartPlanner>(single_query_parts, ctx);
for (auto plan : plans) {
query::plan::EstimatePlanCost(vertex_counts, parameters, *plan);
}

View File

@ -639,11 +639,17 @@ query::SymbolTable MakeSymbolTable(const query::AstTreeStorage &ast) {
auto MakeLogicalPlans(query::AstTreeStorage &ast,
query::SymbolTable &symbol_table,
InteractiveDbAccessor &dba) {
auto query_parts = query::plan::CollectQueryParts(symbol_table, ast);
std::vector<std::pair<std::unique_ptr<query::plan::LogicalOperator>, double>>
plans_with_cost;
auto ctx = query::plan::MakePlanningContext(ast, symbol_table, dba);
auto plans =
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(ctx);
if (query_parts.query_parts.size() <= 0) {
std::cerr << "Failed to extract query parts" << std::endl;
std::exit(EXIT_FAILURE);
}
auto plans = query::plan::MakeLogicalPlanForSingleQuery<
query::plan::VariableStartPlanner>(
query_parts.query_parts.at(0).single_query_parts, ctx);
Parameters parameters;
for (auto plan : plans) {
query::plan::CostEstimator<InteractiveDbAccessor> estimator(dba,

View File

@ -0,0 +1,136 @@
Feature: Union
Scenario: Test return *
Given an empty graph
When executing query:
"""
WITH 5 AS X, 3 AS Y RETURN * UNION WITH 9 AS X, 4 AS Y RETURN *;
"""
Then the result should be:
| X | Y |
| 5 | 3 |
| 9 | 4 |
Scenario: Test return * with swapped parameters
Given an empty graph
When executing query:
"""
WITH 5 AS X, 3 AS Y RETURN * UNION WITH 9 AS Y, 4 AS X RETURN *;
"""
Then the result should be:
| X | Y |
| 5 | 3 |
| 4 | 9 |
Scenario: Test return * and named parameters
Given an empty graph
When executing query:
"""
WITH 5 AS X, 3 AS Y RETURN * UNION WITH 9 AS Y, 4 AS X RETURN Y, X;
"""
Then the result should be:
| X | Y |
| 5 | 3 |
| 4 | 9 |
Scenario: Test distinct single elements are returned
Given an empty graph
And having executed
"""
CREATE (a: A{x: 1, y: 1}), (b: A{x: 1, y: 2}), (c: A{x: 2, y: 1}), (d: A{x: 1, y:1})
"""
When executing query:
"""
MATCH (a: A{x:1}) RETURN a.x AS X UNION MATCH (a: A{y:1}) RETURN a.x AS X;
"""
Then the result should be:
| X |
| 1 |
| 2 |
Scenario: Test all single elements are returned
Given an empty graph
And having executed
"""
CREATE (a: A{x: 1, y: 1}), (b: A{x: 1, y: 2}), (c: A{x: 2, y: 1}), (d: A{x: 1, y:1})
"""
When executing query:
"""
MATCH (a: A{x:1}) RETURN a.x AS X UNION ALL MATCH (a: A{y:1}) RETURN a.x AS X;
"""
Then the result should be:
| X |
| 1 |
| 1 |
| 1 |
| 1 |
| 2 |
| 1 |
Scenario: Test distinct elements are returned
Given an empty graph
And having executed
"""
CREATE (a: A{x: 1, y: 1}), (b: A{x: 1, y: 2}), (c: A{x: 2, y: 1}), (d: A{x: 1, y:1})
"""
When executing query:
"""
MATCH (a: A{x:1}) RETURN a.x AS X, a.y AS Y UNION MATCH (a: A{y:1}) RETURN a.x AS X, a.y AS Y;
"""
Then the result should be:
| X | Y |
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
Scenario: Test all elements are returned
Given an empty graph
And having executed
"""
CREATE (a: A{x: 1, y: 1}), (b: A{x: 1, y: 2}), (c: A{x: 2, y: 1}), (d: A{x: 1, y:1})
"""
When executing query:
"""
MATCH (a: A{x:1}) RETURN a.x AS X, a.y AS Y UNION ALL MATCH (a: A{y:1}) RETURN a.x AS X, a.y AS Y;
"""
Then the result should be:
| X | Y |
| 1 | 1 |
| 1 | 2 |
| 1 | 1 |
| 1 | 1 |
| 2 | 1 |
| 1 | 1 |
Scenario: Test union combinator 1
Given an empty graph
When executing query:
"""
MATCH (a) RETURN a.x as V UNION ALL MATCH (a) RETURN a.y AS V UNION MATCH (a) RETURN a.y + a.x AS V;
"""
Then an error should be raised
Scenario: Test union combinator 2
Given an empty graph
When executing query:
"""
MATCH (a) RETURN a.x as V UNION MATCH (a) RETURN a.y AS V UNION ALL MATCH (a) RETURN a.y + a.x AS V;
"""
Then an error should be raised
Scenario: Different names of return expressions
Given an empty graph
When executing query:
"""
RETURN 10 as x UNION RETURN 11 as y
"""
Then an error should be raised
Scenario: Different number of return expressions
Given an empty graph
When executing query:
"""
RETURN 10 as x, 11 as y UNION RETURN 11 as y
"""
Then an error should be raised

File diff suppressed because it is too large Load Diff

View File

@ -217,38 +217,63 @@ auto GetWithPatterns(TWithPatterns *with_patterns,
///
/// Create a query with given clauses.
///
auto GetQuery(AstTreeStorage &storage, Clause *clause) {
storage.query()->clauses_.emplace_back(clause);
return storage.query();
auto GetSingleQuery(SingleQuery *single_query, Clause *clause) {
single_query->clauses_.emplace_back(clause);
return single_query;
}
auto GetQuery(AstTreeStorage &storage, Match *match, Where *where) {
auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where) {
match->where_ = where;
storage.query()->clauses_.emplace_back(match);
return storage.query();
single_query->clauses_.emplace_back(match);
return single_query;
}
auto GetQuery(AstTreeStorage &storage, With *with, Where *where) {
auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where) {
with->where_ = where;
storage.query()->clauses_.emplace_back(with);
return storage.query();
single_query->clauses_.emplace_back(with);
return single_query;
}
template <class... T>
auto GetQuery(AstTreeStorage &storage, Match *match, Where *where,
T *... clauses) {
auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where,
T *... clauses) {
match->where_ = where;
storage.query()->clauses_.emplace_back(match);
return GetQuery(storage, clauses...);
single_query->clauses_.emplace_back(match);
return GetSingleQuery(single_query, clauses...);
}
template <class... T>
auto GetQuery(AstTreeStorage &storage, With *with, Where *where,
T *... clauses) {
auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where,
T *... clauses) {
with->where_ = where;
storage.query()->clauses_.emplace_back(with);
return GetQuery(storage, clauses...);
single_query->clauses_.emplace_back(with);
return GetSingleQuery(single_query, clauses...);
}
template <class... T>
auto GetQuery(AstTreeStorage &storage, Clause *clause, T *... clauses) {
storage.query()->clauses_.emplace_back(clause);
return GetQuery(storage, clauses...);
auto GetSingleQuery(SingleQuery *single_query, Clause *clause, T *... clauses) {
single_query->clauses_.emplace_back(clause);
return GetSingleQuery(single_query, clauses...);
}
auto GetCypherUnion(CypherUnion *cypher_union, SingleQuery *single_query) {
cypher_union->single_query_ = single_query;
return cypher_union;
}
auto GetQuery(AstTreeStorage &storage, SingleQuery *single_query) {
storage.query()->single_query_ = single_query;
return storage.query();
}
auto GetQuery(AstTreeStorage &storage, SingleQuery *single_query,
CypherUnion *cypher_union) {
storage.query()->cypher_unions_.emplace_back(cypher_union);
return GetQuery(storage, single_query);
}
template <class... T>
auto GetQuery(AstTreeStorage &storage, SingleQuery *single_query,
CypherUnion *cypher_union, T *... cypher_unions) {
storage.query()->cypher_unions_.emplace_back(cypher_union);
return GetQuery(storage, single_query, cypher_unions...);
}
// Helper functions for constructing RETURN and WITH clauses.
@ -524,6 +549,14 @@ auto GetMerge(AstTreeStorage &storage, Pattern *pattern, OnMatch on_match,
#define CREATE_INDEX_ON(label, property) \
storage.Create<query::CreateIndex>((label), (property))
#define QUERY(...) query::test_common::GetQuery(storage, __VA_ARGS__)
#define SINGLE_QUERY(...) \
query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__)
#define UNION(...) \
query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), \
__VA_ARGS__)
#define UNION_ALL(...) \
query::test_common::GetCypherUnion(storage.Create<CypherUnion>(false), \
__VA_ARGS__)
// Various operators
#define ADD(expr1, expr2) \
storage.Create<query::AdditionOperator>((expr1), (expr2))

File diff suppressed because it is too large Load Diff

View File

@ -16,13 +16,13 @@ TEST(TestSymbolGenerator, MatchNodeReturn) {
SymbolTable symbol_table;
AstTreeStorage storage;
// MATCH (node_atom_1) RETURN node_atom_1
auto query_ast =
QUERY(MATCH(PATTERN(NODE("node_atom_1"))), RETURN("node_atom_1"));
auto query_ast = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("node_atom_1"))), RETURN("node_atom_1")));
SymbolGenerator symbol_generator(symbol_table);
query_ast->Accept(symbol_generator);
// symbols for pattern, node_atom_1 and named_expr in return
EXPECT_EQ(symbol_table.max_position(), 3);
auto match = dynamic_cast<Match *>(query_ast->clauses_[0]);
auto match = dynamic_cast<Match *>(query_ast->single_query_->clauses_[0]);
auto pattern = match->patterns_[0];
auto pattern_sym = symbol_table[*pattern->identifier_];
EXPECT_EQ(pattern_sym.type(), Symbol::Type::Path);
@ -31,7 +31,7 @@ TEST(TestSymbolGenerator, MatchNodeReturn) {
auto node_sym = symbol_table[*node_atom->identifier_];
EXPECT_EQ(node_sym.name(), "node_atom_1");
EXPECT_EQ(node_sym.type(), Symbol::Type::Vertex);
auto ret = dynamic_cast<Return *>(query_ast->clauses_[1]);
auto ret = dynamic_cast<Return *>(query_ast->single_query_->clauses_[1]);
auto named_expr = ret->body_.named_expressions[0];
auto column_sym = symbol_table[*named_expr];
EXPECT_EQ(node_sym.name(), column_sym.name());
@ -44,13 +44,13 @@ TEST(TestSymbolGenerator, MatchNamedPattern) {
SymbolTable symbol_table;
AstTreeStorage storage;
// MATCH p = (node_atom_1) RETURN node_atom_1
auto query_ast =
QUERY(MATCH(NAMED_PATTERN("p", NODE("node_atom_1"))), RETURN("p"));
auto query_ast = QUERY(SINGLE_QUERY(
MATCH(NAMED_PATTERN("p", NODE("node_atom_1"))), RETURN("p")));
SymbolGenerator symbol_generator(symbol_table);
query_ast->Accept(symbol_generator);
// symbols for p, node_atom_1 and named_expr in return
EXPECT_EQ(symbol_table.max_position(), 3);
auto match = dynamic_cast<Match *>(query_ast->clauses_[0]);
auto match = dynamic_cast<Match *>(query_ast->single_query_->clauses_[0]);
auto pattern = match->patterns_[0];
auto pattern_sym = symbol_table[*pattern->identifier_];
EXPECT_EQ(pattern_sym.type(), Symbol::Type::Path);
@ -64,8 +64,8 @@ TEST(TestSymbolGenerator, MatchUnboundMultiReturn) {
// AST using variable in return bound by naming the previous return
// expression. This is treated as an unbound variable.
// MATCH (node_atom_1) RETURN node_atom_1 AS n, n
auto query_ast = QUERY(MATCH(PATTERN(NODE("node_atom_1"))),
RETURN("node_atom_1", AS("n"), "n"));
auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("node_atom_1"))),
RETURN("node_atom_1", AS("n"), "n")));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query_ast->Accept(symbol_generator), UnboundVariableError);
}
@ -74,7 +74,7 @@ TEST(TestSymbolGenerator, MatchNodeUnboundReturn) {
SymbolTable symbol_table;
AstTreeStorage storage;
// AST with unbound variable in return: MATCH (n) RETURN x
auto query_ast = QUERY(MATCH(PATTERN(NODE("n"))), RETURN("x"));
auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("x")));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query_ast->Accept(symbol_generator), UnboundVariableError);
}
@ -87,7 +87,7 @@ TEST(TestSymbolGenerator, CreatePropertyUnbound) {
GraphDb db;
GraphDbAccessor dba(db);
node->properties_[PROPERTY_PAIR("prop")] = IDENT("x");
auto query_ast = QUERY(CREATE(PATTERN(node)));
auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(node))));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query_ast->Accept(symbol_generator), UnboundVariableError);
}
@ -96,18 +96,18 @@ TEST(TestSymbolGenerator, CreateNodeReturn) {
SymbolTable symbol_table;
AstTreeStorage storage;
// Simple AST returning a created node: CREATE (n) RETURN n
auto query_ast = QUERY(CREATE(PATTERN(NODE("n"))), RETURN("n"));
auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN("n")));
SymbolGenerator symbol_generator(symbol_table);
query_ast->Accept(symbol_generator);
// symbols for pattern, `n` and named_expr
EXPECT_EQ(symbol_table.max_position(), 3);
auto create = dynamic_cast<Create *>(query_ast->clauses_[0]);
auto create = dynamic_cast<Create *>(query_ast->single_query_->clauses_[0]);
auto pattern = create->patterns_[0];
auto node_atom = dynamic_cast<NodeAtom *>(pattern->atoms_[0]);
auto node_sym = symbol_table[*node_atom->identifier_];
EXPECT_EQ(node_sym.name(), "n");
EXPECT_EQ(node_sym.type(), Symbol::Type::Vertex);
auto ret = dynamic_cast<Return *>(query_ast->clauses_[1]);
auto ret = dynamic_cast<Return *>(query_ast->single_query_->clauses_[1]);
auto named_expr = ret->body_.named_expressions[0];
auto column_sym = symbol_table[*named_expr];
EXPECT_EQ(node_sym.name(), column_sym.name());
@ -120,7 +120,8 @@ TEST(TestSymbolGenerator, CreateRedeclareNode) {
SymbolTable symbol_table;
AstTreeStorage storage;
// AST with redeclaring a variable when creating nodes: CREATE (n), (n)
auto query_ast = QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("n"))));
auto query_ast =
QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("n")))));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query_ast->Accept(symbol_generator), RedeclareVariableError);
}
@ -130,8 +131,8 @@ TEST(TestSymbolGenerator, MultiCreateRedeclareNode) {
AstTreeStorage storage;
// AST with redeclaring a variable when creating nodes with multiple creates:
// CREATE (n) CREATE (n)
auto query_ast =
QUERY(CREATE(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n"))));
auto query_ast = QUERY(
SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n")))));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query_ast->Accept(symbol_generator), RedeclareVariableError);
}
@ -140,7 +141,8 @@ TEST(TestSymbolGenerator, MatchCreateRedeclareNode) {
SymbolTable symbol_table;
AstTreeStorage storage;
// AST with redeclaring a match node variable in create: MATCH (n) CREATE (n)
auto query_ast = QUERY(MATCH(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n"))));
auto query_ast = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n")))));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query_ast->Accept(symbol_generator), RedeclareVariableError);
}
@ -153,11 +155,11 @@ TEST(TestSymbolGenerator, MatchCreateRedeclareEdge) {
GraphDb db;
GraphDbAccessor dba(db);
auto relationship = dba.EdgeType("relationship");
auto query =
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
CREATE(PATTERN(NODE("n"),
EDGE("r", EdgeAtom::Direction::OUT, {relationship}),
NODE("l"))));
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
CREATE(PATTERN(NODE("n"),
EDGE("r", EdgeAtom::Direction::OUT, {relationship}),
NODE("l")))));
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), RedeclareVariableError);
}
@ -166,7 +168,8 @@ TEST(TestSymbolGenerator, MatchTypeMismatch) {
AstTreeStorage storage;
// Using an edge variable as a node causes a type mismatch.
// MATCH (n) -[r]-> (r)
auto query = QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("r"))));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("r")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), TypeMismatchError);
@ -176,10 +179,10 @@ TEST(TestSymbolGenerator, MatchCreateTypeMismatch) {
AstTreeStorage storage;
// Using an edge variable as a node causes a type mismatch.
// MATCH (n1) -[r1]- (n2) CREATE (r1) -[r2]-> (n2)
auto query =
QUERY(MATCH(PATTERN(NODE("n1"), EDGE("r1"), NODE("n2"))),
CREATE(PATTERN(NODE("r1"), EDGE("r2", EdgeAtom::Direction::OUT),
NODE("n2"))));
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n1"), EDGE("r1"), NODE("n2"))),
CREATE(PATTERN(NODE("r1"), EDGE("r2", EdgeAtom::Direction::OUT),
NODE("n2")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), TypeMismatchError);
@ -195,7 +198,7 @@ TEST(TestSymbolGenerator, CreateMultipleEdgeType) {
auto rel2 = dba.EdgeType("rel2");
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {rel1});
edge->edge_types_.emplace_back(rel2);
auto query = QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m"))));
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -208,8 +211,8 @@ TEST(TestSymbolGenerator, CreateBidirectionalEdge) {
GraphDb db;
GraphDbAccessor dba(db);
auto rel1 = dba.EdgeType("rel1");
auto query = QUERY(CREATE(PATTERN(
NODE("n"), EDGE("r", EdgeAtom::Direction::BOTH, {rel1}), NODE("m"))));
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(
NODE("n"), EDGE("r", EdgeAtom::Direction::BOTH, {rel1}), NODE("m")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -218,8 +221,9 @@ TEST(TestSymbolGenerator, CreateBidirectionalEdge) {
TEST(TestSymbolGenerator, MatchWhereUnbound) {
// Test MATCH (n) WHERE missing < 42 RETURN n
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("n"))),
WHERE(LESS(IDENT("missing"), LITERAL(42))), RETURN("n"));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
WHERE(LESS(IDENT("missing"), LITERAL(42))),
RETURN("n")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -230,7 +234,7 @@ TEST(TestSymbolGenerator, CreateDelete) {
AstTreeStorage storage;
auto node = NODE("n");
auto ident = IDENT("n");
auto query = QUERY(CREATE(PATTERN(node)), DELETE(ident));
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node)), DELETE(ident)));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -245,7 +249,8 @@ TEST(TestSymbolGenerator, CreateDelete) {
TEST(TestSymbolGenerator, CreateDeleteUnbound) {
// Test CREATE (n) DELETE missing
AstTreeStorage storage;
auto query = QUERY(CREATE(PATTERN(NODE("n"))), DELETE(IDENT("missing")));
auto query =
QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), DELETE(IDENT("missing"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -259,8 +264,9 @@ TEST(TestSymbolGenerator, MatchWithReturn) {
auto with_as_n = AS("n");
auto n_ident = IDENT("n");
auto ret_as_n = AS("n");
auto query = QUERY(MATCH(PATTERN(node)), WITH(old_ident, with_as_n),
RETURN(n_ident, ret_as_n));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), WITH(old_ident, with_as_n),
RETURN(n_ident, ret_as_n)));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -280,8 +286,8 @@ TEST(TestSymbolGenerator, MatchWithReturn) {
TEST(TestSymbolGenerator, MatchWithReturnUnbound) {
// Test MATCH (old) WITH old AS n RETURN old
AstTreeStorage storage;
auto query =
QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("n")), RETURN("old"));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))),
WITH("old", AS("n")), RETURN("old")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -297,8 +303,9 @@ TEST(TestSymbolGenerator, MatchWithWhere) {
auto old_ident = IDENT("old");
auto with_as_n = AS("n");
auto n_prop = PROPERTY_LOOKUP("n", prop);
auto query = QUERY(MATCH(PATTERN(node)), WITH(old_ident, with_as_n),
WHERE(LESS(n_prop, LITERAL(42))));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), WITH(old_ident, with_as_n),
WHERE(LESS(n_prop, LITERAL(42)))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -319,9 +326,9 @@ TEST(TestSymbolGenerator, MatchWithWhereUnbound) {
GraphDbAccessor dba(db);
auto prop = dba.Property("prop");
AstTreeStorage storage;
auto query =
QUERY(MATCH(PATTERN(NODE("old"))), WITH(COUNT(IDENT("old")), AS("c")),
WHERE(LESS(PROPERTY_LOOKUP("old", prop), LITERAL(42))));
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("old"))), WITH(COUNT(IDENT("old")), AS("c")),
WHERE(LESS(PROPERTY_LOOKUP("old", prop), LITERAL(42)))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -340,8 +347,8 @@ TEST(TestSymbolGenerator, CreateMultiExpand) {
auto node_n2 = NODE("n");
auto edge_p = EDGE("p", EdgeAtom::Direction::OUT, {p_type});
auto node_l = NODE("l");
auto query = QUERY(CREATE(PATTERN(node_n1, edge_r, node_m),
PATTERN(node_n2, edge_p, node_l)));
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node_n1, edge_r, node_m),
PATTERN(node_n2, edge_p, node_l))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -372,10 +379,10 @@ TEST(TestSymbolGenerator, MatchCreateExpandLabel) {
auto r_type = dba.EdgeType("r");
auto label = dba.Label("label");
AstTreeStorage storage;
auto query = QUERY(
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
CREATE(PATTERN(NODE("m"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}),
NODE("n", label))));
NODE("n", label)))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -389,8 +396,8 @@ TEST(TestSymbolGenerator, CreateExpandProperty) {
AstTreeStorage storage;
auto n_prop = NODE("n");
n_prop->properties_[PROPERTY_PAIR("prop")] = LITERAL(42);
auto query = QUERY(CREATE(PATTERN(
NODE("n"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), n_prop)));
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(
NODE("n"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), n_prop))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -405,8 +412,8 @@ TEST(TestSymbolGenerator, MatchReturnSum) {
auto node = NODE("n");
auto sum = SUM(PROPERTY_LOOKUP("n", prop));
auto as_result = AS("result");
auto query =
QUERY(MATCH(PATTERN(node)), RETURN(ADD(sum, LITERAL(42)), as_result));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)),
RETURN(ADD(sum, LITERAL(42)), as_result)));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -426,9 +433,9 @@ TEST(TestSymbolGenerator, NestedAggregation) {
GraphDbAccessor dba(db);
auto prop = dba.Property("prop");
AstTreeStorage storage;
auto query = QUERY(
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
RETURN(SUM(ADD(LITERAL(42), SUM(PROPERTY_LOOKUP("n", prop)))), AS("s")));
RETURN(SUM(ADD(LITERAL(42), SUM(PROPERTY_LOOKUP("n", prop)))), AS("s"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -440,9 +447,9 @@ TEST(TestSymbolGenerator, WrongAggregationContext) {
GraphDbAccessor dba(db);
auto prop = dba.Property("prop");
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("n"))),
WITH(PROPERTY_LOOKUP("n", prop), AS("prop")),
WHERE(LESS(SUM(IDENT("prop")), LITERAL(42))));
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))), WITH(PROPERTY_LOOKUP("n", prop), AS("prop")),
WHERE(LESS(SUM(IDENT("prop")), LITERAL(42)))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -458,7 +465,8 @@ TEST(TestSymbolGenerator, MatchPropCreateNodeProp) {
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
node_m->properties_[prop] = n_prop;
auto query = QUERY(MATCH(PATTERN(node_n)), CREATE(PATTERN(node_m)));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), CREATE(PATTERN(node_m))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -480,7 +488,8 @@ TEST(TestSymbolGenerator, CreateNodeEdge) {
auto node_2 = NODE("n");
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_3 = NODE("n");
auto query = QUERY(CREATE(PATTERN(node_1), PATTERN(node_2, edge, node_3)));
auto query = QUERY(
SINGLE_QUERY(CREATE(PATTERN(node_1), PATTERN(node_2, edge, node_3))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -502,8 +511,8 @@ TEST(TestSymbolGenerator, MatchWithCreate) {
auto node_2 = NODE("m");
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_3 = NODE("m");
auto query = QUERY(MATCH(PATTERN(node_1)), WITH("n", AS("m")),
CREATE(PATTERN(node_2, edge, node_3)));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_1)), WITH("n", AS("m")),
CREATE(PATTERN(node_2, edge, node_3))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -522,8 +531,8 @@ TEST(TestSymbolGenerator, SameResults) {
// Test MATCH (n) WITH n AS m, n AS m
{
AstTreeStorage storage;
auto query =
QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("m"), "n", AS("m")));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
WITH("n", AS("m"), "n", AS("m"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -531,7 +540,8 @@ TEST(TestSymbolGenerator, SameResults) {
// Test MATCH (n) RETURN n, n
{
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", "n"));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", "n")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -542,8 +552,9 @@ TEST(TestSymbolGenerator, SkipUsingIdentifier) {
// Test MATCH (old) WITH old AS new SKIP old
{
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("old"))),
WITH("old", AS("new"), SKIP(IDENT("old"))));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))),
WITH("old", AS("new"), SKIP(IDENT("old")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -551,8 +562,9 @@ TEST(TestSymbolGenerator, SkipUsingIdentifier) {
// Test MATCH (old) WITH old AS new SKIP new
{
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("old"))),
WITH("old", AS("new"), SKIP(IDENT("new"))));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))),
WITH("old", AS("new"), SKIP(IDENT("new")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -562,7 +574,8 @@ TEST(TestSymbolGenerator, SkipUsingIdentifier) {
TEST(TestSymbolGenerator, LimitUsingIdentifier) {
// Test MATCH (n) RETURN n AS n LIMIT n
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", LIMIT(IDENT("n"))));
auto query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", LIMIT(IDENT("n")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -571,8 +584,9 @@ TEST(TestSymbolGenerator, LimitUsingIdentifier) {
TEST(TestSymbolGenerator, OrderByAggregation) {
// Test MATCH (old) RETURN old AS new ORDER BY COUNT(1)
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("old"))),
RETURN("old", AS("new"), ORDER_BY(COUNT(LITERAL(1)))));
auto query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("old"))),
RETURN("old", AS("new"), ORDER_BY(COUNT(LITERAL(1))))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -581,9 +595,9 @@ TEST(TestSymbolGenerator, OrderByAggregation) {
TEST(TestSymbolGenerator, OrderByUnboundVariable) {
// Test MATCH (old) RETURN COUNT(old) AS new ORDER BY old
AstTreeStorage storage;
auto query =
QUERY(MATCH(PATTERN(NODE("old"))),
RETURN(COUNT(IDENT("old")), AS("new"), ORDER_BY(IDENT("old"))));
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("old"))),
RETURN(COUNT(IDENT("old")), AS("new"), ORDER_BY(IDENT("old")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -596,8 +610,9 @@ TEST(TestSymbolGenerator, AggregationOrderBy) {
auto ident_old = IDENT("old");
auto as_new = AS("new");
auto ident_new = IDENT("new");
auto query = QUERY(MATCH(PATTERN(node)),
RETURN(COUNT(ident_old), as_new, ORDER_BY(ident_new)));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(COUNT(ident_old), as_new,
ORDER_BY(ident_new))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -617,8 +632,8 @@ TEST(TestSymbolGenerator, OrderByOldVariable) {
auto ident_old = IDENT("old");
auto as_new = AS("new");
auto by_old = IDENT("old");
auto query =
QUERY(MATCH(PATTERN(node)), RETURN(ident_old, as_new, ORDER_BY(by_old)));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)),
RETURN(ident_old, as_new, ORDER_BY(by_old))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -635,7 +650,8 @@ TEST(TestSymbolGenerator, MergeVariableError) {
// Test MATCH (n) MERGE (n)
{
AstTreeStorage storage;
auto query = QUERY(MATCH(PATTERN(NODE("n"))), MERGE(PATTERN(NODE("n"))));
auto query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), MERGE(PATTERN(NODE("n")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), RedeclareVariableError);
@ -646,10 +662,10 @@ TEST(TestSymbolGenerator, MergeVariableError) {
GraphDbAccessor dba(db);
auto rel = dba.EdgeType("rel");
AstTreeStorage storage;
auto query = QUERY(
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
MERGE(PATTERN(NODE("a"), EDGE("r", EdgeAtom::Direction::BOTH, {rel}),
NODE("b"))));
NODE("b")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), RedeclareVariableError);
@ -659,7 +675,8 @@ TEST(TestSymbolGenerator, MergeVariableError) {
TEST(TestSymbolGenerator, MergeEdgeWithoutType) {
// Test MERGE (a) -[r]- (b)
AstTreeStorage storage;
auto query = QUERY(MERGE(PATTERN(NODE("a"), EDGE("r"), NODE("b"))));
auto query =
QUERY(SINGLE_QUERY(MERGE(PATTERN(NODE("a"), EDGE("r"), NODE("b")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
// Edge must have a type, since it doesn't we raise.
@ -682,11 +699,11 @@ TEST(TestSymbolGenerator, MergeOnMatchOnCreate) {
auto m_prop = PROPERTY_LOOKUP("m", prop);
auto ident_r = IDENT("r");
auto as_r = AS("r");
auto query =
QUERY(MATCH(PATTERN(match_n)), MERGE(PATTERN(merge_n, edge_r, node_m),
ON_MATCH(SET(n_prop, LITERAL(42))),
ON_CREATE(SET(m_prop, LITERAL(42)))),
RETURN(ident_r, as_r));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(match_n)),
MERGE(PATTERN(merge_n, edge_r, node_m),
ON_MATCH(SET(n_prop, LITERAL(42))),
ON_CREATE(SET(m_prop, LITERAL(42)))),
RETURN(ident_r, as_r)));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -709,8 +726,9 @@ TEST(TestSymbolGenerator, MergeOnMatchOnCreate) {
TEST(TestSymbolGenerator, WithUnwindRedeclareReturn) {
// Test WITH [1, 2] AS list UNWIND list AS list RETURN list
AstTreeStorage storage;
auto query = QUERY(WITH(LIST(LITERAL(1), LITERAL(2)), AS("list")),
UNWIND(IDENT("list"), AS("list")), RETURN("list"));
auto query =
QUERY(SINGLE_QUERY(WITH(LIST(LITERAL(1), LITERAL(2)), AS("list")),
UNWIND(IDENT("list"), AS("list")), RETURN("list")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), RedeclareVariableError);
@ -725,8 +743,9 @@ TEST(TestSymbolGenerator, WithUnwindReturn) {
auto ret_as_list = AS("list");
auto ret_elem = IDENT("elem");
auto ret_as_elem = AS("elem");
auto query = QUERY(WITH(LIST(LITERAL(1), LITERAL(2)), with_as_list), unwind,
RETURN(ret_list, ret_as_list, ret_elem, ret_as_elem));
auto query = QUERY(
SINGLE_QUERY(WITH(LIST(LITERAL(1), LITERAL(2)), with_as_list), unwind,
RETURN(ret_list, ret_as_list, ret_elem, ret_as_elem)));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -756,8 +775,8 @@ TEST(TestSymbolGenerator, MatchCrossReferenceVariable) {
node_m->properties_[prop] = n_prop;
auto ident_n = IDENT("n");
auto as_n = AS("n");
auto query =
QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN(ident_n, as_n));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)),
RETURN(ident_n, as_n)));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -787,7 +806,8 @@ TEST(TestSymbolGenerator, MatchWithAsteriskReturnAsterisk) {
auto node_m = NODE("m");
auto with = storage.Create<With>();
with->body_.all_identifiers = true;
auto query = QUERY(MATCH(PATTERN(node_n, edge, node_m)), with, ret);
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, node_m)), with, ret));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -802,7 +822,7 @@ TEST(TestSymbolGenerator, MatchReturnAsteriskSameResult) {
AstTreeStorage storage;
auto ret = RETURN("n");
ret->body_.all_identifiers = true;
auto query = QUERY(MATCH(PATTERN(NODE("n"))), ret);
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), ret));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -815,7 +835,7 @@ TEST(TestSymbolGenerator, MatchReturnAsteriskNoUserVariables) {
ret->body_.all_identifiers = true;
auto ident_n = storage.Create<Identifier>("anon", false);
auto node = storage.Create<NodeAtom>(ident_n);
auto query = QUERY(MATCH(PATTERN(node)), ret);
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), ret));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -828,10 +848,10 @@ TEST(TestSymbolGenerator, MatchMergeExpandLabel) {
auto r_type = dba.EdgeType("r");
auto label = dba.Label("label");
AstTreeStorage storage;
auto query = QUERY(
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
MERGE(PATTERN(NODE("m"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}),
NODE("n", label))));
NODE("n", label)))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -847,7 +867,8 @@ TEST(TestSymbolGenerator, MatchEdgeWithIdentifierInProperty) {
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
edge->properties_[prop] = n_prop;
auto node_n = NODE("n");
auto query = QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r"));
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -868,7 +889,8 @@ TEST(TestSymbolGenerator, MatchVariablePathUsingIdentifier) {
edge->upper_bound_ = l_prop;
auto node_l = NODE("l");
auto query = QUERY(
MATCH(PATTERN(NODE("n"), edge, NODE("m")), PATTERN(node_l)), RETURN("r"));
SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m")), PATTERN(node_l)),
RETURN("r")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -891,8 +913,8 @@ TEST(TestSymbolGenerator, MatchVariablePathUsingUnboundIdentifier) {
auto l_prop = PROPERTY_LOOKUP("l", prop);
edge->upper_bound_ = l_prop;
auto node_l = NODE("l");
auto query = QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))),
MATCH(PATTERN(node_l)), RETURN("r"));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))),
MATCH(PATTERN(node_l)), RETURN("r")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -903,7 +925,7 @@ TEST(TestSymbolGenerator, CreateVariablePath) {
// paths cannot be created.
AstTreeStorage storage;
auto edge = EDGE_VARIABLE("r", EdgeAtom::Direction::OUT);
auto query = QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m"))));
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -914,7 +936,7 @@ TEST(TestSymbolGenerator, MergeVariablePath) {
// paths cannot be created.
AstTreeStorage storage;
auto edge = EDGE_VARIABLE("r", EdgeAtom::Direction::OUT);
auto query = QUERY(MERGE(PATTERN(NODE("n"), edge, NODE("m"))));
auto query = QUERY(SINGLE_QUERY(MERGE(PATTERN(NODE("n"), edge, NODE("m")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
@ -927,7 +949,8 @@ TEST(TestSymbolGenerator, RedeclareVariablePath) {
// should be changed to check for type errors.
AstTreeStorage storage;
auto edge = EDGE_VARIABLE("n", EdgeAtom::Direction::OUT);
auto query = QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("n"));
auto query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("n")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), RedeclareVariableError);
@ -943,7 +966,8 @@ TEST(TestSymbolGenerator, VariablePathSameIdentifier) {
AstTreeStorage storage;
auto edge = EDGE_VARIABLE("r", EdgeAtom::Direction::OUT);
edge->lower_bound_ = PROPERTY_LOOKUP("r", prop);
auto query = QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
auto query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -960,7 +984,7 @@ TEST(TestSymbolGenerator, MatchPropertySameIdentifier) {
auto node_n = NODE("n");
auto n_prop = PROPERTY_LOOKUP("n", prop.second);
node_n->properties_[prop] = n_prop;
auto query = QUERY(MATCH(PATTERN(node_n)), RETURN("n"));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), RETURN("n")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -977,8 +1001,8 @@ TEST(TestSymbolGenerator, WithReturnAll) {
auto *all = ALL("x", LIST(list_x), WHERE(EQ(where_x, LITERAL(2))));
auto *ret_as_x = AS("x");
auto *ret_x = IDENT("x");
auto query = QUERY(WITH(LITERAL(42), with_as_x),
RETURN(all, ret_as_x, ret_x, AS("y")));
auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(42), with_as_x),
RETURN(all, ret_as_x, ret_x, AS("y"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -1011,8 +1035,8 @@ TEST(TestSymbolGenerator, MatchBfsReturn) {
bfs->filter_expression_ = r_prop;
bfs->upper_bound_ = n_prop;
auto *ret_r = IDENT("r");
auto *query =
QUERY(MATCH(PATTERN(node_n, bfs, NODE("m"))), RETURN(ret_r, AS("r")));
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, bfs, NODE("m"))),
RETURN(ret_r, AS("r"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -1039,7 +1063,8 @@ TEST(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) {
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = IDENT("r");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
auto *query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -1055,7 +1080,8 @@ TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) {
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = IDENT("a");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(MATCH(PATTERN(node_a, bfs, NODE("m"))), RETURN("r"));
auto *query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(node_a, bfs, NODE("m"))), RETURN("r")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -1072,7 +1098,8 @@ TEST(TestSymbolGenerator, MatchBfsUsesLaterSymbolError) {
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = IDENT("m");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
auto *query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r")));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), UnboundVariableError);
@ -1090,8 +1117,8 @@ TEST(TestSymbolGenerator, MatchVariableLambdaSymbols) {
edge->inner_node_ = storage.Create<Identifier>("anon_inner_n", false);
auto end_node =
storage.Create<NodeAtom>(storage.Create<Identifier>("anon_end", false));
auto query = QUERY(MATCH(PATTERN(node, edge, end_node)),
RETURN(LITERAL(42), AS("res")));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node, edge, end_node)),
RETURN(LITERAL(42), AS("res"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
@ -1109,4 +1136,135 @@ TEST(TestSymbolGenerator, MatchVariableLambdaSymbols) {
}
}
TEST(TestSymbolGenerator, MatchUnionSymbols) {
// RETURN 5 as X UNION RETURN 6 AS x
AstTreeStorage storage;
auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"))),
UNION(SINGLE_QUERY(RETURN(LITERAL(6), AS("X")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
EXPECT_EQ(symbol_table.max_position(), 3);
}
TEST(TestSymbolGenerator, MatchUnionMultipleSymbols) {
// RETURN 5 as X, 6 AS Y UNION RETURN 5 AS Y, 6 AS x
AstTreeStorage storage;
auto query = QUERY(
SINGLE_QUERY(RETURN(LITERAL(5), AS("X"), LITERAL(6), AS("Y"))),
UNION(SINGLE_QUERY(RETURN(LITERAL(5), AS("Y"), LITERAL(6), AS("X")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
EXPECT_EQ(symbol_table.max_position(), 6);
}
TEST(TestSymbolGenerator, MatchUnionAllSymbols) {
// RETURN 5 as X UNION ALL RETURN 6 AS x
AstTreeStorage storage;
auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"))),
UNION_ALL(SINGLE_QUERY(RETURN(LITERAL(6), AS("X")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
EXPECT_EQ(symbol_table.max_position(), 3);
}
TEST(TestSymbolGenerator, MatchUnionAllMultipleSymbols) {
// RETURN 5 as X, 6 AS Y UNION ALL RETURN 5 AS Y, 6 AS x
AstTreeStorage storage;
auto query = QUERY(
SINGLE_QUERY(RETURN(LITERAL(5), AS("X"), LITERAL(6), AS("Y"))),
UNION_ALL(
SINGLE_QUERY(RETURN(LITERAL(5), AS("Y"), LITERAL(6), AS("X")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
EXPECT_EQ(symbol_table.max_position(), 6);
}
TEST(TestSymbolGenerator, MatchUnionReturnAllSymbols) {
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 3 AS X, 4 AS Y
AstTreeStorage storage;
auto ret = storage.Create<Return>();
ret->body_.all_identifiers = true;
auto query = QUERY(
SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")), ret),
UNION(SINGLE_QUERY(RETURN(LITERAL(3), AS("X"), LITERAL(4), AS("Y")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
EXPECT_EQ(symbol_table.max_position(), 6);
}
TEST(TestSymbolGenerator, MatchUnionReturnSymbols) {
// WITH 1 as X, 2 AS Y RETURN Y, X UNION RETURN 3 AS X, 4 AS Y
AstTreeStorage storage;
auto query = QUERY(
SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")),
RETURN("Y", "X")),
UNION(SINGLE_QUERY(RETURN(LITERAL(3), AS("X"), LITERAL(4), AS("Y")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
EXPECT_EQ(symbol_table.max_position(), 8);
}
TEST(TestSymbolGenerator, MatchUnionParameterNameThrowSemanticExpcetion) {
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 3 AS Z, 4 AS Y
AstTreeStorage storage;
auto ret = storage.Create<Return>();
ret->body_.all_identifiers = true;
auto query = QUERY(
SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")), ret),
UNION(SINGLE_QUERY(RETURN(LITERAL(3), AS("Z"), LITERAL(4), AS("Y")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
}
TEST(TestSymbolGenerator, MatchUnionParameterNumberThrowSemanticExpcetion) {
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 4 AS Y
AstTreeStorage storage;
auto ret = storage.Create<Return>();
ret->body_.all_identifiers = true;
auto query =
QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")), ret),
UNION(SINGLE_QUERY(RETURN(LITERAL(4), AS("Y")))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
EXPECT_THROW(query->Accept(symbol_generator), SemanticException);
}
TEST(TestSymbolGenerator, MatchUnion) {
// WITH 5 AS X, 3 AS Y RETURN * UNION WITH 9 AS Y, 4 AS X RETURN Y, X
AstTreeStorage storage;
auto ret = storage.Create<Return>();
ret->body_.all_identifiers = true;
auto query =
QUERY(SINGLE_QUERY(WITH(LITERAL(5), AS("X"), LITERAL(3), AS("Y")), ret),
UNION(SINGLE_QUERY(WITH(LITERAL(9), AS("Y"), LITERAL(4), AS("X")),
RETURN("Y", "X"))));
SymbolTable symbol_table;
SymbolGenerator symbol_generator(symbol_table);
query->Accept(symbol_generator);
EXPECT_EQ(symbol_table.max_position(), 8);
}
} // namespace

View File

@ -67,7 +67,11 @@ void CheckPlansProduce(
std::function<void(const std::vector<std::vector<TypedValue>> &)> check) {
auto symbol_table = MakeSymbolTable(*storage.query());
auto planning_context = MakePlanningContext(storage, symbol_table, dba);
auto plans = MakeLogicalPlan<VariableStartPlanner>(planning_context);
auto query_parts = CollectQueryParts(symbol_table, storage);
EXPECT_TRUE(query_parts.query_parts.size() > 0);
auto single_query_parts = query_parts.query_parts.at(0).single_query_parts;
auto plans = MakeLogicalPlanForSingleQuery<VariableStartPlanner>(
single_query_parts, planning_context);
EXPECT_EQ(std::distance(plans.begin(), plans.end()), expected_plan_count);
for (const auto &plan : plans) {
auto *produce = dynamic_cast<Produce *>(plan.get());
@ -87,8 +91,9 @@ TEST(TestVariableStartPlanner, MatchReturn) {
dba.AdvanceCommand();
// Test MATCH (n) -[r]-> (m) RETURN n
AstTreeStorage storage;
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
RETURN("n"));
QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
RETURN("n")));
// We have 2 nodes `n` and `m` from which we could start, so expect 2 plans.
CheckPlansProduce(2, storage, dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
@ -109,9 +114,10 @@ TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
{
// Test `MATCH (n) -[r]-> (m) -[e]-> (l) RETURN n`
AstTreeStorage storage;
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"),
EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n"));
QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"),
EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n")));
// We have 3 nodes: `n`, `m` and `l` from which we could start.
CheckPlansProduce(3, storage, dba, [&](const auto &results) {
// We expect to produce only a single (v1) node.
@ -121,9 +127,10 @@ TEST(TestVariableStartPlanner, MatchTripletPatternReturn) {
{
// Equivalent to `MATCH (n) -[r]-> (m), (m) -[e]-> (l) RETURN n`.
AstTreeStorage storage;
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m")),
PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n"));
QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m")),
PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n")));
CheckPlansProduce(3, storage, dba, [&](const auto &results) {
AssertRows(results, {{v1}});
});
@ -142,10 +149,10 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) {
dba.AdvanceCommand();
// Test MATCH (n) -[r]-> (m) OPTIONAL MATCH (m) -[e]-> (l) RETURN n, l
AstTreeStorage storage;
QUERY(
QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
OPTIONAL_MATCH(PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
RETURN("n", "l"));
RETURN("n", "l")));
// We have 2 nodes `n` and `m` from which we could start the MATCH, and 2
// nodes for OPTIONAL MATCH. This should produce 2 * 2 plans.
CheckPlansProduce(4, storage, dba, [&](const auto &results) {
@ -168,11 +175,11 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) {
// Test MATCH (n) -[r]-> (m) OPTIONAL MATCH (m) -[e]-> (l)
// MERGE (u) -[q:r]-> (v) RETURN n, m, l, u, v
AstTreeStorage storage;
QUERY(
QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
OPTIONAL_MATCH(PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))),
MERGE(PATTERN(NODE("u"), EDGE("q", Direction::OUT, {r_type}), NODE("v"))),
RETURN("n", "m", "l", "u", "v"));
RETURN("n", "m", "l", "u", "v")));
// Since MATCH, OPTIONAL MATCH and MERGE each have 2 nodes from which we can
// start, we generate 2 * 2 * 2 plans.
CheckPlansProduce(8, storage, dba, [&](const auto &results) {
@ -191,10 +198,11 @@ TEST(TestVariableStartPlanner, MatchWithMatchReturn) {
dba.AdvanceCommand();
// Test MATCH (n) -[r]-> (m) WITH n MATCH (m) -[r]-> (l) RETURN n, m, l
AstTreeStorage storage;
QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
WITH("n"),
MATCH(PATTERN(NODE("m"), EDGE("r", Direction::OUT), NODE("l"))),
RETURN("n", "m", "l"));
QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))),
WITH("n"),
MATCH(PATTERN(NODE("m"), EDGE("r", Direction::OUT), NODE("l"))),
RETURN("n", "m", "l")));
// We can start from 2 nodes in each match. Since WITH separates query parts,
// we expect to get 2 plans for each, which totals 2 * 2.
CheckPlansProduce(4, storage, dba, [&](const auto &results) {
@ -216,7 +224,7 @@ TEST(TestVariableStartPlanner, MatchVariableExpand) {
// Test MATCH (n) -[r*]-> (m) RETURN r
AstTreeStorage storage;
auto edge = EDGE_VARIABLE("r", Direction::OUT);
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r")));
// We expect to get a single column with the following rows:
TypedValue r1_list(std::vector<TypedValue>{r1}); // [r1]
TypedValue r2_list(std::vector<TypedValue>{r2}); // [r2]
@ -244,7 +252,7 @@ TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) {
AstTreeStorage storage;
auto edge = EDGE_VARIABLE("r", Direction::OUT);
edge->upper_bound_ = PROPERTY_LOOKUP("n", id);
QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"));
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r")));
// We expect to get a single column with the following rows:
TypedValue r1_list(std::vector<TypedValue>{r1}); // [r1] (v1 -[*..1]-> v2)
TypedValue r2_list(std::vector<TypedValue>{r2}); // [r2] (v2 -[*..2]-> v3)
@ -270,7 +278,7 @@ TEST(TestVariableStartPlanner, MatchVariableExpandBoth) {
auto edge = EDGE_VARIABLE("r", Direction::BOTH);
auto node_n = NODE("n");
node_n->properties_[std::make_pair("id", id)] = LITERAL(1);
QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r"));
QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r")));
// We expect to get a single column with the following rows:
TypedValue r1_list(std::vector<TypedValue>{r1}); // [r1]
TypedValue r1_r2_list(std::vector<TypedValue>{r1, r2}); // [r1, r2]
@ -302,7 +310,7 @@ TEST(TestVariableStartPlanner, MatchBfs) {
bfs->inner_node_ = IDENT("n");
bfs->filter_expression_ = NEQ(PROPERTY_LOOKUP("n", id), LITERAL(3));
bfs->upper_bound_ = LITERAL(10);
QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"));
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r")));
// We expect to get a single column with the following rows:
TypedValue r1_list(std::vector<TypedValue>{r1}); // [r1]
CheckPlansProduce(2, storage, dba, [&](const auto &results) {

View File

@ -3,10 +3,10 @@
// Created by Florijan Stamenkovic on 07.03.17.
//
#include "query/frontend/stripped.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "query/exceptions.hpp"
#include "query/frontend/stripped.hpp"
#include "query/typed_value.hpp"
using namespace query;
@ -16,11 +16,11 @@ namespace {
using testing::Pair;
using testing::UnorderedElementsAre;
void EXPECT_PROP_TRUE(const TypedValue& a) {
void EXPECT_PROP_TRUE(const TypedValue &a) {
EXPECT_TRUE(a.type() == TypedValue::Type::Bool && a.Value<bool>());
}
void EXPECT_PROP_EQ(const TypedValue& a, const TypedValue& b) {
void EXPECT_PROP_EQ(const TypedValue &a, const TypedValue &b) {
EXPECT_PROP_TRUE(a == b);
}
@ -323,4 +323,21 @@ TEST(QueryStripper, KeywordInNamedExpression) {
EXPECT_THAT(stripped.named_expressions(),
UnorderedElementsAre(Pair(2, "CoUnT(n)")));
}
TEST(QueryStripper, UnionMultipleReturnStatementsAliasedExpression) {
StrippedQuery stripped("RETURN 1 AS X UNION RETURN 2 AS X");
EXPECT_THAT(stripped.named_expressions(), UnorderedElementsAre());
}
TEST(QueryStripper, UnionMultipleReturnStatementsNamedExpressions) {
StrippedQuery stripped("RETURN x UNION RETURN x");
EXPECT_THAT(stripped.named_expressions(),
UnorderedElementsAre(Pair(2, "x"), Pair(8, "x")));
}
TEST(QueryStripper, UnionAllMultipleReturnStatementsNamedExpressions) {
StrippedQuery stripped("RETURN x UNION ALL RETURN x");
EXPECT_THAT(stripped.named_expressions(),
UnorderedElementsAre(Pair(2, "x"), Pair(10, "x")));
}
}