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:
parent
c737c73d05
commit
69cfd197d8
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user