Implement opencypher functions
Summary: Implement opencypher functions Reviewers: florijan, buda, teon.banek Reviewed By: buda, teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D299
This commit is contained in:
parent
ecca97ef72
commit
0fa4555cad
@ -14,6 +14,7 @@
|
||||
#include "query/interpret/awesome_memgraph_functions.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace query::frontend {
|
||||
|
||||
@ -706,35 +707,26 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitFunctionName(
|
||||
CypherParser::FunctionNameContext *ctx) {
|
||||
std::string function_name = ctx->getText();
|
||||
std::transform(function_name.begin(), function_name.end(),
|
||||
function_name.begin(), toupper);
|
||||
return function_name;
|
||||
return utils::ToUpperCase(ctx->getText());
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitDoubleLiteral(
|
||||
CypherParser::DoubleLiteralContext *ctx) {
|
||||
// stod would be nicer but it uses current locale so we shouldn't use it.
|
||||
double t = 0LL;
|
||||
std::istringstream iss(ctx->getText());
|
||||
iss.imbue(std::locale::classic());
|
||||
iss >> t;
|
||||
if (!iss.eof()) {
|
||||
throw SemanticException();
|
||||
try {
|
||||
return utils::ParseDouble(ctx->getText());
|
||||
} catch (const utils::BasicException &) {
|
||||
throw SemanticException("Couldn't parse string to double");
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitIntegerLiteral(
|
||||
CypherParser::IntegerLiteralContext *ctx) {
|
||||
int64_t t = 0LL;
|
||||
try {
|
||||
// Not really correct since long long can have a bigger range than int64_t.
|
||||
t = std::stoll(ctx->getText(), 0, 0);
|
||||
} catch (std::out_of_range) {
|
||||
return static_cast<int64_t>(std::stoll(ctx->getText(), 0, 0));
|
||||
} catch (const std::out_of_range &) {
|
||||
throw SemanticException();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitStringLiteral(
|
||||
|
@ -1,16 +1,307 @@
|
||||
#include "query/interpret/awesome_memgraph_functions.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
|
||||
#include "query/exceptions.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace query {
|
||||
namespace {
|
||||
|
||||
// Predicate functions.
|
||||
// Neo4j has all, any, exists, none, single
|
||||
// Those functions are a little bit different since they take a filterExpression
|
||||
// as an argument.
|
||||
// There is all, any, none and single productions in opencypher grammar, but it
|
||||
// will be trivial to also add exists.
|
||||
// TODO: Implement this.
|
||||
|
||||
// Scalar functions.
|
||||
// We don't have a way to implement id function since we don't store any. If it
|
||||
// is really neccessary we could probably map vlist* to id.
|
||||
// TODO: Implement length (it works on a path, but we didn't define path
|
||||
// structure yet).
|
||||
// TODO: Implement size(pattern), for example size((a)-[:X]-()) should return
|
||||
// number of results of this pattern. I don't think we will ever do this.
|
||||
// TODO: Implement timestamp, every time it is called in a query it needs to
|
||||
// return same time. We need to store query start time somwhere.
|
||||
// TODO: Implement rest of the list functions.
|
||||
// TODO: Implement rand
|
||||
// TODO: Implement logarithmic, trigonometric, string and spatial functions
|
||||
|
||||
TypedValue Coalesce(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() == 0U) {
|
||||
throw QueryRuntimeException("coalesce requires at least one argument");
|
||||
}
|
||||
for (auto &arg : args) {
|
||||
if (arg.type() != TypedValue::Type::Null) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
return TypedValue::Null;
|
||||
}
|
||||
|
||||
TypedValue EndNode(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("endNode requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Edge:
|
||||
return args[0].Value<EdgeAccessor>().to();
|
||||
default:
|
||||
throw QueryRuntimeException("endNode called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Head(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("head requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::List: {
|
||||
const auto &list = args[0].Value<std::vector<TypedValue>>();
|
||||
if (list.empty()) return TypedValue::Null;
|
||||
return list[0];
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("head called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Last(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("last requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::List: {
|
||||
const auto &list = args[0].Value<std::vector<TypedValue>>();
|
||||
if (list.empty()) return TypedValue::Null;
|
||||
return list.back();
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("last called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Properties(const std::vector<TypedValue> &args,
|
||||
GraphDbAccessor &db_accessor) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("properties requires one argument");
|
||||
}
|
||||
auto get_properties = [&](const auto &record_accessor) {
|
||||
std::map<std::string, TypedValue> properties;
|
||||
for (const auto &property : record_accessor.Properties()) {
|
||||
properties[db_accessor.property_name(property.first)] = property.second;
|
||||
}
|
||||
return properties;
|
||||
};
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Vertex:
|
||||
return get_properties(args[0].Value<VertexAccessor>());
|
||||
case TypedValue::Type::Edge:
|
||||
return get_properties(args[0].Value<EdgeAccessor>());
|
||||
default:
|
||||
throw QueryRuntimeException("properties called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Size(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("size requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::List:
|
||||
return static_cast<int64_t>(
|
||||
args[0].Value<std::vector<TypedValue>>().size());
|
||||
case TypedValue::Type::String:
|
||||
return static_cast<int64_t>(args[0].Value<std::string>().size());
|
||||
case TypedValue::Type::Map:
|
||||
// neo4j doesn't implement size for map, but I don't see a good reason not
|
||||
// to do it.
|
||||
return static_cast<int64_t>(
|
||||
args[0].Value<std::map<std::string, TypedValue>>().size());
|
||||
default:
|
||||
throw QueryRuntimeException("size called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue StartNode(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("startNode requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Edge:
|
||||
return args[0].Value<EdgeAccessor>().from();
|
||||
default:
|
||||
throw QueryRuntimeException("startNode called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue ToBoolean(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("toBoolean requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Bool:
|
||||
return args[0].Value<bool>();
|
||||
case TypedValue::Type::String: {
|
||||
auto s = utils::ToUpperCase(utils::Trim(args[0].Value<std::string>()));
|
||||
if (s == "TRUE") return true;
|
||||
if (s == "FALSE") return false;
|
||||
// I think this is just stupid and that exception should be thrown, but
|
||||
// neo4j does it this way...
|
||||
return TypedValue::Null;
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("toBoolean called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue ToFloat(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("toFloat requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Int:
|
||||
return static_cast<double>(args[0].Value<int64_t>());
|
||||
case TypedValue::Type::Double:
|
||||
return args[0];
|
||||
case TypedValue::Type::String:
|
||||
try {
|
||||
return utils::ParseDouble(utils::Trim(args[0].Value<std::string>()));
|
||||
} catch (const utils::BasicException &) {
|
||||
return TypedValue::Null;
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("toFloat called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue ToInteger(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("toInteger requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Int:
|
||||
return args[0];
|
||||
case TypedValue::Type::Double:
|
||||
return static_cast<int64_t>(args[0].Value<double>());
|
||||
case TypedValue::Type::String:
|
||||
try {
|
||||
// Yup, this is correct. String is valid if it has floating point
|
||||
// number, then it is parsed and converted to int.
|
||||
return static_cast<int64_t>(
|
||||
utils::ParseDouble(utils::Trim(args[0].Value<std::string>())));
|
||||
} catch (const utils::BasicException &) {
|
||||
return TypedValue::Null;
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("toInteger called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Type(const std::vector<TypedValue> &args,
|
||||
GraphDbAccessor &db_accessor) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("type requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Edge:
|
||||
return db_accessor.edge_type_name(
|
||||
args[0].Value<EdgeAccessor>().edge_type());
|
||||
default:
|
||||
throw QueryRuntimeException("type called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Keys(const std::vector<TypedValue> &args,
|
||||
GraphDbAccessor &db_accessor) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("keys requires one argument");
|
||||
}
|
||||
auto get_keys = [&](const auto &record_accessor) {
|
||||
std::vector<TypedValue> keys;
|
||||
for (const auto &property : record_accessor.Properties()) {
|
||||
keys.push_back(db_accessor.property_name(property.first));
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Vertex:
|
||||
return get_keys(args[0].Value<VertexAccessor>());
|
||||
case TypedValue::Type::Edge:
|
||||
return get_keys(args[0].Value<EdgeAccessor>());
|
||||
default:
|
||||
throw QueryRuntimeException("keys called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Labels(const std::vector<TypedValue> &args,
|
||||
GraphDbAccessor &db_accessor) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("labels requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Vertex: {
|
||||
std::vector<TypedValue> labels;
|
||||
for (const auto &label : args[0].Value<VertexAccessor>().labels()) {
|
||||
labels.push_back(db_accessor.label_name(label));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("labels called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Tail(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("tail requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::List: {
|
||||
auto list = args[0].Value<std::vector<TypedValue>>();
|
||||
if (list.empty()) return list;
|
||||
list.erase(list.begin());
|
||||
return list;
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("tail called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Abs(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("ABS requires one argument");
|
||||
throw QueryRuntimeException("abs requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
@ -21,16 +312,99 @@ TypedValue Abs(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
case TypedValue::Type::Double:
|
||||
return std::abs(args[0].Value<double>());
|
||||
default:
|
||||
throw QueryRuntimeException("ABS called with incompatible type");
|
||||
throw QueryRuntimeException("abs called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Ceil(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("ceil requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Int:
|
||||
return ceil(args[0].Value<int64_t>());
|
||||
case TypedValue::Type::Double:
|
||||
return ceil(args[0].Value<double>());
|
||||
default:
|
||||
throw QueryRuntimeException("ceil called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Floor(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("floor requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Int:
|
||||
return floor(args[0].Value<int64_t>());
|
||||
case TypedValue::Type::Double:
|
||||
return floor(args[0].Value<double>());
|
||||
default:
|
||||
throw QueryRuntimeException("floor called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
// We are not completely compatible with neoj4 in this function because,
|
||||
// neo4j rounds -0.5, -1.5, -2.5... to 0, -1, -2...
|
||||
TypedValue Round(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("round requires one argument");
|
||||
}
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Int:
|
||||
return round(args[0].Value<int64_t>());
|
||||
case TypedValue::Type::Double:
|
||||
return round(args[0].Value<double>());
|
||||
default:
|
||||
throw QueryRuntimeException("round called with incompatible type");
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Sign(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
||||
if (args.size() != 1U) {
|
||||
throw QueryRuntimeException("sign requires one argument");
|
||||
}
|
||||
auto sign = [](auto x) { return (0 < x) - (x < 0); };
|
||||
switch (args[0].type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Int:
|
||||
return sign(args[0].Value<int64_t>());
|
||||
case TypedValue::Type::Double:
|
||||
return sign(args[0].Value<double>());
|
||||
default:
|
||||
throw QueryRuntimeException("sign called with incompatible type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::function<TypedValue(const std::vector<TypedValue> &, GraphDbAccessor &)>
|
||||
NameToFunction(const std::string &function_name) {
|
||||
if (function_name == "ABS") {
|
||||
return Abs;
|
||||
}
|
||||
if (function_name == "COALESCE") return Coalesce;
|
||||
if (function_name == "ENDNODE") return EndNode;
|
||||
if (function_name == "HEAD") return Head;
|
||||
if (function_name == "LAST") return Last;
|
||||
if (function_name == "PROPERTIES") return Properties;
|
||||
if (function_name == "SIZE") return Size;
|
||||
if (function_name == "STARTNODE") return StartNode;
|
||||
if (function_name == "TOBOOLEAN") return ToBoolean;
|
||||
if (function_name == "TOFLOAT") return ToFloat;
|
||||
if (function_name == "TOINTEGER") return ToInteger;
|
||||
if (function_name == "TYPE") return Type;
|
||||
if (function_name == "KEYS") return Keys;
|
||||
if (function_name == "LABELS") return Labels;
|
||||
if (function_name == "TAIL") return Tail;
|
||||
if (function_name == "ABS") return Abs;
|
||||
if (function_name == "CEIL") return Ceil;
|
||||
if (function_name == "FLOOR") return Floor;
|
||||
if (function_name == "ROUND") return Round;
|
||||
if (function_name == "SIGN") return Sign;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
@ -18,7 +20,7 @@ namespace utils {
|
||||
*
|
||||
* @return trimmed string
|
||||
*/
|
||||
std::string Trim(const std::string& s) {
|
||||
inline std::string Trim(const std::string& s) {
|
||||
auto begin = s.begin();
|
||||
auto end = s.end();
|
||||
if (begin == end) {
|
||||
@ -37,7 +39,7 @@ std::string Trim(const std::string& s) {
|
||||
/**
|
||||
* Return string with all lowercased characters (locale independent).
|
||||
*/
|
||||
std::string ToLowerCase(std::string s) {
|
||||
inline std::string ToLowerCase(std::string s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(),
|
||||
[](char c) { return tolower(c); });
|
||||
return s;
|
||||
@ -46,7 +48,7 @@ std::string ToLowerCase(std::string s) {
|
||||
/**
|
||||
* Return string with all uppercased characters (locale independent).
|
||||
*/
|
||||
std::string ToUpperCase(std::string s) {
|
||||
inline std::string ToUpperCase(std::string s) {
|
||||
std::string s2(s.size(), ' ');
|
||||
std::transform(s.begin(), s.end(), s.begin(),
|
||||
[](char c) { return toupper(c); });
|
||||
@ -56,8 +58,8 @@ std::string ToUpperCase(std::string s) {
|
||||
/**
|
||||
* Join strings in vector separated by a given separator.
|
||||
*/
|
||||
std::string Join(const std::vector<std::string>& strings,
|
||||
const char* separator) {
|
||||
inline std::string Join(const std::vector<std::string>& strings,
|
||||
const char* separator) {
|
||||
std::ostringstream oss;
|
||||
std::copy(strings.begin(), strings.end(),
|
||||
std::ostream_iterator<std::string>(oss, separator));
|
||||
@ -68,8 +70,8 @@ std::string Join(const std::vector<std::string>& strings,
|
||||
* Replaces all occurences of <match> in <src> with <replacement>.
|
||||
*/
|
||||
// TODO: This could be implemented much more efficient.
|
||||
std::string Replace(std::string src, const std::string& match,
|
||||
const std::string& replacement) {
|
||||
inline std::string Replace(std::string src, const std::string& match,
|
||||
const std::string& replacement) {
|
||||
for (size_t pos = src.find(match); pos != std::string::npos;
|
||||
pos = src.find(match, pos + replacement.size())) {
|
||||
src.erase(pos, match.length()).insert(pos, replacement);
|
||||
@ -80,8 +82,8 @@ std::string Replace(std::string src, const std::string& match,
|
||||
/**
|
||||
* Split string by delimeter and return vector of results.
|
||||
*/
|
||||
std::vector<std::string> Split(const std::string& src,
|
||||
const std::string& delimiter) {
|
||||
inline std::vector<std::string> Split(const std::string& src,
|
||||
const std::string& delimiter) {
|
||||
size_t index = 0;
|
||||
std::vector<std::string> res;
|
||||
size_t n = src.find(delimiter, index);
|
||||
@ -92,4 +94,20 @@ std::vector<std::string> Split(const std::string& src,
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse double using classic locale, throws BasicException if it wasn't able to
|
||||
* parse whole string.
|
||||
*/
|
||||
inline double ParseDouble(const std::string& s) {
|
||||
// stod would be nicer but it uses current locale so we shouldn't use it.
|
||||
double t = 0LL;
|
||||
std::istringstream iss(s);
|
||||
iss.imbue(std::locale::classic());
|
||||
iss >> t;
|
||||
if (!iss.eof()) {
|
||||
throw BasicException("Couldn't parse string");
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
@ -918,7 +918,7 @@ TEST(CypherMainVisitorTest, WithWhere) {
|
||||
|
||||
TEST(CypherMainVisitorTest, ClausesOrdering) {
|
||||
// Obviously some of the ridiculous combinations don't fail here, but they
|
||||
// will fail in semantic analysis or they make perfect sense AS a part of
|
||||
// will fail in semantic analysis or they make perfect sense as a part of
|
||||
// bigger query.
|
||||
AstGenerator("RETURN 1");
|
||||
ASSERT_THROW(AstGenerator("RETURN 1 RETURN 1"), SemanticException);
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "database/graph_db_datatypes.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
|
@ -1,8 +1,3 @@
|
||||
//
|
||||
// Copyright 2017 Memgraph
|
||||
// Created by Mislav Bradac on 27.03.17.
|
||||
//
|
||||
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -18,6 +13,10 @@
|
||||
#include "query/interpret/eval.hpp"
|
||||
|
||||
using namespace query;
|
||||
using testing::Pair;
|
||||
using testing::UnorderedElementsAre;
|
||||
|
||||
namespace {
|
||||
|
||||
struct NoContextExpressionEvaluator {
|
||||
NoContextExpressionEvaluator() {}
|
||||
@ -28,6 +27,23 @@ struct NoContextExpressionEvaluator {
|
||||
ExpressionEvaluator eval{frame, symbol_table, *dba};
|
||||
};
|
||||
|
||||
TypedValue EvaluateFunction(const std::string &function_name,
|
||||
const std::vector<TypedValue> &args) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
|
||||
std::vector<Expression *> expressions;
|
||||
for (const auto &arg : args) {
|
||||
expressions.push_back(storage.Create<Literal>(arg));
|
||||
}
|
||||
auto *op =
|
||||
storage.Create<Function>(NameToFunction(function_name), expressions);
|
||||
op->Accept(eval.eval);
|
||||
return eval.eval.PopBack();
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, OrOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
@ -250,41 +266,6 @@ TEST(ExpressionEvaluator, IsNullOperator) {
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, AbsFunction) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
{
|
||||
std::vector<Expression *> arguments = {
|
||||
storage.Create<Literal>(TypedValue::Null)};
|
||||
auto *op = storage.Create<Function>(NameToFunction("ABS"), arguments);
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
std::vector<Expression *> arguments = {storage.Create<Literal>(-2)};
|
||||
auto *op = storage.Create<Function>(NameToFunction("ABS"), arguments);
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 2);
|
||||
}
|
||||
{
|
||||
std::vector<Expression *> arguments = {storage.Create<Literal>(-2.5)};
|
||||
auto *op = storage.Create<Function>(NameToFunction("ABS"), arguments);
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<double>(), 2.5);
|
||||
}
|
||||
{
|
||||
std::vector<Expression *> arguments = {storage.Create<Literal>(true)};
|
||||
auto *op = storage.Create<Function>(NameToFunction("ABS"), arguments);
|
||||
ASSERT_THROW(op->Accept(eval.eval), QueryRuntimeException);
|
||||
}
|
||||
{
|
||||
std::vector<Expression *> arguments = {
|
||||
storage.Create<Literal>(std::vector<TypedValue>(5))};
|
||||
auto *op = storage.Create<Function>(NameToFunction("ABS"), arguments);
|
||||
ASSERT_THROW(op->Accept(eval.eval), QueryRuntimeException);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, Aggregation) {
|
||||
AstTreeStorage storage;
|
||||
auto aggr = storage.Create<Aggregation>(storage.Create<Literal>(42),
|
||||
@ -300,3 +281,290 @@ TEST(ExpressionEvaluator, Aggregation) {
|
||||
aggr->Accept(eval);
|
||||
EXPECT_EQ(eval.PopBack().Value<int64_t>(), 1);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionCoalesce) {
|
||||
ASSERT_THROW(EvaluateFunction("COALESCE", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(
|
||||
EvaluateFunction("COALESCE", {TypedValue::Null, TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(
|
||||
EvaluateFunction("COALESCE", {TypedValue::Null, 2, 3}).Value<int64_t>(),
|
||||
2);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionEndNode) {
|
||||
ASSERT_THROW(EvaluateFunction("ENDNODE", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("ENDNODE", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v1 = dba->insert_vertex();
|
||||
v1.add_label(dba->label("label1"));
|
||||
auto v2 = dba->insert_vertex();
|
||||
v2.add_label(dba->label("label2"));
|
||||
auto e = dba->insert_edge(v1, v2, dba->edge_type("t"));
|
||||
ASSERT_TRUE(EvaluateFunction("ENDNODE", {e})
|
||||
.Value<VertexAccessor>()
|
||||
.has_label(dba->label("label2")));
|
||||
ASSERT_THROW(EvaluateFunction("ENDNODE", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionHead) {
|
||||
ASSERT_THROW(EvaluateFunction("HEAD", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("HEAD", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
std::vector<TypedValue> arguments;
|
||||
arguments.push_back(std::vector<TypedValue>{3, 4, 5});
|
||||
ASSERT_EQ(EvaluateFunction("HEAD", arguments).Value<int64_t>(), 3);
|
||||
arguments[0].Value<std::vector<TypedValue>>().clear();
|
||||
ASSERT_EQ(EvaluateFunction("HEAD", arguments).type(), TypedValue::Type::Null);
|
||||
ASSERT_THROW(EvaluateFunction("HEAD", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionProperties) {
|
||||
ASSERT_THROW(EvaluateFunction("PROPERTIES", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("PROPERTIES", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v1 = dba->insert_vertex();
|
||||
v1.PropsSet(dba->property("height"), 5);
|
||||
v1.PropsSet(dba->property("age"), 10);
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto e = dba->insert_edge(v1, v2, dba->edge_type("type1"));
|
||||
e.PropsSet(dba->property("height"), 3);
|
||||
e.PropsSet(dba->property("age"), 15);
|
||||
|
||||
auto prop_values_to_int = [](TypedValue t) {
|
||||
std::unordered_map<std::string, int> properties;
|
||||
for (auto property : t.Value<std::map<std::string, TypedValue>>()) {
|
||||
properties[property.first] = property.second.Value<int64_t>();
|
||||
}
|
||||
return properties;
|
||||
};
|
||||
ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", {v1})),
|
||||
UnorderedElementsAre(Pair("height", 5), Pair("age", 10)));
|
||||
ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", {e})),
|
||||
UnorderedElementsAre(Pair("height", 3), Pair("age", 15)));
|
||||
ASSERT_THROW(EvaluateFunction("PROPERTIES", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionLast) {
|
||||
ASSERT_THROW(EvaluateFunction("LAST", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("LAST", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
std::vector<TypedValue> arguments;
|
||||
arguments.push_back(std::vector<TypedValue>{3, 4, 5});
|
||||
ASSERT_EQ(EvaluateFunction("LAST", arguments).Value<int64_t>(), 5);
|
||||
arguments[0].Value<std::vector<TypedValue>>().clear();
|
||||
ASSERT_EQ(EvaluateFunction("LAST", arguments).type(), TypedValue::Type::Null);
|
||||
ASSERT_THROW(EvaluateFunction("LAST", {5}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionSize) {
|
||||
ASSERT_THROW(EvaluateFunction("SIZE", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("SIZE", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
std::vector<TypedValue> arguments;
|
||||
arguments.push_back(std::vector<TypedValue>{3, 4, 5});
|
||||
ASSERT_EQ(EvaluateFunction("SIZE", arguments).Value<int64_t>(), 3);
|
||||
ASSERT_EQ(EvaluateFunction("SIZE", {"john"}).Value<int64_t>(), 4);
|
||||
ASSERT_EQ(EvaluateFunction("SIZE", {std::map<std::string, TypedValue>{
|
||||
{"a", 5}, {"b", true}, {"c", "123"}}})
|
||||
.Value<int64_t>(),
|
||||
3);
|
||||
ASSERT_THROW(EvaluateFunction("SIZE", {5}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionStartNode) {
|
||||
ASSERT_THROW(EvaluateFunction("STARTNODE", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("STARTNODE", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v1 = dba->insert_vertex();
|
||||
v1.add_label(dba->label("label1"));
|
||||
auto v2 = dba->insert_vertex();
|
||||
v2.add_label(dba->label("label2"));
|
||||
auto e = dba->insert_edge(v1, v2, dba->edge_type("t"));
|
||||
ASSERT_TRUE(EvaluateFunction("STARTNODE", {e})
|
||||
.Value<VertexAccessor>()
|
||||
.has_label(dba->label("label1")));
|
||||
ASSERT_THROW(EvaluateFunction("STARTNODE", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionToBoolean) {
|
||||
ASSERT_THROW(EvaluateFunction("TOBOOLEAN", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {" trUE \n\t"}).Value<bool>(), true);
|
||||
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {"\n\tFalsE "}).Value<bool>(), false);
|
||||
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {"\n\tFALSEA "}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {true}).Value<bool>(), true);
|
||||
ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {false}).Value<bool>(), false);
|
||||
ASSERT_THROW(EvaluateFunction("TOBOOLEAN", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionToFloat) {
|
||||
ASSERT_THROW(EvaluateFunction("TOFLOAT", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("TOFLOAT", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("TOFLOAT", {" -3.5 \n\t"}).Value<double>(), -3.5);
|
||||
ASSERT_EQ(EvaluateFunction("TOFLOAT", {"\n\t0.5e-1"}).Value<double>(), 0.05);
|
||||
ASSERT_EQ(EvaluateFunction("TOFLOAT", {"\n\t3.4e-3X "}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("TOFLOAT", {-3.5}).Value<double>(), -3.5);
|
||||
ASSERT_EQ(EvaluateFunction("TOFLOAT", {-3}).Value<double>(), -3.0);
|
||||
ASSERT_THROW(EvaluateFunction("TOFLOAT", {true}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionToInteger) {
|
||||
ASSERT_THROW(EvaluateFunction("TOINTEGER", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("TOINTEGER", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("TOINTEGER", {"\n\t3"}).Value<int64_t>(), 3);
|
||||
ASSERT_EQ(EvaluateFunction("TOINTEGER", {" -3.5 \n\t"}).Value<int64_t>(), -3);
|
||||
ASSERT_EQ(EvaluateFunction("TOINTEGER", {"\n\t3X "}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("TOINTEGER", {-3.5}).Value<int64_t>(), -3);
|
||||
ASSERT_EQ(EvaluateFunction("TOINTEGER", {3.5}).Value<int64_t>(), 3);
|
||||
ASSERT_THROW(EvaluateFunction("TOINTEGER", {true}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionType) {
|
||||
ASSERT_THROW(EvaluateFunction("TYPE", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("TYPE", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v1 = dba->insert_vertex();
|
||||
v1.add_label(dba->label("label1"));
|
||||
auto v2 = dba->insert_vertex();
|
||||
v2.add_label(dba->label("label2"));
|
||||
auto e = dba->insert_edge(v1, v2, dba->edge_type("type1"));
|
||||
ASSERT_EQ(EvaluateFunction("TYPE", {e}).Value<std::string>(), "type1");
|
||||
ASSERT_THROW(EvaluateFunction("TYPE", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionLabels) {
|
||||
ASSERT_THROW(EvaluateFunction("LABELS", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("LABELS", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v = dba->insert_vertex();
|
||||
v.add_label(dba->label("label1"));
|
||||
v.add_label(dba->label("label2"));
|
||||
std::vector<std::string> labels;
|
||||
auto _labels =
|
||||
EvaluateFunction("LABELS", {v}).Value<std::vector<TypedValue>>();
|
||||
for (auto label : _labels) {
|
||||
labels.push_back(label.Value<std::string>());
|
||||
}
|
||||
ASSERT_THAT(labels, UnorderedElementsAre("label1", "label2"));
|
||||
ASSERT_THROW(EvaluateFunction("LABELS", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionKeys) {
|
||||
ASSERT_THROW(EvaluateFunction("KEYS", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("KEYS", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
Dbms dbms;
|
||||
auto dba = dbms.active();
|
||||
auto v1 = dba->insert_vertex();
|
||||
v1.PropsSet(dba->property("height"), 5);
|
||||
v1.PropsSet(dba->property("age"), 10);
|
||||
auto v2 = dba->insert_vertex();
|
||||
auto e = dba->insert_edge(v1, v2, dba->edge_type("type1"));
|
||||
e.PropsSet(dba->property("width"), 3);
|
||||
e.PropsSet(dba->property("age"), 15);
|
||||
|
||||
auto prop_keys_to_string = [](TypedValue t) {
|
||||
std::vector<std::string> keys;
|
||||
for (auto property : t.Value<std::vector<TypedValue>>()) {
|
||||
keys.push_back(property.Value<std::string>());
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
ASSERT_THAT(prop_keys_to_string(EvaluateFunction("KEYS", {v1})),
|
||||
UnorderedElementsAre("height", "age"));
|
||||
ASSERT_THAT(prop_keys_to_string(EvaluateFunction("KEYS", {e})),
|
||||
UnorderedElementsAre("width", "age"));
|
||||
ASSERT_THROW(EvaluateFunction("KEYS", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionTail) {
|
||||
ASSERT_THROW(EvaluateFunction("TAIL", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("TAIL", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
std::vector<TypedValue> arguments;
|
||||
arguments.push_back(std::vector<TypedValue>{});
|
||||
ASSERT_EQ(EvaluateFunction("TAIL", arguments)
|
||||
.Value<std::vector<TypedValue>>()
|
||||
.size(),
|
||||
0U);
|
||||
arguments[0] = std::vector<TypedValue>{3, 4, true, "john"};
|
||||
auto list =
|
||||
EvaluateFunction("TAIL", arguments).Value<std::vector<TypedValue>>();
|
||||
ASSERT_EQ(list.size(), 3U);
|
||||
ASSERT_EQ(list[0].Value<int64_t>(), 4);
|
||||
ASSERT_EQ(list[1].Value<bool>(), true);
|
||||
ASSERT_EQ(list[2].Value<std::string>(), "john");
|
||||
ASSERT_THROW(EvaluateFunction("TAIL", {2}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionAbs) {
|
||||
ASSERT_THROW(EvaluateFunction("ABS", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("ABS", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("ABS", {-2}).Value<int64_t>(), 2);
|
||||
ASSERT_EQ(EvaluateFunction("ABS", {-2.5}).Value<double>(), 2.5);
|
||||
ASSERT_THROW(EvaluateFunction("ABS", {true}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionCeil) {
|
||||
ASSERT_THROW(EvaluateFunction("CEIL", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("CEIL", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("CEIL", {-2}).Value<double>(), -2);
|
||||
ASSERT_EQ(EvaluateFunction("CEIL", {-2.5}).Value<double>(), -2);
|
||||
ASSERT_EQ(EvaluateFunction("CEIL", {2.5}).Value<double>(), 3);
|
||||
ASSERT_THROW(EvaluateFunction("CEIL", {true}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionFloor) {
|
||||
ASSERT_THROW(EvaluateFunction("FLOOR", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("FLOOR", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("FLOOR", {-2}).Value<double>(), -2);
|
||||
ASSERT_EQ(EvaluateFunction("FLOOR", {-2.5}).Value<double>(), -3);
|
||||
ASSERT_EQ(EvaluateFunction("FLOOR", {2.5}).Value<double>(), 2);
|
||||
ASSERT_THROW(EvaluateFunction("FLOOR", {true}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionRound) {
|
||||
ASSERT_THROW(EvaluateFunction("ROUND", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {-2}).Value<double>(), -2);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {-2.4}).Value<double>(), -2);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {-2.5}).Value<double>(), -3);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {-2.6}).Value<double>(), -3);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {2.4}).Value<double>(), 2);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {2.5}).Value<double>(), 3);
|
||||
ASSERT_EQ(EvaluateFunction("ROUND", {2.6}).Value<double>(), 3);
|
||||
ASSERT_THROW(EvaluateFunction("ROUND", {true}), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, FunctionSign) {
|
||||
ASSERT_THROW(EvaluateFunction("SIGN", {}), QueryRuntimeException);
|
||||
ASSERT_EQ(EvaluateFunction("SIGN", {TypedValue::Null}).type(),
|
||||
TypedValue::Type::Null);
|
||||
ASSERT_EQ(EvaluateFunction("SIGN", {-2}).Value<int64_t>(), -1);
|
||||
ASSERT_EQ(EvaluateFunction("SIGN", {-0.2}).Value<int64_t>(), -1);
|
||||
ASSERT_EQ(EvaluateFunction("SIGN", {0.0}).Value<int64_t>(), 0);
|
||||
ASSERT_EQ(EvaluateFunction("SIGN", {2.5}).Value<int64_t>(), 1);
|
||||
ASSERT_THROW(EvaluateFunction("SIGN", {true}), QueryRuntimeException);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user