Use new/old on Accessor in logical operators
Summary: Replace includes in operator.hpp with forward declarations. Provide basic documentation for remaining operators. Use SwitchOld on accessors used in Expand and Filter operations. Add SwitchOld and SwitchNew to ExpressionEvaluator. Evaluate new or old state in operators. Test operators use correct accessors. Add some basic tests for cases where switching accessors to old and new matters. Reviewers: mislav.bradac, buda, florijan Reviewed By: florijan Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D208
This commit is contained in:
parent
9ce2081103
commit
05d19f6218
@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/exceptions/not_yet_implemented.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "query/backend/cpp/typed_value.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
#include <utils/exceptions/not_yet_implemented.hpp>
|
||||
|
||||
namespace query {
|
||||
|
||||
@ -29,6 +30,22 @@ class ExpressionEvaluator : public TreeVisitorBase {
|
||||
ExpressionEvaluator(Frame &frame, SymbolTable &symbol_table)
|
||||
: frame_(frame), symbol_table_(symbol_table) {}
|
||||
|
||||
/** When evaluting @c RecordAccessor, use @c SwitchNew to get the new data, as
|
||||
* modified during the current command.
|
||||
*/
|
||||
auto &SwitchNew() {
|
||||
use_new_ = true;
|
||||
return *this;
|
||||
};
|
||||
|
||||
/** When evaluting @c RecordAccessor, use @c SwitchOld to get the old data,
|
||||
* before the modification done by the current command.
|
||||
*/
|
||||
auto &SwitchOld() {
|
||||
use_new_ = false;
|
||||
return *this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes and returns the last value from the result stack.
|
||||
* Consumers of this function are PostVisit functions for
|
||||
@ -51,7 +68,9 @@ class ExpressionEvaluator : public TreeVisitorBase {
|
||||
}
|
||||
|
||||
void Visit(Identifier &ident) override {
|
||||
result_stack_.push_back(frame_[symbol_table_[ident]]);
|
||||
auto value = frame_[symbol_table_[ident]];
|
||||
SwitchAccessors(value);
|
||||
result_stack_.emplace_back(std::move(value));
|
||||
}
|
||||
|
||||
#define BINARY_OPERATOR_VISITOR(OP_NODE, CPP_OP) \
|
||||
@ -97,12 +116,12 @@ class ExpressionEvaluator : public TreeVisitorBase {
|
||||
expression_result.Value<VertexAccessor>().PropsAt(
|
||||
property_lookup.property_));
|
||||
break;
|
||||
case TypedValue::Type::Edge:
|
||||
case TypedValue::Type::Edge: {
|
||||
result_stack_.emplace_back(
|
||||
expression_result.Value<EdgeAccessor>().PropsAt(
|
||||
property_lookup.property_));
|
||||
break;
|
||||
|
||||
}
|
||||
case TypedValue::Type::Map:
|
||||
// TODO implement me
|
||||
throw NotYetImplemented();
|
||||
@ -121,8 +140,44 @@ class ExpressionEvaluator : public TreeVisitorBase {
|
||||
}
|
||||
|
||||
private:
|
||||
// If the given TypedValue contains accessors, switch them to New or Old,
|
||||
// depending on use_new_ flag.
|
||||
void SwitchAccessors(TypedValue &value) {
|
||||
switch (value.type()) {
|
||||
case TypedValue::Type::Vertex: {
|
||||
auto &vertex = value.Value<VertexAccessor>();
|
||||
if (use_new_)
|
||||
vertex.SwitchNew();
|
||||
else
|
||||
vertex.SwitchOld();
|
||||
break;
|
||||
}
|
||||
case TypedValue::Type::Edge: {
|
||||
auto &edge = value.Value<EdgeAccessor>();
|
||||
if (use_new_)
|
||||
edge.SwitchNew();
|
||||
else
|
||||
edge.SwitchOld();
|
||||
break;
|
||||
}
|
||||
case TypedValue::Type::List: {
|
||||
auto &list = value.Value<std::vector<TypedValue>>();
|
||||
for (auto &list_value : list) SwitchAccessors(list_value);
|
||||
}
|
||||
case TypedValue::Type::Map: {
|
||||
auto &map = value.Value<std::map<std::string, TypedValue>>();
|
||||
for (auto &kv : map) SwitchAccessors(kv.second);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Frame &frame_;
|
||||
SymbolTable &symbol_table_;
|
||||
std::list<TypedValue> result_stack_;
|
||||
// If true, use SwitchNew on evaluated record accessors. This should be done
|
||||
// only in expressions which may return one. E.g. identifier, list indexing.
|
||||
bool use_new_ = false;
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "operator.hpp"
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/logical/operator.hpp"
|
||||
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/interpret/interpret.hpp"
|
||||
|
||||
namespace query {
|
||||
namespace plan {
|
||||
@ -47,6 +49,9 @@ void CreateNode::CreateNodeCursor::Create(Frame &frame,
|
||||
for (auto label : self_.node_atom_->labels_) new_node.add_label(label);
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Evaluator should use the latest accessors, as modified in this query, when
|
||||
// setting properties on new nodes.
|
||||
evaluator.SwitchNew();
|
||||
for (auto &kv : self_.node_atom_->properties_) {
|
||||
kv.second->Accept(evaluator);
|
||||
new_node.PropsSet(kv.first, evaluator.PopBack());
|
||||
@ -82,13 +87,19 @@ bool CreateExpand::CreateExpandCursor::Pull(Frame &frame,
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
// get the origin vertex
|
||||
TypedValue vertex_value = frame[self_.input_symbol_];
|
||||
auto v1 = vertex_value.Value<VertexAccessor>();
|
||||
TypedValue &vertex_value = frame[self_.input_symbol_];
|
||||
auto &v1 = vertex_value.Value<VertexAccessor>();
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Similarly to CreateNode, newly created edges and nodes should use the
|
||||
// latest accesors.
|
||||
// E.g. we pickup new properties: `CREATE (n {p: 42}) -[:r {ep: n.p}]-> ()`
|
||||
v1.SwitchNew();
|
||||
evaluator.SwitchNew();
|
||||
|
||||
// get the destination vertex (possibly an existing node)
|
||||
VertexAccessor v2 = OtherVertex(frame, symbol_table, evaluator);
|
||||
auto &v2 = OtherVertex(frame, symbol_table, evaluator);
|
||||
v2.SwitchNew();
|
||||
|
||||
// create an edge between the two nodes
|
||||
switch (self_.edge_atom_->direction_) {
|
||||
@ -105,7 +116,7 @@ bool CreateExpand::CreateExpandCursor::Pull(Frame &frame,
|
||||
return true;
|
||||
}
|
||||
|
||||
VertexAccessor CreateExpand::CreateExpandCursor::OtherVertex(
|
||||
VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(
|
||||
Frame &frame, SymbolTable &symbol_table, ExpressionEvaluator &evaluator) {
|
||||
if (self_.node_existing_) {
|
||||
TypedValue &dest_node_value =
|
||||
@ -119,8 +130,9 @@ VertexAccessor CreateExpand::CreateExpandCursor::OtherVertex(
|
||||
kv.second->Accept(evaluator);
|
||||
node.PropsSet(kv.first, evaluator.PopBack());
|
||||
}
|
||||
frame[symbol_table[*self_.node_atom_->identifier_]] = node;
|
||||
return node;
|
||||
auto symbol = symbol_table[*self_.node_atom_->identifier_];
|
||||
frame[symbol] = node;
|
||||
return frame[symbol].Value<VertexAccessor>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,8 +220,13 @@ bool Expand::ExpandCursor::Pull(Frame &frame, SymbolTable &symbol_table) {
|
||||
bool Expand::ExpandCursor::InitEdges(Frame &frame, SymbolTable &symbol_table) {
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
TypedValue vertex_value = frame[self_.input_symbol_];
|
||||
auto vertex = vertex_value.Value<VertexAccessor>();
|
||||
TypedValue &vertex_value = frame[self_.input_symbol_];
|
||||
auto &vertex = vertex_value.Value<VertexAccessor>();
|
||||
// We don't want newly created edges, so switch to old. If we included new
|
||||
// edges, e.g. created by CreateExpand operator, the behaviour would be wrong
|
||||
// and may cause infinite loops by continually creating edges and traversing
|
||||
// them.
|
||||
vertex.SwitchOld();
|
||||
|
||||
auto direction = self_.edge_atom_->direction_;
|
||||
if (direction == EdgeAtom::Direction::LEFT ||
|
||||
@ -294,7 +311,10 @@ NodeFilter::NodeFilterCursor::NodeFilterCursor(NodeFilter &self,
|
||||
bool NodeFilter::NodeFilterCursor::Pull(Frame &frame,
|
||||
SymbolTable &symbol_table) {
|
||||
while (input_cursor_->Pull(frame, symbol_table)) {
|
||||
const auto &vertex = frame[self_.input_symbol_].Value<VertexAccessor>();
|
||||
auto &vertex = frame[self_.input_symbol_].Value<VertexAccessor>();
|
||||
// Filter needs to use the old, unmodified vertex, even though we may change
|
||||
// properties or labels during the current command.
|
||||
vertex.SwitchOld();
|
||||
if (VertexPasses(vertex, frame, symbol_table)) return true;
|
||||
}
|
||||
return false;
|
||||
@ -307,6 +327,8 @@ bool NodeFilter::NodeFilterCursor::VertexPasses(const VertexAccessor &vertex,
|
||||
if (!vertex.has_label(label)) return false;
|
||||
|
||||
ExpressionEvaluator expression_evaluator(frame, symbol_table);
|
||||
// We don't want newly set properties to affect filtering.
|
||||
expression_evaluator.SwitchOld();
|
||||
for (auto prop_pair : self_.node_atom_->properties_) {
|
||||
prop_pair.second->Accept(expression_evaluator);
|
||||
TypedValue comparison_result =
|
||||
@ -338,7 +360,10 @@ EdgeFilter::EdgeFilterCursor::EdgeFilterCursor(EdgeFilter &self,
|
||||
bool EdgeFilter::EdgeFilterCursor::Pull(Frame &frame,
|
||||
SymbolTable &symbol_table) {
|
||||
while (input_cursor_->Pull(frame, symbol_table)) {
|
||||
const auto &edge = frame[self_.input_symbol_].Value<EdgeAccessor>();
|
||||
auto &edge = frame[self_.input_symbol_].Value<EdgeAccessor>();
|
||||
// Filter needs to use the old, unmodified edge, even though we may change
|
||||
// properties or types during the current command.
|
||||
edge.SwitchOld();
|
||||
if (EdgePasses(edge, frame, symbol_table)) return true;
|
||||
}
|
||||
return false;
|
||||
@ -355,6 +380,8 @@ bool EdgeFilter::EdgeFilterCursor::EdgePasses(const EdgeAccessor &edge,
|
||||
return false;
|
||||
|
||||
ExpressionEvaluator expression_evaluator(frame, symbol_table);
|
||||
// We don't want newly set properties to affect filtering.
|
||||
expression_evaluator.SwitchOld();
|
||||
for (auto prop_pair : self_.edge_atom_->properties_) {
|
||||
prop_pair.second->Accept(expression_evaluator);
|
||||
TypedValue comparison_result =
|
||||
@ -385,6 +412,9 @@ Filter::FilterCursor::FilterCursor(Filter &self, GraphDbAccessor &db)
|
||||
|
||||
bool Filter::FilterCursor::Pull(Frame &frame, SymbolTable &symbol_table) {
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Like all filters, newly set values should not affect filtering of old nodes
|
||||
// and edges.
|
||||
evaluator.SwitchOld();
|
||||
while (input_cursor_->Pull(frame, symbol_table)) {
|
||||
self_.expression_->Accept(evaluator);
|
||||
TypedValue result = evaluator.PopBack();
|
||||
@ -418,6 +448,8 @@ Produce::ProduceCursor::ProduceCursor(Produce &self, GraphDbAccessor &db)
|
||||
input_cursor_(self.input_ ? self_.input_->MakeCursor(db) : nullptr) {}
|
||||
bool Produce::ProduceCursor::Pull(Frame &frame, SymbolTable &symbol_table) {
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Produce should always yield the latest results.
|
||||
evaluator.SwitchNew();
|
||||
if (input_cursor_) {
|
||||
if (input_cursor_->Pull(frame, symbol_table)) {
|
||||
for (auto named_expr : self_.named_expressions_)
|
||||
@ -455,6 +487,9 @@ bool Delete::DeleteCursor::Pull(Frame &frame, SymbolTable &symbol_table) {
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Delete should get the latest information, this way it is also possible to
|
||||
// delete newly added nodes and edges.
|
||||
evaluator.SwitchNew();
|
||||
for (Expression *expression : self_.expressions_) {
|
||||
expression->Accept(evaluator);
|
||||
TypedValue value = evaluator.PopBack();
|
||||
@ -505,6 +540,8 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame,
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Set, just like Create needs to see the latest changes.
|
||||
evaluator.SwitchNew();
|
||||
self_.lhs_->expression_->Accept(evaluator);
|
||||
TypedValue lhs = evaluator.PopBack();
|
||||
self_.rhs_->Accept(evaluator);
|
||||
@ -551,9 +588,11 @@ bool SetProperties::SetPropertiesCursor::Pull(Frame &frame,
|
||||
SymbolTable &symbol_table) {
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
TypedValue lhs = frame[self_.input_symbol_];
|
||||
TypedValue &lhs = frame[self_.input_symbol_];
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Set, just like Create needs to see the latest changes.
|
||||
evaluator.SwitchNew();
|
||||
self_.rhs_->Accept(evaluator);
|
||||
TypedValue rhs = evaluator.PopBack();
|
||||
|
||||
@ -574,6 +613,7 @@ bool SetProperties::SetPropertiesCursor::Pull(Frame &frame,
|
||||
template <typename TRecordAccessor>
|
||||
void SetProperties::SetPropertiesCursor::Set(TRecordAccessor &record,
|
||||
const TypedValue &rhs) {
|
||||
record.SwitchNew();
|
||||
if (self_.op_ == Op::REPLACE) record.PropsClear();
|
||||
|
||||
auto set_props = [&record](const auto &properties) {
|
||||
@ -630,8 +670,9 @@ SetLabels::SetLabelsCursor::SetLabelsCursor(SetLabels &self,
|
||||
bool SetLabels::SetLabelsCursor::Pull(Frame &frame, SymbolTable &symbol_table) {
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
TypedValue vertex_value = frame[self_.input_symbol_];
|
||||
VertexAccessor vertex = vertex_value.Value<VertexAccessor>();
|
||||
TypedValue &vertex_value = frame[self_.input_symbol_];
|
||||
auto &vertex = vertex_value.Value<VertexAccessor>();
|
||||
vertex.SwitchNew();
|
||||
for (auto label : self_.labels_) vertex.add_label(label);
|
||||
|
||||
return true;
|
||||
@ -660,6 +701,8 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame,
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
ExpressionEvaluator evaluator(frame, symbol_table);
|
||||
// Remove, just like Delete needs to see the latest changes.
|
||||
evaluator.SwitchNew();
|
||||
self_.lhs_->expression_->Accept(evaluator);
|
||||
TypedValue lhs = evaluator.PopBack();
|
||||
|
||||
@ -702,8 +745,9 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(
|
||||
Frame &frame, SymbolTable &symbol_table) {
|
||||
if (!input_cursor_->Pull(frame, symbol_table)) return false;
|
||||
|
||||
TypedValue vertex_value = frame[self_.input_symbol_];
|
||||
VertexAccessor vertex = vertex_value.Value<VertexAccessor>();
|
||||
TypedValue &vertex_value = frame[self_.input_symbol_];
|
||||
auto &vertex = vertex_value.Value<VertexAccessor>();
|
||||
vertex.SwitchNew();
|
||||
for (auto label : self_.labels_) vertex.remove_label(label);
|
||||
|
||||
return true;
|
||||
|
@ -1,3 +1,5 @@
|
||||
/** @file */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
@ -5,17 +7,33 @@
|
||||
|
||||
#include "database/graph_db_accessor.hpp"
|
||||
#include "database/graph_db_datatypes.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/interpret/interpret.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "utils/visitor/visitable.hpp"
|
||||
#include "utils/visitor/visitor.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
class Frame;
|
||||
class ExpressionEvaluator;
|
||||
|
||||
namespace plan {
|
||||
|
||||
/** @brief Base class for iteration cursors of @c LogicalOperator classes.
|
||||
*
|
||||
* Each @c LogicalOperator must produce a concrete @c Cursor, which provides
|
||||
* the iteration mechanism.
|
||||
*/
|
||||
class Cursor {
|
||||
public:
|
||||
/** @brief Run an iteration of a @c LogicalOperator.
|
||||
*
|
||||
* Since operators may be chained, the iteration may pull results from
|
||||
* multiple operators.
|
||||
*
|
||||
* @param Frame May be read from or written to while performing the
|
||||
* iteration.
|
||||
* @param SymbolTable Used to get the position of symbols in frame.
|
||||
*/
|
||||
virtual bool Pull(Frame &, SymbolTable &) = 0;
|
||||
virtual ~Cursor() {}
|
||||
};
|
||||
@ -35,31 +53,43 @@ class SetLabels;
|
||||
class RemoveProperty;
|
||||
class RemoveLabels;
|
||||
|
||||
/** @brief Base class for visitors of @c LogicalOperator class hierarchy. */
|
||||
using LogicalOperatorVisitor =
|
||||
::utils::Visitor<CreateNode, CreateExpand, ScanAll, Expand, NodeFilter,
|
||||
EdgeFilter, Filter, Produce, Delete, SetProperty,
|
||||
SetProperties, SetLabels, RemoveProperty, RemoveLabels>;
|
||||
|
||||
/** @brief Base class for logical operators.
|
||||
*
|
||||
* Each operator describes an operation, which is to be performed on the
|
||||
* database. Operators are iterated over using a @c Cursor. Various operators
|
||||
* can serve as inputs to others and thus a sequence of operations is formed.
|
||||
*/
|
||||
class LogicalOperator : public ::utils::Visitable<LogicalOperatorVisitor> {
|
||||
public:
|
||||
/** @brief Constructs a @c Cursor which is used to run this operator.
|
||||
*
|
||||
* @param GraphDbAccessor Used to perform operations on the database.
|
||||
*/
|
||||
virtual std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) = 0;
|
||||
virtual ~LogicalOperator() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Operator for creating a node. This op is used both for
|
||||
* creating a single node (CREATE statement without
|
||||
* a preceeding MATCH), or multiple nodes (MATCH CREATE).
|
||||
/** @brief Operator for creating a node.
|
||||
*
|
||||
* This node
|
||||
* This op is used both for creating a single node (`CREATE` statement without
|
||||
* a preceeding `MATCH`), or multiple nodes (`MATCH ... CREATE` or
|
||||
* `CREATE (), () ...`).
|
||||
*
|
||||
* @sa CreateExpand
|
||||
*/
|
||||
class CreateNode : public LogicalOperator {
|
||||
public:
|
||||
/**
|
||||
*
|
||||
* @param node_atom
|
||||
* @param input Optional. If nullptr, then a single node will be
|
||||
* created (a single successful Pull from this Op's Cursor).
|
||||
* @param node_atom @c NodeAtom with information on how to create a node.
|
||||
* @param input Optional. If @c nullptr, then a single node will be
|
||||
* created (a single successful @c Cursor::Pull from this op's @c Cursor).
|
||||
* If a valid input, then a node will be created for each
|
||||
* successful pull from the given input.
|
||||
*/
|
||||
@ -92,8 +122,33 @@ class CreateNode : public LogicalOperator {
|
||||
};
|
||||
};
|
||||
|
||||
/** @brief Operator for creating edges and destination nodes.
|
||||
*
|
||||
* This operator extends already created nodes with an edge. If the other node
|
||||
* on the edge does not exist, it will be created. For example, in `MATCH (n)
|
||||
* CREATE (n) -[r:r]-> (n)` query, this operator will create just the edge `r`.
|
||||
* In `MATCH (n) CREATE (n) -[r:r]-> (m)` query, the operator will create both
|
||||
* the edge `r` and the node `m`. In case of `CREATE (n) -[r:r]-> (m)` the
|
||||
* first node `n` is created by @c CreateNode operator, while @c CreateExpand
|
||||
* will create the edge `r` and `m`. Similarly, multiple @c CreateExpand are
|
||||
* chained in cases when longer paths need creating.
|
||||
*
|
||||
* @sa CreateNode
|
||||
*/
|
||||
class CreateExpand : public LogicalOperator {
|
||||
public:
|
||||
/** @brief Construct @c CreateExpand.
|
||||
*
|
||||
* @param node_atom @c NodeAtom at the end of the edge. Used to create a node,
|
||||
* unless it refers to an existing one.
|
||||
* @param edge_atom @c EdgeAtom with information for the edge to be created.
|
||||
* @param input Required. Previous @c LogicalOperator which will be pulled.
|
||||
* For each successful @c Cursor::Pull, this operator will create an
|
||||
* expansion.
|
||||
* @param input_symbol @c Symbol for the node at the start of the edge.
|
||||
* @param node_existing @c bool indicating whether the @c node_atom refers to
|
||||
* an existing node. If @c false, the operator will also create the node.
|
||||
*/
|
||||
CreateExpand(NodeAtom *node_atom, EdgeAtom *edge_atom,
|
||||
const std::shared_ptr<LogicalOperator> &input,
|
||||
const Symbol &input_symbol, bool node_existing);
|
||||
@ -126,10 +181,10 @@ class CreateExpand : public LogicalOperator {
|
||||
|
||||
/**
|
||||
* Helper function for getting an existing node or creating a new one.
|
||||
* @return The newly created or already existing node.
|
||||
* @return The newly created or already existing node.
|
||||
*/
|
||||
VertexAccessor OtherVertex(Frame &frame, SymbolTable &symbol_table,
|
||||
ExpressionEvaluator &evaluator);
|
||||
VertexAccessor &OtherVertex(Frame &frame, SymbolTable &symbol_table,
|
||||
ExpressionEvaluator &evaluator);
|
||||
|
||||
/**
|
||||
* Helper function for creating an edge and adding it
|
||||
@ -144,6 +199,9 @@ class CreateExpand : public LogicalOperator {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Operator which iterates over all the nodes currently in the database.
|
||||
*/
|
||||
class ScanAll : public LogicalOperator {
|
||||
public:
|
||||
ScanAll(NodeAtom *node_atom);
|
||||
@ -166,7 +224,7 @@ class ScanAll : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Expansion operator. For a node existing in the frame it
|
||||
* @brief Expansion operator. For a node existing in the frame it
|
||||
* expands one edge and one node and places them on the frame.
|
||||
*
|
||||
* This class does not handle node/edge filtering based on
|
||||
@ -174,7 +232,7 @@ class ScanAll : public LogicalOperator {
|
||||
* cycle filtering.
|
||||
*
|
||||
* Cycle filtering means that for a pattern that references
|
||||
* the same node or edge in two places (for example (n)-->(n)),
|
||||
* the same node or edge in two places (for example `(n)-->(n)`),
|
||||
* only expansions that match defined equalities are succesfully
|
||||
* pulled.
|
||||
*/
|
||||
@ -187,7 +245,7 @@ class Expand : public LogicalOperator {
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates an expansion.
|
||||
* @brief Creates an expansion.
|
||||
*
|
||||
* Cycle-checking is controlled via booleans. A true value
|
||||
* simply denotes that this expansion references an already
|
||||
@ -278,8 +336,19 @@ class Expand : public LogicalOperator {
|
||||
};
|
||||
};
|
||||
|
||||
/** @brief Operator which filters nodes by labels and properties.
|
||||
*
|
||||
* This operator is used to implement `MATCH (n :label {prop: value})`, so that
|
||||
* it filters nodes with specified labels and properties by value.
|
||||
*/
|
||||
class NodeFilter : public LogicalOperator {
|
||||
public:
|
||||
/** @brief Construct @c NodeFilter.
|
||||
*
|
||||
* @param input Required, preceding @c LogicalOperator.
|
||||
* @param input_symbol @c Symbol where the node to be filtered is stored.
|
||||
* @param node_atom @c NodeAtom with labels and properties to filter by.
|
||||
*/
|
||||
NodeFilter(std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
|
||||
NodeAtom *node_atom);
|
||||
void Accept(LogicalOperatorVisitor &visitor) override;
|
||||
@ -306,8 +375,19 @@ class NodeFilter : public LogicalOperator {
|
||||
};
|
||||
};
|
||||
|
||||
/** @brief Operator which filters edges by relationship type and properties.
|
||||
*
|
||||
* This operator is used to implement `MATCH () -[r :label {prop: value}]- ()`,
|
||||
* so that it filters edges with specified types and properties by value.
|
||||
*/
|
||||
class EdgeFilter : public LogicalOperator {
|
||||
public:
|
||||
/** @brief Construct @c EdgeFilter.
|
||||
*
|
||||
* @param input Required, preceding @c LogicalOperator.
|
||||
* @param input_symbol @c Symbol where the edge to be filtered is stored.
|
||||
* @param edge_atom @c EdgeAtom with edge types and properties to filter by.
|
||||
*/
|
||||
EdgeFilter(std::shared_ptr<LogicalOperator> input, Symbol input_symbol,
|
||||
EdgeAtom *edge_atom);
|
||||
void Accept(LogicalOperatorVisitor &visitor) override;
|
||||
@ -335,9 +415,11 @@ class EdgeFilter : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter whose Pull returns true only when the given expression
|
||||
* evaluates into true. The given expression is assumed to
|
||||
* return either NULL (treated as false) or a boolean value.
|
||||
* @brief Filter whose Pull returns true only when the given expression
|
||||
* evaluates into true.
|
||||
*
|
||||
* The given expression is assumed to return either NULL (treated as false) or a
|
||||
* boolean value.
|
||||
*/
|
||||
class Filter : public LogicalOperator {
|
||||
public:
|
||||
@ -362,7 +444,7 @@ class Filter : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* A logical operator that places an arbitrary number
|
||||
* @brief A logical operator that places an arbitrary number
|
||||
* if named expressions on the frame (the logical operator
|
||||
* for the RETURN clause).
|
||||
*
|
||||
@ -399,7 +481,8 @@ class Produce : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Operator for deleting vertices and edges.
|
||||
* @brief Operator for deleting vertices and edges.
|
||||
*
|
||||
* Has a flag for using DETACH DELETE when deleting
|
||||
* vertices.
|
||||
*/
|
||||
@ -431,11 +514,10 @@ class Delete : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Logical Op for setting a single property
|
||||
* on a single vertex or edge. The property value
|
||||
* is an expression that must evaluate to some
|
||||
* type that can be stored (a TypedValue that can
|
||||
* be converted to PropertyValue).
|
||||
* @brief Logical Op for setting a single property on a single vertex or edge.
|
||||
*
|
||||
* The property value is an expression that must evaluate to some type that can
|
||||
* be stored (a TypedValue that can be converted to PropertyValue).
|
||||
*/
|
||||
class SetProperty : public LogicalOperator {
|
||||
public:
|
||||
@ -461,22 +543,23 @@ class SetProperty : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Logical op for setting the whole properties set
|
||||
* on a vertex or an edge. The value being set is
|
||||
* an expression that must evaluate to a vertex,
|
||||
* edge or map (literal or parameter).
|
||||
* @brief Logical op for setting the whole properties set on a vertex or an
|
||||
* edge.
|
||||
*
|
||||
* Supports setting (replacing the whole properties
|
||||
* set with another) and updating.
|
||||
* The value being set is an expression that must evaluate to a vertex, edge or
|
||||
* map (literal or parameter).
|
||||
*
|
||||
* Supports setting (replacing the whole properties set with another) and
|
||||
* updating.
|
||||
*/
|
||||
class SetProperties : public LogicalOperator {
|
||||
public:
|
||||
/**
|
||||
* Defines how setting the properties works. UPDATE means
|
||||
* that the current property set is augmented with additional
|
||||
* ones (existing props of the same name are replaced), while
|
||||
* REPLACE means that the old props are discarded and replaced
|
||||
* with new ones.
|
||||
* @brief Defines how setting the properties works.
|
||||
*
|
||||
* @c UPDATE means that the current property set is augmented with additional
|
||||
* ones (existing props of the same name are replaced), while @c REPLACE means
|
||||
* that the old props are discarded and replaced with new ones.
|
||||
*/
|
||||
enum class Op { UPDATE, REPLACE };
|
||||
|
||||
@ -512,9 +595,10 @@ class SetProperties : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Logical operator for setting an arbitrary number of
|
||||
* labels on a Vertex. It does NOT remove labels that
|
||||
* are already set on that Vertex.
|
||||
* @brief Logical operator for setting an arbitrary number of labels on a
|
||||
* Vertex.
|
||||
*
|
||||
* It does NOT remove labels that are already set on that Vertex.
|
||||
*/
|
||||
class SetLabels : public LogicalOperator {
|
||||
public:
|
||||
@ -541,7 +625,7 @@ class SetLabels : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Logical op for removing a property from an
|
||||
* @brief Logical op for removing a property from an
|
||||
* edge or a vertex.
|
||||
*/
|
||||
class RemoveProperty : public LogicalOperator {
|
||||
@ -567,9 +651,10 @@ class RemoveProperty : public LogicalOperator {
|
||||
};
|
||||
|
||||
/**
|
||||
* Logical operator for removing an arbitrary number of
|
||||
* labels on a Vertex. If a label does not exist on a Vertex,
|
||||
* nothing happens.
|
||||
* @brief Logical operator for removing an arbitrary number of
|
||||
* labels on a Vertex.
|
||||
*
|
||||
* If a label does not exist on a Vertex, nothing happens.
|
||||
*/
|
||||
class RemoveLabels : public LogicalOperator {
|
||||
public:
|
||||
|
@ -1090,3 +1090,101 @@ TEST(Interpreter, RemoveLabels) {
|
||||
EXPECT_FALSE(vertex.has_label(label2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Interpreter, NodeFilterSet) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
// Create a graph such that (v1 {prop: 42}) is connected to v2 and v3.
|
||||
auto v1 = dba->insert_vertex();
|
||||
auto prop = dba->property("prop");
|
||||
v1.PropsSet(prop, 42);
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto v3 = dba->insert_vertex();
|
||||
auto edge_type = dba->edge_type("Edge");
|
||||
dba->insert_edge(v1, v2, edge_type);
|
||||
dba->insert_edge(v1, v3, edge_type);
|
||||
dba->advance_command();
|
||||
// Create operations which match (v1 {prop: 42}) -- (v) and increment the
|
||||
// v1.prop. The expected result is two incremenentations, since v1 is matched
|
||||
// twice for 2 edges it has.
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
// MATCH (n {prop: 42}) -[r]- (m)
|
||||
auto scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||
scan_all.node_->properties_[prop] = LITERAL(42);
|
||||
auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_,
|
||||
"r", EdgeAtom::Direction::BOTH, false, "m", false);
|
||||
auto node_filter =
|
||||
std::make_shared<NodeFilter>(expand.op_, scan_all.sym_, scan_all.node_);
|
||||
// SET n.prop = n.prop + 1
|
||||
auto set_prop = PROPERTY_LOOKUP("n", prop);
|
||||
auto add = ADD(PROPERTY_LOOKUP("n", prop), LITERAL(1));
|
||||
auto set = std::make_shared<plan::SetProperty>(node_filter, set_prop, add);
|
||||
EXPECT_EQ(2, PullAll(set, *dba, symbol_table));
|
||||
dba->advance_command();
|
||||
v1.Reconstruct();
|
||||
auto prop_eq = v1.PropsAt(prop) == TypedValue(42 + 2);
|
||||
ASSERT_EQ(prop_eq.type(), TypedValue::Type::Bool);
|
||||
EXPECT_TRUE(prop_eq.Value<bool>());
|
||||
}
|
||||
|
||||
TEST(Interpreter, FilterRemove) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
// Create a graph such that (v1 {prop: 42}) is connected to v2 and v3.
|
||||
auto v1 = dba->insert_vertex();
|
||||
auto prop = dba->property("prop");
|
||||
v1.PropsSet(prop, 42);
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto v3 = dba->insert_vertex();
|
||||
auto edge_type = dba->edge_type("Edge");
|
||||
dba->insert_edge(v1, v2, edge_type);
|
||||
dba->insert_edge(v1, v3, edge_type);
|
||||
dba->advance_command();
|
||||
// Create operations which match (v1 {prop: 42}) -- (v) and remove v1.prop.
|
||||
// The expected result is two matches, for each edge of v1.
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
// MATCH (n) -[r]- (m) WHERE n.prop < 43
|
||||
auto scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||
scan_all.node_->properties_[prop] = LITERAL(42);
|
||||
auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_,
|
||||
"r", EdgeAtom::Direction::BOTH, false, "m", false);
|
||||
auto filter_prop = PROPERTY_LOOKUP("n", prop);
|
||||
symbol_table[*filter_prop->expression_] = scan_all.sym_;
|
||||
auto filter =
|
||||
std::make_shared<Filter>(expand.op_, LESS(filter_prop, LITERAL(43)));
|
||||
// REMOVE n.prop
|
||||
auto rem_prop = PROPERTY_LOOKUP("n", prop);
|
||||
symbol_table[*rem_prop->expression_] = scan_all.sym_;
|
||||
auto rem = std::make_shared<plan::RemoveProperty>(filter, rem_prop);
|
||||
EXPECT_EQ(2, PullAll(rem, *dba, symbol_table));
|
||||
dba->advance_command();
|
||||
v1.Reconstruct();
|
||||
EXPECT_EQ(v1.PropsAt(prop).type(), PropertyValue::Type::Null);
|
||||
}
|
||||
|
||||
TEST(Interpreter, SetRemove) {
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v = dba->insert_vertex();
|
||||
auto label1 = dba->label("label1");
|
||||
auto label2 = dba->label("label2");
|
||||
dba->advance_command();
|
||||
// Create operations which match (v) and set and remove v :label.
|
||||
// The expected result is single (v) as it was at the start.
|
||||
AstTreeStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
// MATCH (n) SET n :label1 :label2 REMOVE n :label1 :label2
|
||||
auto scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||
auto set = std::make_shared<plan::SetLabels>(
|
||||
scan_all.op_, scan_all.sym_,
|
||||
std::vector<GraphDbTypes::Label>{label1, label2});
|
||||
auto rem = std::make_shared<plan::RemoveLabels>(
|
||||
set, scan_all.sym_, std::vector<GraphDbTypes::Label>{label1, label2});
|
||||
EXPECT_EQ(1, PullAll(rem, *dba, symbol_table));
|
||||
dba->advance_command();
|
||||
v.Reconstruct();
|
||||
EXPECT_FALSE(v.has_label(label1));
|
||||
EXPECT_FALSE(v.has_label(label2));
|
||||
}
|
||||
|
@ -217,4 +217,6 @@ auto GetRemove(AstTreeStorage &storage, const std::string &name,
|
||||
#define SET(...) query::test_common::GetSet(storage, __VA_ARGS__)
|
||||
#define REMOVE(...) query::test_common::GetRemove(storage, __VA_ARGS__)
|
||||
#define QUERY(...) query::test_common::GetQuery(storage, __VA_ARGS__)
|
||||
// Various operators
|
||||
#define ADD(expr1, expr2) storage.Create<query::AdditionOperator>((expr1), (expr2))
|
||||
#define LESS(expr1, expr2) storage.Create<query::LessOperator>((expr1), (expr2))
|
||||
|
Loading…
Reference in New Issue
Block a user