memgraph/tests/unit/query_semantic.cpp
Gareth Andrew Lloyd 0fb8e4116f
Fix REPLICA timestamps (#1615)
* Fix up REPLICA GetInfo and CreateSnapshot

Subtle bug where these actions were using the incorrect transactional
access while in REPLICA role. This casued timestamp to be incorrectly
bumped, breaking REPLICA from doing replication.

* Delay DNS resolution

Rather than resolve at endpoint creation, we will instread resolve only
on Socket connect. This allows k8s deployments to change their IP during
pod restarts.

* Minor sonarsource fixes

---------
Co-authored-by: Andreja <andreja.tonev@memgraph.io>
Co-authored-by: DavIvek <david.ivekovic@memgraph.io>
2024-01-05 16:42:54 +00:00

1445 lines
71 KiB
C++

// Copyright 2024 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <memory>
#include <sstream>
#include <variant>
#include "disk_test_utils.hpp"
#include "gtest/gtest.h"
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_generator.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/plan/preprocess.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "query_common.hpp"
using namespace memgraph::query;
template <typename StorageType>
class TestSymbolGenerator : public ::testing::Test {
protected:
const std::string testSuite = "query_semantic";
memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite);
std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)};
std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{
db->Access(memgraph::replication::ReplicationRole::MAIN)};
memgraph::query::DbAccessor dba{storage_dba.get()};
AstStorage storage;
void TearDown() override {
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
disk_test_utils::RemoveRocksDbDirs(testSuite);
}
}
};
using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>;
TYPED_TEST_CASE(TestSymbolGenerator, StorageTypes);
TYPED_TEST(TestSymbolGenerator, MatchNodeReturn) {
// MATCH (node_atom_1) RETURN node_atom_1
auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("node_atom_1"))), RETURN("node_atom_1")));
auto symbol_table = memgraph::query::MakeSymbolTable(query_ast);
// 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->single_query_->clauses_[0]);
auto *pattern = match->patterns_[0];
auto pattern_sym = symbol_table.at(*pattern->identifier_);
EXPECT_EQ(pattern_sym.type(), Symbol::Type::PATH);
EXPECT_FALSE(pattern_sym.user_declared());
auto *node_atom = dynamic_cast<NodeAtom *>(pattern->atoms_[0]);
auto node_sym = symbol_table.at(*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->single_query_->clauses_[1]);
auto *named_expr = ret->body_.named_expressions[0];
auto column_sym = symbol_table.at(*named_expr);
EXPECT_EQ(node_sym.name(), column_sym.name());
EXPECT_NE(node_sym, column_sym);
auto ret_sym = symbol_table.at(*dynamic_cast<Identifier *>(named_expr->expression_));
EXPECT_EQ(node_sym, ret_sym);
}
TYPED_TEST(TestSymbolGenerator, MatchNamedPattern) {
// MATCH p = (node_atom_1) RETURN node_atom_1
auto query_ast = QUERY(SINGLE_QUERY(MATCH(NAMED_PATTERN("p", NODE("node_atom_1"))), RETURN("p")));
auto symbol_table = memgraph::query::MakeSymbolTable(query_ast);
// 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->single_query_->clauses_[0]);
auto *pattern = match->patterns_[0];
auto pattern_sym = symbol_table.at(*pattern->identifier_);
EXPECT_EQ(pattern_sym.type(), Symbol::Type::PATH);
EXPECT_EQ(pattern_sym.name(), "p");
EXPECT_TRUE(pattern_sym.user_declared());
}
TYPED_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(SINGLE_QUERY(MATCH(PATTERN(NODE("node_atom_1"))), RETURN("node_atom_1", AS("n"), "n")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchNodeUnboundReturn) {
// AST with unbound variable in return: MATCH (n) RETURN x
auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("x")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, CreatePropertyUnbound) {
// AST with unbound variable in create: CREATE ({prop: x})
auto node = NODE("anon");
std::get<0>(node->properties_)[this->storage.GetPropertyIx("prop")] = IDENT("x");
auto *query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(node))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, CreateNodeReturn) {
// Simple AST returning a created node: CREATE (n) RETURN n
auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query_ast);
// symbols for pattern, `n` and named_expr
EXPECT_EQ(symbol_table.max_position(), 3);
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.at(*node_atom->identifier_);
EXPECT_EQ(node_sym.name(), "n");
EXPECT_EQ(node_sym.type(), Symbol::Type::VERTEX);
auto *ret = dynamic_cast<Return *>(query_ast->single_query_->clauses_[1]);
auto *named_expr = ret->body_.named_expressions[0];
auto column_sym = symbol_table.at(*named_expr);
EXPECT_EQ(node_sym.name(), column_sym.name());
EXPECT_NE(node_sym, column_sym);
auto ret_sym = symbol_table.at(*dynamic_cast<Identifier *>(named_expr->expression_));
EXPECT_EQ(node_sym, ret_sym);
}
TYPED_TEST(TestSymbolGenerator, CreateRedeclareNode) {
// AST with redeclaring a variable when creating nodes: CREATE (n), (n)
auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("n")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, MultiCreateRedeclareNode) {
// AST with redeclaring a variable when creating nodes with multiple creates:
// CREATE (n) CREATE (n)
auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchCreateRedeclareNode) {
// AST with redeclaring a match node variable in create: MATCH (n) CREATE (n)
auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchCreateRedeclareEdge) {
// AST with redeclaring a match edge variable in create:
// MATCH (n) -[r]- (m) CREATE (n) -[r :relationship]-> (l)
const auto *relationship = "relationship";
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")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchTypeMismatch) {
// Using an edge variable as a node causes a type mismatch.
// MATCH (n) -[r]-> (r)
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("r")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), TypeMismatchError);
}
TYPED_TEST(TestSymbolGenerator, MatchCreateTypeMismatch) {
// Using an edge variable as a node causes a type mismatch.
// MATCH (n1) -[r1]- (n2) CREATE (r1) -[r2]-> (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")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), TypeMismatchError);
}
TYPED_TEST(TestSymbolGenerator, CreateMultipleEdgeType) {
// Multiple edge relationship are not allowed when creating edges.
// CREATE (n) -[r :rel1 | :rel2]-> (m)
const auto *rel1 = "rel1";
const auto *rel2 = "rel2";
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {rel1});
edge->edge_types_.emplace_back(this->storage.GetEdgeTypeIx(rel2));
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, CreateBidirectionalEdge) {
// Bidirectional relationships are not allowed when creating edges.
// CREATE (n) -[r :rel1]- (m)
const auto *rel1 = "rel1";
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", EdgeAtom::Direction::BOTH, {rel1}), NODE("m")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchWhereUnbound) {
// Test MATCH (n) WHERE missing < 42 RETURN n
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(LESS(IDENT("missing"), LITERAL(42))), RETURN("n")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, CreateDelete) {
// Test CREATE (n) DELETE n
auto node = NODE("n");
auto ident = IDENT("n");
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node)), DELETE(ident)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// symbols for pattern and `n`
EXPECT_EQ(symbol_table.max_position(), 2);
auto node_symbol = symbol_table.at(*node->identifier_);
auto ident_symbol = symbol_table.at(*ident);
EXPECT_EQ(node_symbol.type(), Symbol::Type::VERTEX);
EXPECT_EQ(node_symbol, ident_symbol);
}
TYPED_TEST(TestSymbolGenerator, CreateDeleteUnbound) {
// Test CREATE (n) DELETE missing
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), DELETE(IDENT("missing"))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchWithReturn) {
// Test MATCH (old) WITH old AS n RETURN n AS n
auto node = NODE("old");
auto old_ident = IDENT("old");
auto with_as_n = AS("n");
auto n_ident = IDENT("n");
auto ret_as_n = AS("n");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), WITH(old_ident, with_as_n), RETURN(n_ident, ret_as_n)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// symbols for pattern, `old`, `n` and named_expr in return
EXPECT_EQ(symbol_table.max_position(), 4);
auto node_symbol = symbol_table.at(*node->identifier_);
auto old = symbol_table.at(*old_ident);
EXPECT_EQ(node_symbol, old);
auto with_n = symbol_table.at(*with_as_n);
EXPECT_NE(old, with_n);
auto n = symbol_table.at(*n_ident);
EXPECT_EQ(n, with_n);
auto ret_n = symbol_table.at(*ret_as_n);
EXPECT_NE(n, ret_n);
}
TYPED_TEST(TestSymbolGenerator, MatchWithReturnUnbound) {
// Test MATCH (old) WITH old AS n RETURN old
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("n")), RETURN("old")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchWithWhere) {
// Test MATCH (old) WITH old AS n WHERE n.prop < 42
auto prop = this->dba.NameToProperty("prop");
auto node = NODE("old");
auto old_ident = IDENT("old");
auto with_as_n = AS("n");
auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop);
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), WITH(old_ident, with_as_n), WHERE(LESS(n_prop, LITERAL(42)))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// symbols for pattern, `old` and `n`
EXPECT_EQ(symbol_table.max_position(), 3);
auto node_symbol = symbol_table.at(*node->identifier_);
auto old = symbol_table.at(*old_ident);
EXPECT_EQ(node_symbol, old);
auto with_n = symbol_table.at(*with_as_n);
EXPECT_NE(old, with_n);
auto n = symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_));
EXPECT_EQ(n, with_n);
}
TYPED_TEST(TestSymbolGenerator, MatchWithWhereUnbound) {
// Test MATCH (old) WITH COUNT(old) AS c WHERE old.prop < 42
auto prop = this->dba.NameToProperty("prop");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH(COUNT(IDENT("old"), false), AS("c")),
WHERE(LESS(PROPERTY_LOOKUP(this->dba, "old", prop), LITERAL(42)))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, CreateMultiExpand) {
// Test CREATE (n) -[r :r]-> (m), (n) - [p :p]-> (l)
const auto *r_type = "r";
const auto *p_type = "p";
auto node_n1 = NODE("n");
auto edge_r = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_m = NODE("m");
auto node_n2 = NODE("n");
auto edge_p = EDGE("p", EdgeAtom::Direction::OUT, {p_type});
auto node_l = NODE("l");
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node_n1, edge_r, node_m), PATTERN(node_n2, edge_p, node_l))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// symbols for pattern * 2, `n`, `r`, `m`, `p`, `l`
EXPECT_EQ(symbol_table.max_position(), 7);
auto n1 = symbol_table.at(*node_n1->identifier_);
auto n2 = symbol_table.at(*node_n2->identifier_);
EXPECT_EQ(n1, n2);
EXPECT_EQ(n1.type(), Symbol::Type::VERTEX);
auto m = symbol_table.at(*node_m->identifier_);
EXPECT_EQ(m.type(), Symbol::Type::VERTEX);
EXPECT_NE(m, n1);
auto l = symbol_table.at(*node_l->identifier_);
EXPECT_EQ(l.type(), Symbol::Type::VERTEX);
EXPECT_NE(l, n1);
EXPECT_NE(l, m);
auto r = symbol_table.at(*edge_r->identifier_);
auto p = symbol_table.at(*edge_p->identifier_);
EXPECT_EQ(r.type(), Symbol::Type::EDGE);
EXPECT_EQ(p.type(), Symbol::Type::EDGE);
EXPECT_NE(r, p);
}
TYPED_TEST(TestSymbolGenerator, MatchCreateExpandLabel) {
// Test MATCH (n) CREATE (m) -[r :r]-> (n:label)
const auto *r_type = "r";
const auto *label = "label";
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
CREATE(PATTERN(NODE("m"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), NODE("n", label)))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, CreateExpandProperty) {
// Test CREATE (n) -[r :r]-> (n {prop: 42})
const auto *r_type = "r";
auto n_prop = NODE("n");
std::get<0>(n_prop->properties_)[this->storage.GetPropertyIx("prop")] = LITERAL(42);
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), n_prop))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchReturnSum) {
// Test MATCH (n) RETURN SUM(n.prop) + 42 AS result
auto prop = this->dba.NameToProperty("prop");
auto node = NODE("n");
auto sum = SUM(PROPERTY_LOOKUP(this->dba, "n", prop), false);
auto as_result = AS("result");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(ADD(sum, LITERAL(42)), as_result)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// 3 symbols for: pattern, 'n', 'sum' and 'result'.
EXPECT_EQ(symbol_table.max_position(), 4);
auto node_symbol = symbol_table.at(*node->identifier_);
auto sum_symbol = symbol_table.at(*sum);
EXPECT_NE(node_symbol, sum_symbol);
auto result_symbol = symbol_table.at(*as_result);
EXPECT_NE(result_symbol, node_symbol);
EXPECT_NE(result_symbol, sum_symbol);
}
TYPED_TEST(TestSymbolGenerator, NestedAggregation) {
// Test MATCH (n) RETURN SUM(42 + SUM(n.prop)) AS s
auto prop = this->dba.NameToProperty("prop");
auto query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
RETURN(SUM(ADD(LITERAL(42), SUM(PROPERTY_LOOKUP(this->dba, "n", prop), false)), false), AS("s"))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, WrongAggregationContext) {
// Test MATCH (n) WITH n.prop AS prop WHERE SUM(prop) < 42
auto prop = this->dba.NameToProperty("prop");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(PROPERTY_LOOKUP(this->dba, "n", prop), AS("prop")),
WHERE(LESS(SUM(IDENT("prop"), false), LITERAL(42)))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchPropCreateNodeProp) {
// Test MATCH (n) CREATE (m {prop: n.prop})
auto prop = PROPERTY_PAIR(this->dba, "prop");
auto node_n = NODE("n");
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second);
std::get<0>(node_m->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop;
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), CREATE(PATTERN(node_m))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// symbols: pattern * 2, `node_n`, `node_m`
EXPECT_EQ(symbol_table.max_position(), 4);
auto n = symbol_table.at(*node_n->identifier_);
EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_)));
auto m = symbol_table.at(*node_m->identifier_);
EXPECT_NE(n, m);
}
TYPED_TEST(TestSymbolGenerator, CreateNodeEdge) {
// Test CREATE (n), (n) -[r :r]-> (n)
const auto *r_type = "r";
auto node_1 = NODE("n");
auto node_2 = NODE("n");
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_3 = NODE("n");
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node_1), PATTERN(node_2, edge, node_3))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// symbols: pattern * 2, `n`, `r`
EXPECT_EQ(symbol_table.max_position(), 4);
auto n = symbol_table.at(*node_1->identifier_);
EXPECT_EQ(n, symbol_table.at(*node_2->identifier_));
EXPECT_EQ(n, symbol_table.at(*node_3->identifier_));
EXPECT_NE(n, symbol_table.at(*edge->identifier_));
}
TYPED_TEST(TestSymbolGenerator, MatchWithCreate) {
// Test MATCH (n) WITH n AS m CREATE (m) -[r :r]-> (m)
const auto *r_type = "r";
auto node_1 = NODE("n");
auto node_2 = NODE("m");
auto edge = EDGE("r", EdgeAtom::Direction::OUT, {r_type});
auto node_3 = NODE("m");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_1)), WITH("n", AS("m")), CREATE(PATTERN(node_2, edge, node_3))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// symbols: pattern * 2, `n`, `m`, `r`
EXPECT_EQ(symbol_table.max_position(), 5);
auto n = symbol_table.at(*node_1->identifier_);
EXPECT_EQ(n.type(), Symbol::Type::VERTEX);
auto m = symbol_table.at(*node_2->identifier_);
EXPECT_NE(n, m);
// Currently we don't infer expression types, so we lost true type of 'm'.
EXPECT_EQ(m.type(), Symbol::Type::ANY);
EXPECT_EQ(m, symbol_table.at(*node_3->identifier_));
}
TYPED_TEST(TestSymbolGenerator, SameResultsWith) {
// Test MATCH (n) WITH n AS m, n AS m
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("m"), "n", AS("m"))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, SameResults) {
// Test MATCH (n) RETURN n, n
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", "n")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, SkipUsingIdentifier) {
// Test MATCH (old) WITH old AS new SKIP old
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new"), SKIP(IDENT("old")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, SkipUsingIdentifierAlias) {
// Test MATCH (old) WITH old AS new SKIP new
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new"), SKIP(IDENT("new")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, LimitUsingIdentifier) {
// Test MATCH (n) RETURN n AS n LIMIT n
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", LIMIT(IDENT("n")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, OrderByAggregation) {
// Test MATCH (old) RETURN old AS new ORDER BY COUNT(1)
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN("old", AS("new"), ORDER_BY(COUNT(LITERAL(1), false)))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, OrderByUnboundVariable) {
// Test MATCH (old) RETURN COUNT(old) AS new ORDER BY old
auto query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN(COUNT(IDENT("old"), false), AS("new"), ORDER_BY(IDENT("old")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, AggregationOrderBy) {
// Test MATCH (old) RETURN COUNT(old) AS new ORDER BY new
auto node = NODE("old");
auto ident_old = IDENT("old");
auto as_new = AS("new");
auto ident_new = IDENT("new");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(COUNT(ident_old, false), as_new, ORDER_BY(ident_new))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern, `old`, `count(old)` and `new`
EXPECT_EQ(symbol_table.max_position(), 4);
auto old = symbol_table.at(*node->identifier_);
EXPECT_EQ(old, symbol_table.at(*ident_old));
auto new_sym = symbol_table.at(*as_new);
EXPECT_NE(old, new_sym);
EXPECT_EQ(new_sym, symbol_table.at(*ident_new));
}
TYPED_TEST(TestSymbolGenerator, OrderByOldVariable) {
// Test MATCH (old) RETURN old AS new ORDER BY old
auto node = NODE("old");
auto ident_old = IDENT("old");
auto as_new = AS("new");
auto by_old = IDENT("old");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(ident_old, as_new, ORDER_BY(by_old))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern, `old` and `new`
EXPECT_EQ(symbol_table.max_position(), 3);
auto old = symbol_table.at(*node->identifier_);
EXPECT_EQ(old, symbol_table.at(*ident_old));
EXPECT_EQ(old, symbol_table.at(*by_old));
auto new_sym = symbol_table.at(*as_new);
EXPECT_NE(old, new_sym);
}
TYPED_TEST(TestSymbolGenerator, MergeVariableError) {
// Test MATCH (n) MERGE (n)
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), MERGE(PATTERN(NODE("n")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, MergeVariableErrorEdge) {
// Test MATCH (n) -[r]- (m) MERGE (a) -[r :rel]- (b)
const auto *rel = "rel";
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")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, MergeEdgeWithoutType) {
// Test MERGE (a) -[r]- (b)
auto query = QUERY(SINGLE_QUERY(MERGE(PATTERN(NODE("a"), EDGE("r"), NODE("b")))));
// Edge must have a type, since it doesn't we raise.
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MergeOnMatchOnCreate) {
// Test MATCH (n) MERGE (n) -[r :rel]- (m) ON MATCH SET n.prop = 42
// ON CREATE SET m.prop = 42 RETURN r AS r
const auto *rel = "rel";
auto prop = this->dba.NameToProperty("prop");
auto match_n = NODE("n");
auto merge_n = NODE("n");
auto edge_r = EDGE("r", EdgeAtom::Direction::BOTH, {rel});
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop);
auto m_prop = PROPERTY_LOOKUP(this->dba, "m", prop);
auto ident_r = IDENT("r");
auto as_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)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for: pattern * 2, `n`, `r`, `m` and `AS r`.
EXPECT_EQ(symbol_table.max_position(), 6);
auto n = symbol_table.at(*match_n->identifier_);
EXPECT_EQ(n, symbol_table.at(*merge_n->identifier_));
EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_)));
auto r = symbol_table.at(*edge_r->identifier_);
EXPECT_NE(r, n);
EXPECT_EQ(r, symbol_table.at(*ident_r));
EXPECT_NE(r, symbol_table.at(*as_r));
auto m = symbol_table.at(*node_m->identifier_);
EXPECT_NE(m, n);
EXPECT_NE(m, r);
EXPECT_NE(m, symbol_table.at(*as_r));
EXPECT_EQ(m, symbol_table.at(*dynamic_cast<Identifier *>(m_prop->expression_)));
}
TYPED_TEST(TestSymbolGenerator, WithUnwindRedeclareReturn) {
// Test WITH [1, 2] AS list UNWIND 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")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, WithUnwindReturn) {
// WITH [1, 2] AS list UNWIND list AS elem RETURN list AS list, elem AS elem
auto with_as_list = AS("list");
auto unwind = UNWIND(IDENT("list"), AS("elem"));
auto ret_list = IDENT("list");
auto ret_as_list = AS("list");
auto ret_elem = IDENT("elem");
auto ret_as_elem = 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)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for: `list`, `elem`, `AS list`, `AS elem`
EXPECT_EQ(symbol_table.max_position(), 4);
const auto &list = symbol_table.at(*with_as_list);
EXPECT_EQ(list, symbol_table.at(*dynamic_cast<Identifier *>(unwind->named_expression_->expression_)));
const auto &elem = symbol_table.at(*unwind->named_expression_);
EXPECT_NE(list, elem);
EXPECT_EQ(list, symbol_table.at(*ret_list));
EXPECT_NE(list, symbol_table.at(*ret_as_list));
EXPECT_EQ(elem, symbol_table.at(*ret_elem));
EXPECT_NE(elem, symbol_table.at(*ret_as_elem));
}
TYPED_TEST(TestSymbolGenerator, MatchCrossReferenceVariable) {
// MATCH (n {prop: m.prop}), (m {prop: n.prop}) RETURN n
auto prop = PROPERTY_PAIR(this->dba, "prop");
auto node_n = NODE("n");
auto m_prop = PROPERTY_LOOKUP(this->dba, "m", prop.second);
std::get<0>(node_n->properties_)[this->storage.GetPropertyIx(prop.first)] = m_prop;
auto node_m = NODE("m");
auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second);
std::get<0>(node_m->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop;
auto ident_n = IDENT("n");
auto as_n = AS("n");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN(ident_n, as_n)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern * 2, `n`, `m` and `AS n`
EXPECT_EQ(symbol_table.max_position(), 5);
auto n = symbol_table.at(*node_n->identifier_);
EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_)));
EXPECT_EQ(n, symbol_table.at(*ident_n));
EXPECT_NE(n, symbol_table.at(*as_n));
auto m = symbol_table.at(*node_m->identifier_);
EXPECT_EQ(m, symbol_table.at(*dynamic_cast<Identifier *>(m_prop->expression_)));
EXPECT_NE(n, m);
EXPECT_NE(m, symbol_table.at(*as_n));
}
TYPED_TEST(TestSymbolGenerator, MatchWithAsteriskReturnAsterisk) {
// MATCH (n) -[e]- (m) WITH * RETURN *, n.prop
auto prop = this->dba.NameToProperty("prop");
auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop);
auto ret = RETURN(n_prop, AS("n.prop"));
ret->body_.all_identifiers = true;
auto node_n = NODE("n");
auto edge = EDGE("e");
auto node_m = NODE("m");
auto with = this->storage.template Create<With>();
with->body_.all_identifiers = true;
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, node_m)), with, ret));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern, `n`, `e`, `m`, `AS n.prop`.
EXPECT_EQ(symbol_table.max_position(), 5);
auto n = symbol_table.at(*node_n->identifier_);
EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_)));
}
TYPED_TEST(TestSymbolGenerator, MatchReturnAsteriskSameResult) {
// MATCH (n) RETURN *, n
auto ret = RETURN("n");
ret->body_.all_identifiers = true;
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), ret));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchReturnAsteriskNoUserVariables) {
// MATCH () RETURN *
auto ret = this->storage.template Create<Return>();
ret->body_.all_identifiers = true;
auto ident_n = this->storage.template Create<Identifier>("anon", false);
auto node = this->storage.template Create<NodeAtom>(ident_n);
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), ret));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchMergeExpandLabel) {
// Test MATCH (n) MERGE (m) -[r :r]-> (n:label)
const auto *r_type = "r";
const auto *label = "label";
auto query =
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
MERGE(PATTERN(NODE("m"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), NODE("n", label)))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchEdgeWithIdentifierInProperty) {
// Test MATCH (n) -[r {prop: n.prop}]- (m) RETURN r
auto prop = PROPERTY_PAIR(this->dba, "prop");
auto edge = EDGE("r");
auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second);
std::get<0>(edge->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop;
auto node_n = NODE("n");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern, `n`, `r`, `m` and implicit in RETURN `r AS r`
EXPECT_EQ(symbol_table.max_position(), 5);
auto n = symbol_table.at(*node_n->identifier_);
EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_)));
}
TYPED_TEST(TestSymbolGenerator, MatchVariablePathUsingIdentifier) {
// Test MATCH (n) -[r *..l.prop]- (m), (l) RETURN r
auto prop = this->dba.NameToProperty("prop");
auto edge = EDGE_VARIABLE("r");
auto l_prop = PROPERTY_LOOKUP(this->dba, "l", prop);
edge->upper_bound_ = l_prop;
auto node_l = NODE("l");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m")), PATTERN(node_l)), RETURN("r")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern * 2, `n`, `r`, inner_node, inner_edge, `m`, `l` and
// implicit in RETURN `r AS r`
EXPECT_EQ(symbol_table.max_position(), 9);
auto l = symbol_table.at(*node_l->identifier_);
EXPECT_EQ(l, symbol_table.at(*dynamic_cast<Identifier *>(l_prop->expression_)));
auto r = symbol_table.at(*edge->identifier_);
EXPECT_EQ(r.type(), Symbol::Type::EDGE_LIST);
}
TYPED_TEST(TestSymbolGenerator, MatchVariablePathUsingUnboundIdentifier) {
// Test MATCH (n) -[r *..l.prop]- (m) MATCH (l) RETURN r
auto prop = this->dba.NameToProperty("prop");
auto edge = EDGE_VARIABLE("r");
auto l_prop = PROPERTY_LOOKUP(this->dba, "l", prop);
edge->upper_bound_ = l_prop;
auto node_l = NODE("l");
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), MATCH(PATTERN(node_l)), RETURN("r")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, CreateVariablePath) {
// Test CREATE (n) -[r *]-> (m) raises a SemanticException, since variable
// paths cannot be created.
auto edge = EDGE_VARIABLE("r", EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::OUT);
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MergeVariablePath) {
// Test MERGE (n) -[r *]-> (m) raises a SemanticException, since variable
// paths cannot be created.
auto edge = EDGE_VARIABLE("r", EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::OUT);
auto query = QUERY(SINGLE_QUERY(MERGE(PATTERN(NODE("n"), edge, NODE("m")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, RedeclareVariablePath) {
// Test MATCH (n) -[n*]-> (m) RETURN n raises RedeclareVariableError.
// This is just a temporary solution, before we add the support for using
// variable paths with already declared symbols. In the future, this test
// should be changed to check for type errors.
auto edge = EDGE_VARIABLE("n", EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::OUT);
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("n")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError);
}
TYPED_TEST(TestSymbolGenerator, VariablePathSameIdentifier) {
// Test MATCH (n) -[r *r.prop..]-> (m) RETURN r raises UnboundVariableError.
// `r` cannot be used inside the range expression, since it is bound by the
// variable expansion itself.
auto prop = this->dba.NameToProperty("prop");
auto edge = EDGE_VARIABLE("r", EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::OUT);
edge->lower_bound_ = PROPERTY_LOOKUP(this->dba, "r", prop);
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchPropertySameIdentifier) {
// Test MATCH (n {prop: n.prop}) RETURN n
// Using `n.prop` needs to work, because filters are run after the value for
// matched symbol is obtained.
auto prop = PROPERTY_PAIR(this->dba, "prop");
auto node_n = NODE("n");
auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second);
std::get<0>(node_n->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop;
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto n = symbol_table.at(*node_n->identifier_);
EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_)));
}
TYPED_TEST(TestSymbolGenerator, WithReturnAll) {
// Test WITH 42 AS x RETURN all(x IN [x] WHERE x = 2) AS x, x AS y
auto *with_as_x = AS("x");
auto *list_x = IDENT("x");
auto *where_x = IDENT("x");
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(SINGLE_QUERY(WITH(LITERAL(42), with_as_x), RETURN(all, ret_as_x, ret_x, AS("y"))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for `WITH .. AS x`, `ALL(x ...)`, `ALL(...) AS x` and `AS y`.
EXPECT_EQ(symbol_table.max_position(), 4);
// Check `WITH .. AS x` is the same as `[x]` and `RETURN ... x AS y`
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*list_x));
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*ret_x));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*all->identifier_));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*ret_as_x));
// Check `ALL(x ...)` is only equal to `WHERE x = 2`
EXPECT_EQ(symbol_table.at(*all->identifier_), symbol_table.at(*where_x));
EXPECT_NE(symbol_table.at(*all->identifier_), symbol_table.at(*ret_as_x));
}
TYPED_TEST(TestSymbolGenerator, WithReturnSingle) {
// Test WITH 42 AS x RETURN single(x IN [x] WHERE x = 2) AS x, x AS y
auto *with_as_x = AS("x");
auto *list_x = IDENT("x");
auto *where_x = IDENT("x");
auto *single = SINGLE("x", LIST(list_x), WHERE(EQ(where_x, LITERAL(2))));
auto *ret_as_x = AS("x");
auto *ret_x = IDENT("x");
auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(42), with_as_x), RETURN(single, ret_as_x, ret_x, AS("y"))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for `WITH .. AS x`, `SINGLE(x ...)`, `SINGLE(...) AS x` and `AS y`.
EXPECT_EQ(symbol_table.max_position(), 4);
// Check `WITH .. AS x` is the same as `[x]` and `RETURN ... x AS y`
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*list_x));
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*ret_x));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*single->identifier_));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*ret_as_x));
// Check `SINGLE(x ...)` is only equal to `WHERE x = 2`
EXPECT_EQ(symbol_table.at(*single->identifier_), symbol_table.at(*where_x));
EXPECT_NE(symbol_table.at(*single->identifier_), symbol_table.at(*ret_as_x));
}
TYPED_TEST(TestSymbolGenerator, WithReturnReduce) {
// Test WITH 42 AS x RETURN reduce(y = 0, x IN [x] y + x) AS x, x AS y
auto *with_as_x = AS("x");
auto *list_x = IDENT("x");
auto *expr_x = IDENT("x");
auto *expr_y = IDENT("y");
auto *reduce = REDUCE("y", LITERAL(0), "x", LIST(list_x), ADD(expr_y, expr_x));
auto *ret_as_x = AS("x");
auto *ret_x = IDENT("x");
auto *ret_as_y = AS("y");
auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(42), with_as_x), RETURN(reduce, ret_as_x, ret_x, ret_as_y)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for `WITH .. AS x`, `REDUCE(y, x ...)`, `REDUCE(...) AS x` and `AS
// y`.
EXPECT_EQ(symbol_table.max_position(), 5);
// Check `WITH .. AS x` is the same as `[x]` and `RETURN ... x AS y`
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*list_x));
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*ret_x));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*reduce->identifier_));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*ret_as_x));
// Check `REDUCE(y, x ...)` is only equal to `y + x`
EXPECT_EQ(symbol_table.at(*reduce->identifier_), symbol_table.at(*expr_x));
EXPECT_NE(symbol_table.at(*reduce->identifier_), symbol_table.at(*ret_as_x));
EXPECT_EQ(symbol_table.at(*reduce->accumulator_), symbol_table.at(*expr_y));
EXPECT_NE(symbol_table.at(*reduce->accumulator_), symbol_table.at(*ret_as_y));
}
TYPED_TEST(TestSymbolGenerator, WithReturnExtract) {
// Test WITH [1, 2, 3] AS x RETURN extract(x IN x | x + 1) AS x, x AS y
auto *with_as_x = AS("x");
auto *list_x = IDENT("x");
auto *expr_x = IDENT("x");
auto *extract = EXTRACT("x", LIST(list_x), ADD(expr_x, LITERAL(1)));
auto *ret_as_x = AS("x");
auto *ret_x = IDENT("x");
auto *ret_as_y = AS("y");
auto query = QUERY(SINGLE_QUERY(WITH(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), with_as_x),
RETURN(extract, ret_as_x, ret_x, ret_as_y)));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for `WITH .. AS x`, `EXTRACT(x ...)`, `EXTRACT(...) AS x` and
// `AS y`.
EXPECT_EQ(symbol_table.max_position(), 4);
// Check `WITH .. AS x` is the same as `... IN x` and `RETURN ... x AS y`
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*list_x));
EXPECT_EQ(symbol_table.at(*with_as_x), symbol_table.at(*ret_x));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*extract->identifier_));
EXPECT_NE(symbol_table.at(*with_as_x), symbol_table.at(*ret_as_x));
// Check `EXTRACT(x ...)` is only equal to `x + 1`
EXPECT_EQ(symbol_table.at(*extract->identifier_), symbol_table.at(*expr_x));
EXPECT_NE(symbol_table.at(*extract->identifier_), symbol_table.at(*ret_as_x));
}
TYPED_TEST(TestSymbolGenerator, MatchBfsReturn) {
// Test MATCH (n) -[r *bfs..n.prop] (r, n | r.prop)]-> (m) RETURN r AS r
auto prop = this->dba.NameToProperty("prop");
auto *node_n = NODE("n");
auto *r_prop = PROPERTY_LOOKUP(this->dba, "r", prop);
auto *n_prop = PROPERTY_LOOKUP(this->dba, "n", prop);
auto *bfs = this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST,
EdgeAtom::Direction::OUT, std::vector<EdgeTypeIx>{});
bfs->filter_lambda_.inner_edge = IDENT("r");
bfs->filter_lambda_.inner_node = IDENT("n");
bfs->filter_lambda_.expression = r_prop;
bfs->upper_bound_ = n_prop;
auto *ret_r = IDENT("r");
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, bfs, NODE("m"))), RETURN(ret_r, AS("r"))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern, `n`, `[r]`, `r|`, `n|`, `m` and `AS r`.
EXPECT_EQ(symbol_table.max_position(), 7);
EXPECT_EQ(symbol_table.at(*ret_r), symbol_table.at(*bfs->identifier_));
EXPECT_NE(symbol_table.at(*ret_r), symbol_table.at(*bfs->filter_lambda_.inner_edge));
EXPECT_TRUE(symbol_table.at(*bfs->filter_lambda_.inner_edge).user_declared());
EXPECT_EQ(symbol_table.at(*bfs->filter_lambda_.inner_edge),
symbol_table.at(*dynamic_cast<Identifier *>(r_prop->expression_)));
EXPECT_NE(symbol_table.at(*node_n->identifier_), symbol_table.at(*bfs->filter_lambda_.inner_node));
EXPECT_TRUE(symbol_table.at(*bfs->filter_lambda_.inner_node).user_declared());
EXPECT_EQ(symbol_table.at(*node_n->identifier_), symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_)));
}
TYPED_TEST(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) {
// Test MATCH (n) -[r *bfs..10 (e, n | r)]-> (m) RETURN r
auto *bfs =
this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT);
bfs->filter_lambda_.inner_edge = IDENT("e");
bfs->filter_lambda_.inner_node = IDENT("n");
bfs->filter_lambda_.expression = IDENT("r");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) {
// Test MATCH (a) -[r *bfs..10 (e, n | a)]-> (m) RETURN r
auto *node_a = NODE("a");
auto *bfs =
this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT);
bfs->filter_lambda_.inner_edge = IDENT("e");
bfs->filter_lambda_.inner_node = IDENT("n");
bfs->filter_lambda_.expression = IDENT("a");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_a, bfs, NODE("m"))), RETURN("r")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.at(*node_a->identifier_),
symbol_table.at(*dynamic_cast<Identifier *>(bfs->filter_lambda_.expression)));
}
TYPED_TEST(TestSymbolGenerator, MatchBfsUsesLaterSymbolError) {
// Test MATCH (n) -[r *bfs..10 (e, n | m)]-> (m) RETURN r
auto *bfs =
this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT);
bfs->filter_lambda_.inner_edge = IDENT("e");
bfs->filter_lambda_.inner_node = IDENT("n");
bfs->filter_lambda_.expression = IDENT("m");
bfs->upper_bound_ = LITERAL(10);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, MatchVariableLambdaSymbols) {
// MATCH ()-[*]-() RETURN 42 AS res
auto ident_n = this->storage.template Create<Identifier>("anon_n", false);
auto node = this->storage.template Create<NodeAtom>(ident_n);
auto edge = this->storage.template Create<EdgeAtom>(this->storage.template Create<Identifier>("anon_r", false),
EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::BOTH);
edge->filter_lambda_.inner_edge = this->storage.template Create<Identifier>("anon_inner_e", false);
edge->filter_lambda_.inner_node = this->storage.template Create<Identifier>("anon_inner_n", false);
auto end_node = this->storage.template Create<NodeAtom>(this->storage.template Create<Identifier>("anon_end", false));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node, edge, end_node)), RETURN(LITERAL(42), AS("res"))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for `anon_n`, `anon_r`, `anon_inner_e`, `anon_inner_n`, `anon_end`
// `AS res` and the auto-generated path name symbol.
EXPECT_EQ(symbol_table.max_position(), 7);
// All symbols except `AS res` are anonymously generated.
for (const auto &symbol : symbol_table.table()) {
if (symbol.second.name() == "res") {
EXPECT_TRUE(symbol.second.user_declared());
} else {
EXPECT_FALSE(symbol.second.user_declared());
}
}
}
TYPED_TEST(TestSymbolGenerator, MatchWShortestReturn) {
// Test MATCH (n) -[r *wShortest (r, n | r.weight) (r, n | r.filter)]-> (m)
// RETURN r AS r
auto weight = this->dba.NameToProperty("weight");
auto filter = this->dba.NameToProperty("filter");
auto *node_n = NODE("n");
auto *r_weight = PROPERTY_LOOKUP(this->dba, "r", weight);
auto *r_filter = PROPERTY_LOOKUP(this->dba, "r", filter);
auto *shortest = this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::WEIGHTED_SHORTEST_PATH,
EdgeAtom::Direction::OUT, std::vector<EdgeTypeIx>{});
{
shortest->weight_lambda_.inner_edge = IDENT("r");
shortest->weight_lambda_.inner_node = IDENT("n");
shortest->weight_lambda_.expression = r_weight;
shortest->total_weight_ = IDENT("total_weight");
}
{
shortest->filter_lambda_.inner_edge = IDENT("r");
shortest->filter_lambda_.inner_node = IDENT("n");
shortest->filter_lambda_.expression = r_filter;
}
auto *ret_r = IDENT("r");
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, shortest, NODE("m"))), RETURN(ret_r, AS("r"))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
// Symbols for pattern, `n`, `[r]`, `total_weight`, (`r|`, `n|`)x2, `m` and
// `AS r`.
EXPECT_EQ(symbol_table.max_position(), 10);
EXPECT_EQ(symbol_table.at(*ret_r), symbol_table.at(*shortest->identifier_));
EXPECT_NE(symbol_table.at(*ret_r), symbol_table.at(*shortest->weight_lambda_.inner_edge));
EXPECT_NE(symbol_table.at(*ret_r), symbol_table.at(*shortest->filter_lambda_.inner_edge));
EXPECT_TRUE(symbol_table.at(*shortest->filter_lambda_.inner_edge).user_declared());
EXPECT_EQ(symbol_table.at(*shortest->weight_lambda_.inner_edge),
symbol_table.at(*dynamic_cast<Identifier *>(r_weight->expression_)));
EXPECT_NE(symbol_table.at(*shortest->weight_lambda_.inner_edge),
symbol_table.at(*shortest->filter_lambda_.inner_edge));
EXPECT_NE(symbol_table.at(*shortest->weight_lambda_.inner_node),
symbol_table.at(*shortest->filter_lambda_.inner_node));
EXPECT_EQ(symbol_table.at(*shortest->filter_lambda_.inner_edge),
symbol_table.at(*dynamic_cast<Identifier *>(r_filter->expression_)));
EXPECT_TRUE(symbol_table.at(*shortest->filter_lambda_.inner_node).user_declared());
}
TYPED_TEST(TestSymbolGenerator, MatchUnionSymbols) {
// RETURN 5 as X UNION RETURN 6 AS x
auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"))), UNION(SINGLE_QUERY(RETURN(LITERAL(6), AS("X")))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 3);
}
TYPED_TEST(TestSymbolGenerator, MatchUnionMultipleSymbols) {
// RETURN 5 as X, 6 AS Y UNION RETURN 5 AS Y, 6 AS x
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")))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 6);
}
TYPED_TEST(TestSymbolGenerator, MatchUnionAllSymbols) {
// RETURN 5 as X UNION ALL RETURN 6 AS x
auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"))), UNION_ALL(SINGLE_QUERY(RETURN(LITERAL(6), AS("X")))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 3);
}
TYPED_TEST(TestSymbolGenerator, MatchUnionAllMultipleSymbols) {
// RETURN 5 as X, 6 AS Y UNION ALL RETURN 5 AS Y, 6 AS x
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")))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 6);
}
TYPED_TEST(TestSymbolGenerator, MatchUnionReturnAllSymbols) {
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 3 AS X, 4 AS Y
auto ret = this->storage.template 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")))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 6);
}
TYPED_TEST(TestSymbolGenerator, MatchUnionReturnSymbols) {
// WITH 1 as X, 2 AS Y RETURN Y, X UNION RETURN 3 AS X, 4 AS Y
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")))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 8);
}
TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNameThrowSemanticException) {
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 3 AS Z, 4 AS Y
auto ret = this->storage.template 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")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNumberThrowSemanticException) {
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 4 AS Y
auto ret = this->storage.template 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")))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, MatchUnion) {
// WITH 5 AS X, 3 AS Y RETURN * UNION WITH 9 AS Y, 4 AS X RETURN Y, X
auto ret = this->storage.template 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"))));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 8);
}
TYPED_TEST(TestSymbolGenerator, CallProcedureYield) {
// WITH 1 AS x CALL proc(x) YIELD x AS y RETURN x, y
auto call = this->storage.template Create<CallProcedure>();
call->procedure_name_ = "proc";
auto *arg_x = IDENT("x");
call->arguments_.push_back(arg_x);
call->result_fields_.emplace_back("x");
call->result_identifiers_.push_back(IDENT("y"));
auto *as_x = AS("x");
auto *ret = RETURN("x", "y");
auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), as_x), call, ret));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
EXPECT_EQ(symbol_table.max_position(), 4);
const auto &sym_x = symbol_table.at(*as_x);
const auto &sym_y = symbol_table.at(*call->result_identifiers_.back());
EXPECT_EQ(symbol_table.at(*arg_x), sym_x);
auto *ret_x = dynamic_cast<Identifier *>(ret->body_.named_expressions[0]->expression_);
ASSERT_TRUE(ret_x);
auto *ret_y = dynamic_cast<Identifier *>(ret->body_.named_expressions[1]->expression_);
ASSERT_TRUE(ret_y);
EXPECT_EQ(symbol_table.at(*ret_x), sym_x);
EXPECT_EQ(symbol_table.at(*ret_y), sym_y);
EXPECT_NE(symbol_table.at(*ret->body_.named_expressions[0]), sym_x);
EXPECT_NE(symbol_table.at(*ret->body_.named_expressions[1]), sym_y);
}
TYPED_TEST(TestSymbolGenerator, CallProcedureShadowingYield) {
// WITH 1 AS x CALL proc() YIELD x RETURN 42 AS res
auto call = this->storage.template Create<CallProcedure>();
call->procedure_name_ = "proc";
call->result_fields_.emplace_back("x");
call->result_identifiers_.push_back(IDENT("x"));
auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("x")), call, RETURN(LITERAL(42), AS("res"))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, CallProcedureShadowingYieldAlias) {
// WITH 1 AS x CALL proc() YIELD y AS x RETURN 42 AS res
auto call = this->storage.template Create<CallProcedure>();
call->procedure_name_ = "proc";
call->result_fields_.emplace_back("y");
call->result_identifiers_.push_back(IDENT("x"));
auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("x")), call, RETURN(LITERAL(42), AS("res"))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, CallProcedureUnboundArgument) {
// CALL proc(unbound)
auto call = this->storage.template Create<CallProcedure>();
call->procedure_name_ = "proc";
call->arguments_.push_back(IDENT("unbound"));
auto query = QUERY(SINGLE_QUERY(call));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, CallWithoutFieldsReturnAsterisk) {
// CALL proc() RETURN *
auto call = this->storage.template Create<CallProcedure>();
call->procedure_name_ = "proc";
auto ret = this->storage.template Create<Return>();
ret->body_.all_identifiers = true;
auto query = QUERY(SINGLE_QUERY(call, ret));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
}
TEST(TestSymbolTable, CreateAnonymousSymbols) {
SymbolTable symbol_table;
auto anon1 = symbol_table.CreateAnonymousSymbol();
ASSERT_EQ(anon1.name_, "anon1");
auto anon2 = symbol_table.CreateAnonymousSymbol();
ASSERT_EQ(anon2.name_, "anon2");
}
TEST(TestSymbolTable, CreateAnonymousSymbolWithExistingUserSymbolCalledAnon) {
SymbolTable symbol_table;
symbol_table.CreateSymbol("anon1", false);
auto anon2 = symbol_table.CreateAnonymousSymbol();
ASSERT_EQ(anon2.name_, "anon2");
}
TYPED_TEST(TestSymbolGenerator, PredefinedIdentifiers) {
auto *first_op = IDENT("first_op", false);
auto *second_op = IDENT("second_op", false);
// RETURN first_op + second_op AS result
auto query = QUERY(SINGLE_QUERY(RETURN(ADD(first_op, second_op), AS("result"))));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
EXPECT_THROW(memgraph::query::MakeSymbolTable(query, {first_op}), SemanticException);
EXPECT_THROW(memgraph::query::MakeSymbolTable(query, {second_op}), SemanticException);
auto symbol_table = memgraph::query::MakeSymbolTable(query, {first_op, second_op});
ASSERT_EQ(symbol_table.max_position(), 3);
// predefined identifier can only be used in one scope
// RETURN first_op + second_op AS result UNION RETURN second_op + first_op AS result
query = QUERY(SINGLE_QUERY(RETURN(ADD(first_op, second_op), AS("result"))),
UNION(SINGLE_QUERY(RETURN(ADD(second_op, first_op), AS("result")))));
ASSERT_THROW(memgraph::query::MakeSymbolTable(query, {first_op, second_op}), SemanticException);
// predefined identifier can be introduced in any of the scope
// different predefined identifiers can be introduced in different scopes
// RETURN first_op AS result UNION RETURN second_op AS result
query = QUERY(SINGLE_QUERY(RETURN(first_op, AS("result"))), UNION(SINGLE_QUERY(RETURN(second_op, AS("result")))));
ASSERT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
symbol_table = memgraph::query::MakeSymbolTable(query, {first_op, second_op});
ASSERT_EQ(symbol_table.max_position(), 5);
// WITH statement resets the scope, but the predefined identifier is okay
// because it's the first introduction of it in the query
// WITH 1 as one RETURN first_op AS first
query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("one")), RETURN(first_op, AS("first"))));
ASSERT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
symbol_table = memgraph::query::MakeSymbolTable(query, {first_op});
ASSERT_EQ(symbol_table.max_position(), 3);
// In the first scope, first_op represents identifier created by match,
// in the second it represent the predefined identifier
// MATCH(first_op) WITH first_op as n RETURN first_op, n
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("first_op"))), WITH("first_op", AS("n")), RETURN("first_op", "n")));
ASSERT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
symbol_table = memgraph::query::MakeSymbolTable(query, {first_op});
ASSERT_EQ(symbol_table.max_position(), 6);
// You cannot redaclare the predefined identifier in the same scope
// UNWIND first_op as u CREATE(first_op {prop: u})
auto unwind = UNWIND(first_op, AS("u"));
auto node = NODE("first_op");
std::get<0>(node->properties_)[this->storage.GetPropertyIx("prop")] =
dynamic_cast<Identifier *>(unwind->named_expression_->expression_);
query = QUERY(SINGLE_QUERY(unwind, CREATE(PATTERN(node))));
ASSERT_THROW(memgraph::query::MakeSymbolTable(query, {first_op}), SemanticException);
}
TYPED_TEST(TestSymbolGenerator, Foreach) {
auto *i = NEXPR("i", IDENT("i"));
auto query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), RETURN("n")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(i, {CREATE(PATTERN(NODE("v")))})));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
ASSERT_EQ(symbol_table.max_position(), 6);
query = QUERY(SINGLE_QUERY(FOREACH(i, {FOREACH(i, {CREATE(PATTERN(NODE("i")))})})));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError);
query = QUERY(SINGLE_QUERY(FOREACH(i, {FOREACH(i, {CREATE(PATTERN(NODE("v")))})})));
symbol_table = memgraph::query::MakeSymbolTable(query);
ASSERT_EQ(symbol_table.max_position(), 4);
query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), RETURN("i")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
}
TYPED_TEST(TestSymbolGenerator, Exists) {
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("", EdgeAtom::Direction::BOTH, {}, false), NODE("m")))), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("r"), NODE("", std::nullopt, false)))), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(EXISTS(PATTERN(NODE("n"), EDGE("r"), NODE("m")))), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
// Symbols for match pattern, node symbol, exists pattern, exists edge, exists second node, named expression in return
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("edge", EdgeAtom::Direction::BOTH, {}, false),
NODE("node", std::nullopt, false)))),
RETURN("n")));
auto symbol_table = MakeSymbolTable(query);
ASSERT_EQ(symbol_table.max_position(), 7);
memgraph::query::plan::UsedSymbolsCollector collector(symbol_table);
auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
auto *expression = dynamic_cast<Expression *>(match->where_->expression_);
expression->Accept(collector);
ASSERT_EQ(collector.symbols_.size(), 1);
auto symbol = *collector.symbols_.begin();
ASSERT_EQ(symbol.name_, "n");
}
TYPED_TEST(TestSymbolGenerator, Subqueries) {
// MATCH (n) CALL { MATCH (n) RETURN n } RETURN n
// Yields exception because n in subquery is referenced in outer scope
auto subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n")));
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
// MATCH (n) CALL { MATCH (m) RETURN m.prop } RETURN n
// Yields exception because m.prop must be aliased before returning
subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), RETURN("m.prop")));
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
// MATCH (n) CALL { MATCH (m) RETURN m, m.prop } RETURN n
// Yields exception because m.prop must be aliased before returning
subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), RETURN("m", "m.prop")));
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
// MATCH (n) CALL { MATCH (m) RETURN m.prop, m } RETURN n
// Yields exception because m.prop must be aliased before returning
subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), RETURN("m.prop", "m")));
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
// MATCH (n) CALL { MATCH (m) RETURN m } RETURN n, m
subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), RETURN("m")));
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n", "m")));
auto symbol_table = MakeSymbolTable(query);
ASSERT_EQ(symbol_table.max_position(), 7);
// MATCH (n) CALL { MATCH (m) RETURN m UNION MATCH (m) RETURN m } RETURN n, m
subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), RETURN("m")),
UNION(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), RETURN("m"))));
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n", "m")));
symbol_table = MakeSymbolTable(query);
ASSERT_EQ(symbol_table.max_position(), 11);
// MATCH (n) CALL { MATCH (s) RETURN s } RETURN n UNION MATCH (n) CALL { MATCH (s) RETURN s } RETURN n
subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("s"))), RETURN("s")));
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n")),
UNION(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n"))));
symbol_table = MakeSymbolTable(query);
ASSERT_EQ(symbol_table.max_position(), 13);
}
TYPED_TEST(TestSymbolGenerator, PropertyCachingSingleLookup) {
// WITH {icode: 0000} AS item
// RETURN {icode: item.icode} AS new_map;
auto prop1_key = this->storage.GetPropertyIx("icode");
auto prop1_val = PROPERTY_LOOKUP(this->dba, "item", this->dba.NameToProperty("icode"));
auto has_properties = MAP({prop1_key, LITERAL(0000)});
auto new_map = MAP({prop1_key, prop1_val});
auto query = QUERY(SINGLE_QUERY(WITH(has_properties, AS("item")), RETURN(new_map, AS("new_map"))));
memgraph::query::MakeSymbolTable(query);
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[prop1_key])->evaluation_mode_;
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
}
TYPED_TEST(TestSymbolGenerator, PropertyCachingTwoSingleLookups) {
// WITH {icode: 0000} AS item1, {icode: 1111} AS item2
// RETURN {icode1: item1.icode, icode2: item2.icode} AS new_map;
auto in_prop1_key = this->storage.GetPropertyIx("icode");
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
auto out_prop2_key = this->storage.GetPropertyIx("icode2");
auto out_prop2_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)});
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)});
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop2_key, out_prop2_val});
auto query = QUERY(
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
memgraph::query::MakeSymbolTable(query);
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop2_key])->evaluation_mode_;
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
}
TYPED_TEST(TestSymbolGenerator, PropertyCachingMultipleLookup) {
// WITH {icode: 0000, price: 10} AS item
// RETURN {icode: item.icode, price: item.price} AS new_map;
auto prop1_key = this->storage.GetPropertyIx("icode");
auto prop1_val = PROPERTY_LOOKUP(this->dba, "item", this->dba.NameToProperty("icode"));
auto prop2_key = this->storage.GetPropertyIx("price");
auto prop2_val = PROPERTY_LOOKUP(this->dba, "item", this->dba.NameToProperty("price"));
auto has_properties = MAP({prop1_key, LITERAL(0000)}, {prop2_key, LITERAL(10)});
auto new_map = MAP({prop1_key, prop1_val}, {prop2_key, prop2_val});
auto query = QUERY(SINGLE_QUERY(WITH(has_properties, AS("item")), RETURN(new_map, AS("new_map"))));
memgraph::query::MakeSymbolTable(query);
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[prop1_key])->evaluation_mode_;
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[prop2_key])->evaluation_mode_;
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
}
TYPED_TEST(TestSymbolGenerator, PropertyCachingTwoMultipleLookups) {
// WITH {icode: 0000, price: 10} AS item1, {icode: 1111, price: 16} AS item2
// RETURN {icode1: item1.icode, price1: item1.price, icode2: item2.icode, price2: item2.price} AS new_map;
auto in_prop1_key = this->storage.GetPropertyIx("icode");
auto in_prop2_key = this->storage.GetPropertyIx("price");
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
auto out_prop2_key = this->storage.GetPropertyIx("price1");
auto out_prop2_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("price"));
auto out_prop3_key = this->storage.GetPropertyIx("icode2");
auto out_prop3_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
auto out_prop4_key = this->storage.GetPropertyIx("price2");
auto out_prop4_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("price"));
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)}, {in_prop2_key, LITERAL(10)});
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)}, {in_prop2_key, LITERAL(16)});
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop2_key, out_prop2_val}, {out_prop3_key, out_prop3_val},
{out_prop4_key, out_prop4_val});
auto query = QUERY(
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
memgraph::query::MakeSymbolTable(query);
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop2_key])->evaluation_mode_;
auto prop3_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop3_key])->evaluation_mode_;
auto prop4_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop4_key])->evaluation_mode_;
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
ASSERT_TRUE(prop3_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
ASSERT_TRUE(prop4_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
}
TYPED_TEST(TestSymbolGenerator, PropertyCachingMixedLookups1) {
// WITH {icode: 0000, price: 10} AS item1, {icode: 1111, price: 16} AS item2
// RETURN {icode1: item1.icode, price1: item1.price, icode2: item2.icode} AS new_map;
auto in_prop1_key = this->storage.GetPropertyIx("icode");
auto in_prop2_key = this->storage.GetPropertyIx("price");
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
auto out_prop2_key = this->storage.GetPropertyIx("price1");
auto out_prop2_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("price"));
auto out_prop3_key = this->storage.GetPropertyIx("icode2");
auto out_prop3_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)}, {in_prop2_key, LITERAL(10)});
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)}, {in_prop2_key, LITERAL(16)});
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop2_key, out_prop2_val}, {out_prop3_key, out_prop3_val});
auto query = QUERY(
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
memgraph::query::MakeSymbolTable(query);
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop2_key])->evaluation_mode_;
auto prop3_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop3_key])->evaluation_mode_;
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
ASSERT_TRUE(prop3_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
}
TYPED_TEST(TestSymbolGenerator, PropertyCachingMixedLookups2) {
// WITH {icode: 0000, price: 10} AS item1, {icode: 1111, price: 16} AS item2
// RETURN {icode1: item1.icode, icode2: item2.icode, price2: item2.price} AS new_map;
auto in_prop1_key = this->storage.GetPropertyIx("icode");
auto in_prop2_key = this->storage.GetPropertyIx("price");
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
auto out_prop3_key = this->storage.GetPropertyIx("icode2");
auto out_prop3_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
auto out_prop4_key = this->storage.GetPropertyIx("price2");
auto out_prop4_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("price"));
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)}, {in_prop2_key, LITERAL(10)});
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)}, {in_prop2_key, LITERAL(16)});
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop3_key, out_prop3_val}, {out_prop4_key, out_prop4_val});
auto query = QUERY(
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
memgraph::query::MakeSymbolTable(query);
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
auto prop3_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop3_key])->evaluation_mode_;
auto prop4_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop4_key])->evaluation_mode_;
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
ASSERT_TRUE(prop3_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
ASSERT_TRUE(prop4_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
}