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:
parent
dae79413bf
commit
9ad7d82a54
@ -11,6 +11,7 @@
|
||||
|
||||
* Write-ahead log added.
|
||||
* `nodes` and `relationships` functions added.
|
||||
* `UNION` and `UNION ALL` is implemented
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
64
src/query/frontend/semantic/symbol.hpp
Normal file
64
src/query/frontend/semantic/symbol.hpp
Normal 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
|
@ -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_);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/parameters.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ¶meters,
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
136
tests/qa/tck_engine/tests/memgraph_V1/features/union.feature
Normal file
136
tests/qa/tck_engine/tests/memgraph_V1/features/union.feature
Normal 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
@ -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
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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")));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user