Don't raise TypedValueException during query execution

Summary:
Handle TypedValueExceptions in query/plan/operator.cpp

Raise QueryRuntimeException during expression evaluation

Reviewers: florijan, mislav.bradac, buda

Reviewed By: mislav.bradac

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D457
This commit is contained in:
Teon Banek 2017-06-12 15:12:31 +02:00
parent c737c73d05
commit 69cfd197d8
4 changed files with 164 additions and 108 deletions

View File

@ -7,6 +7,7 @@
#include "database/graph_db_accessor.hpp"
#include "query/common.hpp"
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/interpret/frame.hpp"
@ -66,36 +67,47 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
return value;
}
#define BINARY_OPERATOR_VISITOR(OP_NODE, CPP_OP) \
TypedValue Visit(OP_NODE &op) override { \
auto val1 = op.expression1_->Accept(*this); \
auto val2 = op.expression2_->Accept(*this); \
return val1 CPP_OP val2; \
#define BINARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
TypedValue Visit(OP_NODE &op) override { \
auto val1 = op.expression1_->Accept(*this); \
auto val2 = op.expression2_->Accept(*this); \
try { \
return val1 CPP_OP val2; \
} catch (const TypedValueException &) { \
throw QueryRuntimeException("Invalid types: {} and {} for '{}'", \
val1.type(), val2.type(), #CYPHER_OP); \
} \
}
#define UNARY_OPERATOR_VISITOR(OP_NODE, CPP_OP) \
TypedValue Visit(OP_NODE &op) override { \
return CPP_OP op.expression_->Accept(*this); \
#define UNARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
TypedValue Visit(OP_NODE &op) override { \
auto val = op.expression_->Accept(*this); \
try { \
return CPP_OP val; \
} catch (const TypedValueException &) { \
throw QueryRuntimeException("Invalid type {} for '{}'", val.type(), \
#CYPHER_OP); \
} \
}
BINARY_OPERATOR_VISITOR(OrOperator, ||);
BINARY_OPERATOR_VISITOR(XorOperator, ^);
BINARY_OPERATOR_VISITOR(AndOperator, &&);
BINARY_OPERATOR_VISITOR(AdditionOperator, +);
BINARY_OPERATOR_VISITOR(SubtractionOperator, -);
BINARY_OPERATOR_VISITOR(MultiplicationOperator, *);
BINARY_OPERATOR_VISITOR(DivisionOperator, /);
BINARY_OPERATOR_VISITOR(ModOperator, %);
BINARY_OPERATOR_VISITOR(NotEqualOperator, !=);
BINARY_OPERATOR_VISITOR(EqualOperator, ==);
BINARY_OPERATOR_VISITOR(LessOperator, <);
BINARY_OPERATOR_VISITOR(GreaterOperator, >);
BINARY_OPERATOR_VISITOR(LessEqualOperator, <=);
BINARY_OPERATOR_VISITOR(GreaterEqualOperator, >=);
BINARY_OPERATOR_VISITOR(OrOperator, ||, OR);
BINARY_OPERATOR_VISITOR(XorOperator, ^, XOR);
BINARY_OPERATOR_VISITOR(AndOperator, &&, AND);
BINARY_OPERATOR_VISITOR(AdditionOperator, +, +);
BINARY_OPERATOR_VISITOR(SubtractionOperator, -, -);
BINARY_OPERATOR_VISITOR(MultiplicationOperator, *, *);
BINARY_OPERATOR_VISITOR(DivisionOperator, /, /);
BINARY_OPERATOR_VISITOR(ModOperator, %, %);
BINARY_OPERATOR_VISITOR(NotEqualOperator, !=, <>);
BINARY_OPERATOR_VISITOR(EqualOperator, ==, =);
BINARY_OPERATOR_VISITOR(LessOperator, <, <);
BINARY_OPERATOR_VISITOR(GreaterOperator, >, >);
BINARY_OPERATOR_VISITOR(LessEqualOperator, <=, <=);
BINARY_OPERATOR_VISITOR(GreaterEqualOperator, >=, >=);
UNARY_OPERATOR_VISITOR(NotOperator, !);
UNARY_OPERATOR_VISITOR(UnaryPlusOperator, +);
UNARY_OPERATOR_VISITOR(UnaryMinusOperator, -);
UNARY_OPERATOR_VISITOR(NotOperator, !, NOT);
UNARY_OPERATOR_VISITOR(UnaryPlusOperator, +, +);
UNARY_OPERATOR_VISITOR(UnaryMinusOperator, -, -);
#undef BINARY_OPERATOR_VISITOR
#undef UNARY_OPERATOR_VISITOR
@ -115,9 +127,12 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
if (_list.IsNull()) {
return TypedValue::Null;
}
// Exceptions have higher priority than returning null.
// We need to convert list to its type before checking if literal is null,
// because conversion will throw exception if list conversion fails.
// Exceptions have higher priority than returning nulls when list expression
// is not null.
if (_list.type() != TypedValue::Type::List) {
throw QueryRuntimeException("'IN' expected a list, but got {}",
_list.type());
}
auto list = _list.Value<std::vector<TypedValue>>();
if (literal.IsNull()) {
return TypedValue::Null;
@ -142,12 +157,14 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
auto _list = list_indexing.expression1_->Accept(*this);
if (_list.type() != TypedValue::Type::List &&
_list.type() != TypedValue::Type::Null) {
throw TypedValueException("Incompatible type in list lookup");
throw QueryRuntimeException(
"Expected a list to index with '[]', but got {}", _list.type());
}
auto _index = list_indexing.expression2_->Accept(*this);
if (_index.type() != TypedValue::Type::Int &&
_index.type() != TypedValue::Type::Null) {
throw TypedValueException("Incompatible type in list lookup");
throw QueryRuntimeException("Expected an int as a list index, but got {}",
_index.type());
}
if (_index.type() == TypedValue::Type::Null ||
_list.type() == TypedValue::Type::Null) {
@ -174,7 +191,9 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
if (bound.type() == TypedValue::Type::Null) {
is_null = true;
} else if (bound.type() != TypedValue::Type::Int) {
throw TypedValueException("Incompatible type in list slicing");
throw QueryRuntimeException(
"Expected an int for a bound in list slicing, but got {}",
bound.type());
}
return bound;
}
@ -188,7 +207,8 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
if (_list.type() == TypedValue::Type::Null) {
is_null = true;
} else if (_list.type() != TypedValue::Type::List) {
throw TypedValueException("Incompatible type in list slicing");
throw QueryRuntimeException("Expected a list to slice, but got {}",
_list.type());
}
if (is_null) {
@ -232,7 +252,7 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
throw utils::NotYetImplemented(
"Not yet implemented property lookup on map");
default:
throw TypedValueException(
throw QueryRuntimeException(
"Expected Node, Edge or Map for property lookup");
}
}
@ -252,7 +272,7 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
return true;
}
default:
throw TypedValueException("Expected Node in labels test");
throw QueryRuntimeException("Expected Node in labels test");
}
}
@ -272,7 +292,7 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
return false;
}
default:
throw TypedValueException("Expected Edge in edge type test");
throw QueryRuntimeException("Expected Edge in edge type test");
}
}

View File

@ -19,6 +19,32 @@
namespace query::plan {
namespace {
// Sets a property on a record accessor from a TypedValue. In cases when the
// TypedValue cannot be converted to PropertyValue,
// QueryRuntimeException is raised.
template <class TRecordAccessor>
void PropsSetChecked(TRecordAccessor &record, GraphDbTypes::Property key,
TypedValue value) {
try {
record.PropsSet(key, value);
} catch (const TypedValueException &) {
throw QueryRuntimeException("'{}' cannot be used as a property value.",
value.type());
}
}
// Checks if the given value of the symbol has the expected type. If not, raises
// QueryRuntimeException.
void ExpectType(Symbol symbol, TypedValue value, TypedValue::Type expected) {
if (value.type() != expected)
throw QueryRuntimeException("Expected a {} for '{}', but got {}.", expected,
symbol.name(), value.type());
}
} // namespace
bool Once::OnceCursor::Pull(Frame &, const SymbolTable &) {
if (!did_pull_) {
did_pull_ = true;
@ -66,9 +92,8 @@ void CreateNode::CreateNodeCursor::Create(Frame &frame,
// Evaluator should use the latest accessors, as modified in this query, when
// setting properties on new nodes.
ExpressionEvaluator evaluator(frame, symbol_table, db_, GraphView::NEW);
for (auto &kv : self_.node_atom_->properties_) {
new_node.PropsSet(kv.first, kv.second->Accept(evaluator));
}
for (auto &kv : self_.node_atom_->properties_)
PropsSetChecked(new_node, kv.first, kv.second->Accept(evaluator));
frame[symbol_table.at(*self_.node_atom_->identifier_)] = new_node;
}
@ -97,6 +122,7 @@ bool CreateExpand::CreateExpandCursor::Pull(Frame &frame,
// get the origin vertex
TypedValue &vertex_value = frame[self_.input_symbol_];
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &v1 = vertex_value.Value<VertexAccessor>();
// Similarly to CreateNode, newly created edges and nodes should use the
@ -134,16 +160,17 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(
Frame &frame, const SymbolTable &symbol_table,
ExpressionEvaluator &evaluator) {
if (self_.existing_node_) {
TypedValue &dest_node_value =
frame[symbol_table.at(*self_.node_atom_->identifier_)];
const auto &dest_node_symbol =
symbol_table.at(*self_.node_atom_->identifier_);
TypedValue &dest_node_value = frame[dest_node_symbol];
ExpectType(dest_node_symbol, dest_node_value, TypedValue::Type::Vertex);
return dest_node_value.Value<VertexAccessor>();
} else {
// the node does not exist, it needs to be created
auto node = db_.insert_vertex();
for (auto label : self_.node_atom_->labels_) node.add_label(label);
for (auto kv : self_.node_atom_->properties_) {
node.PropsSet(kv.first, kv.second->Accept(evaluator));
}
for (auto kv : self_.node_atom_->properties_)
PropsSetChecked(node, kv.first, kv.second->Accept(evaluator));
auto symbol = symbol_table.at(*self_.node_atom_->identifier_);
frame[symbol] = node;
return frame[symbol].Value<VertexAccessor>();
@ -155,9 +182,8 @@ void CreateExpand::CreateExpandCursor::CreateEdge(
const SymbolTable &symbol_table, ExpressionEvaluator &evaluator) {
EdgeAccessor edge =
db_.insert_edge(from, to, self_.edge_atom_->edge_types_[0]);
for (auto kv : self_.edge_atom_->properties_) {
edge.PropsSet(kv.first, kv.second->Accept(evaluator));
}
for (auto kv : self_.edge_atom_->properties_)
PropsSetChecked(edge, kv.first, kv.second->Accept(evaluator));
frame[symbol_table.at(*self_.edge_atom_->identifier_)] = edge;
}
@ -321,7 +347,7 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame,
// Vertex could be null if it is created by a failed optional match, in such a
// case we should stop expanding.
if (vertex_value.IsNull()) return false;
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &vertex = vertex_value.Value<VertexAccessor>();
// Switch the expansion origin vertex to the desired state.
switch (self_.graph_view_) {
@ -362,8 +388,9 @@ bool Expand::ExpandCursor::HandleExistingEdge(const EdgeAccessor &new_edge,
if (self_.existing_edge_) {
TypedValue &old_edge_value = frame[self_.edge_symbol_];
// old_edge_value may be Null when using optional matching
return !old_edge_value.IsNull() &&
old_edge_value.Value<EdgeAccessor>() == new_edge;
if (old_edge_value.IsNull()) return false;
ExpectType(self_.edge_symbol_, old_edge_value, TypedValue::Type::Edge);
return old_edge_value.Value<EdgeAccessor>() == new_edge;
} else {
// not matching existing, so put the new_edge into the frame and return true
frame[self_.edge_symbol_] = new_edge;
@ -390,8 +417,9 @@ bool Expand::ExpandCursor::HandleExistingNode(const VertexAccessor new_node,
if (self_.existing_node_) {
TypedValue &old_node_value = frame[self_.node_symbol_];
// old_node_value may be Null when using optional matching
return !old_node_value.IsNull() &&
old_node_value.Value<VertexAccessor>() == new_node;
if (old_node_value.IsNull()) return false;
ExpectType(self_.node_symbol_, old_node_value, TypedValue::Type::Vertex);
return old_node_value.Value<VertexAccessor>() == new_node;
} else {
// not matching existing, so put the new_node into the frame and return true
frame[self_.node_symbol_] = new_node;
@ -419,7 +447,14 @@ bool Filter::FilterCursor::Pull(Frame &frame, const SymbolTable &symbol_table) {
ExpressionEvaluator evaluator(frame, symbol_table, db_, GraphView::OLD);
while (input_cursor_->Pull(frame, symbol_table)) {
TypedValue result = self_.expression_->Accept(evaluator);
if (result.IsNull() || !result.Value<bool>()) continue;
// Null is treated like false.
if (result.IsNull()) continue;
if (result.type() != TypedValue::Type::Bool)
throw QueryRuntimeException(
"Filter expression must be a bool or null, but got {}.",
result.type());
if (!result.Value<bool>()) continue;
return true;
}
return false;
@ -522,7 +557,7 @@ bool Delete::DeleteCursor::Pull(Frame &frame, const SymbolTable &symbol_table) {
break;
// check we're not trying to delete anything except vertices and edges
default:
throw TypedValueException("Can only delete edges and vertices");
throw QueryRuntimeException("Can only delete edges and vertices");
}
return true;
@ -553,17 +588,12 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame,
TypedValue lhs = self_.lhs_->expression_->Accept(evaluator);
TypedValue rhs = self_.rhs_->Accept(evaluator);
// TODO the following code uses implicit TypedValue to PropertyValue
// conversion which throws a TypedValueException if impossible
// this is correct, but the end user will get a not-very-informative
// error message, so address this when improving error feedback
switch (lhs.type()) {
case TypedValue::Type::Vertex:
lhs.Value<VertexAccessor>().PropsSet(self_.lhs_->property_, rhs);
PropsSetChecked(lhs.Value<VertexAccessor>(), self_.lhs_->property_, rhs);
break;
case TypedValue::Type::Edge:
lhs.Value<EdgeAccessor>().PropsSet(self_.lhs_->property_, rhs);
PropsSetChecked(lhs.Value<EdgeAccessor>(), self_.lhs_->property_, rhs);
break;
case TypedValue::Type::Null:
// Skip setting properties on Null (can occur in optional match).
@ -638,12 +668,8 @@ void SetProperties::SetPropertiesCursor::Set(TRecordAccessor &record,
set_props(rhs.Value<VertexAccessor>().Properties());
break;
case TypedValue::Type::Map: {
// TODO the following code uses implicit TypedValue to PropertyValue
// conversion which throws a TypedValueException if impossible
// this is correct, but the end user will get a not-very-informative
// error message, so address this when improving error feedback
for (const auto &kv : rhs.Value<std::map<std::string, TypedValue>>())
record.PropsSet(db_.property(kv.first), kv.second);
PropsSetChecked(record, db_.property(kv.first), kv.second);
break;
}
default:
@ -680,7 +706,7 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame,
TypedValue &vertex_value = frame[self_.input_symbol_];
// Skip setting labels on Null (can occur in optional match).
if (vertex_value.IsNull()) return true;
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &vertex = vertex_value.Value<VertexAccessor>();
vertex.SwitchNew();
for (auto label : self_.labels_) vertex.add_label(label);
@ -723,8 +749,6 @@ bool RemoveProperty::RemovePropertyCursor::Pull(
// Skip removing properties on Null (can occur in optional match).
break;
default:
// TODO consider throwing a TypedValueException here
// deal with this when we'll be overhauling error-feedback
throw QueryRuntimeException(
"Properties can only be removed on Vertices and Edges");
}
@ -755,7 +779,7 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame,
TypedValue &vertex_value = frame[self_.input_symbol_];
// Skip removing labels on Null (can occur in optional match).
if (vertex_value.IsNull()) return true;
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &vertex = vertex_value.Value<VertexAccessor>();
vertex.SwitchNew();
for (auto label : self_.labels_) vertex.remove_label(label);
@ -796,6 +820,9 @@ bool ExpandUniquenessFilter<TAccessor>::ExpandUniquenessFilterCursor::Pull(
TAccessor &expand_accessor = expand_value.Value<TAccessor>();
for (const auto &previous_symbol : self_.previous_symbols_) {
TypedValue &previous_value = frame[previous_symbol];
// This shouldn't raise a TypedValueException, because the planner makes
// sure these are all of the expected type. In case they are not, an error
// should be raised long before this code is executed.
TAccessor &previous_accessor = previous_value.Value<TAccessor>();
if (expand_accessor == previous_accessor) return false;
}
@ -1026,10 +1053,12 @@ void Aggregate::AggregateCursor::Update(
Frame &frame, const SymbolTable &symbol_table,
ExpressionEvaluator &evaluator,
Aggregate::AggregateCursor::AggregationValue &agg_value) {
debug_assert(self_.aggregations_.size() == agg_value.values_.size(),
"Inappropriate AggregationValue.values_ size");
debug_assert(self_.aggregations_.size() == agg_value.counts_.size(),
"Inappropriate AggregationValue.counts_ size");
debug_assert(
self_.aggregations_.size() == agg_value.values_.size(),
"Expected as much AggregationValue.values_ as there are aggregations.");
debug_assert(
self_.aggregations_.size() == agg_value.counts_.size(),
"Expected as much AggregationValue.counts_ as there are aggregations.");
// we iterate over counts, values and aggregation info at the same time
auto count_it = agg_value.counts_.begin();
@ -1083,20 +1112,28 @@ void Aggregate::AggregateCursor::Update(
break;
case Aggregation::Op::MIN: {
EnsureOkForMinMax(input_value);
// TODO an illegal comparison here will throw a TypedValueException
// consider catching and throwing something else
TypedValue comparison_result = input_value < *value_it;
// since we skip nulls we either have a valid comparison, or
// an exception was just thrown above
// safe to assume a bool TypedValue
if (comparison_result.Value<bool>()) *value_it = input_value;
try {
TypedValue comparison_result = input_value < *value_it;
// since we skip nulls we either have a valid comparison, or
// an exception was just thrown above
// safe to assume a bool TypedValue
if (comparison_result.Value<bool>()) *value_it = input_value;
} catch (const TypedValueException &) {
throw QueryRuntimeException("Unable to get MIN of '{}' and '{}'",
input_value.type(), value_it->type());
}
break;
}
case Aggregation::Op::MAX: {
// all comments as for Op::Min
EnsureOkForMinMax(input_value);
TypedValue comparison_result = input_value > *value_it;
if (comparison_result.Value<bool>()) *value_it = input_value;
try {
TypedValue comparison_result = input_value > *value_it;
if (comparison_result.Value<bool>()) *value_it = input_value;
} catch (const TypedValueException &) {
throw QueryRuntimeException("Unable to get MAX of '{}' and '{}'",
input_value.type(), value_it->type());
}
break;
}
case Aggregation::Op::AVG:
@ -1129,9 +1166,8 @@ void Aggregate::AggregateCursor::EnsureOkForMinMax(
case TypedValue::Type::String:
return;
default:
// TODO consider better error feedback
throw TypedValueException(
"Only Bool, Int, Double and String properties are allowed in "
throw QueryRuntimeException(
"Only Bool, Int, Double and String values are allowed in "
"MIN and MAX aggregations");
}
}
@ -1142,9 +1178,8 @@ void Aggregate::AggregateCursor::EnsureOkForAvgSum(
case TypedValue::Type::Double:
return;
default:
// TODO consider better error feedback
throw TypedValueException(
"Only numeric properties allowed in SUM and AVG aggregations");
throw QueryRuntimeException(
"Only numeric values allowed in SUM and AVG aggregations");
}
}
@ -1547,7 +1582,8 @@ bool Unwind::UnwindCursor::Pull(Frame &frame, const SymbolTable &symbol_table) {
ExpressionEvaluator evaluator(frame, symbol_table, db_);
TypedValue input_value = self_.input_expression_->Accept(evaluator);
if (input_value.type() != TypedValue::Type::List)
throw QueryRuntimeException("UNWIND only accepts list values");
throw QueryRuntimeException("UNWIND only accepts list values, got '{}'",
input_value.type());
input_value_ = input_value.Value<std::vector<TypedValue>>();
input_value_it_ = input_value_.begin();
}

View File

@ -361,7 +361,7 @@ TEST(ExpressionEvaluator, ListIndexingOperator) {
auto *op = storage.Create<ListIndexingOperator>(
storage.Create<PrimitiveLiteral>(2),
storage.Create<PrimitiveLiteral>(TypedValue::Null));
EXPECT_THROW(op->Accept(eval.eval), TypedValueException);
EXPECT_THROW(op->Accept(eval.eval), QueryRuntimeException);
}
}
@ -431,14 +431,14 @@ TEST(ExpressionEvaluator, ListSlicingOperator) {
auto *op = storage.Create<ListSlicingOperator>(
list_literal, storage.Create<PrimitiveLiteral>(TypedValue::Null),
storage.Create<PrimitiveLiteral>("mirko"));
EXPECT_THROW(op->Accept(eval.eval), TypedValueException);
EXPECT_THROW(op->Accept(eval.eval), QueryRuntimeException);
}
{
// List of illegal type.
auto *op = storage.Create<ListSlicingOperator>(
storage.Create<PrimitiveLiteral>("a"),
storage.Create<PrimitiveLiteral>(-2), nullptr);
EXPECT_THROW(op->Accept(eval.eval), TypedValueException);
EXPECT_THROW(op->Accept(eval.eval), QueryRuntimeException);
}
{
// Null value list with undefined upper bound.

View File

@ -478,19 +478,19 @@ TEST(QueryPlan, AggregateFirstValueTypes) {
// everything except for COUNT fails on a Vertex
aggregate(n_id, Aggregation::Op::COUNT);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), QueryRuntimeException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), QueryRuntimeException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), QueryRuntimeException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), QueryRuntimeException);
// on strings AVG and SUM fail
aggregate(n_prop_string, Aggregation::Op::COUNT);
aggregate(n_prop_string, Aggregation::Op::MIN);
aggregate(n_prop_string, Aggregation::Op::MAX);
EXPECT_THROW(aggregate(n_prop_string, Aggregation::Op::AVG),
TypedValueException);
QueryRuntimeException);
EXPECT_THROW(aggregate(n_prop_string, Aggregation::Op::SUM),
TypedValueException);
QueryRuntimeException);
// on ints nothing fails
aggregate(n_prop_int, Aggregation::Op::COUNT);
@ -534,24 +534,24 @@ TEST(QueryPlan, AggregateTypes) {
// everything except for COUNT fails on a Vertex
auto n_id = n_p1->expression_;
aggregate(n_id, Aggregation::Op::COUNT);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), TypedValueException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), QueryRuntimeException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), QueryRuntimeException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), QueryRuntimeException);
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), QueryRuntimeException);
// on strings AVG and SUM fail
aggregate(n_p1, Aggregation::Op::COUNT);
aggregate(n_p1, Aggregation::Op::MIN);
aggregate(n_p1, Aggregation::Op::MAX);
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::AVG), TypedValueException);
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::SUM), TypedValueException);
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::AVG), QueryRuntimeException);
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::SUM), QueryRuntimeException);
// combination of int and bool, everything except count fails
aggregate(n_p2, Aggregation::Op::COUNT);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MIN), TypedValueException);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MAX), TypedValueException);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::AVG), TypedValueException);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::SUM), TypedValueException);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MIN), QueryRuntimeException);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MAX), QueryRuntimeException);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::AVG), QueryRuntimeException);
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::SUM), QueryRuntimeException);
}
TEST(QueryPlan, Unwind) {