Implement string functions

Summary:
Added missing string functions.
I also cleaned up error messages a bit in effort to make them uniform.

Reviewers: teon.banek, buda

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1458
This commit is contained in:
Marin Tomic 2018-06-29 14:58:10 +02:00
parent 843aa4f92a
commit 18d8129b99
9 changed files with 524 additions and 79 deletions

View File

@ -11,7 +11,10 @@
* Properties on disk added.
* Telemetry added.
* SSL support added.
* Add `toString` function to openCypher.
* Added string functions to openCypher (`lTrim`, `left`, `rTrim`, `replace`,
`reverse`, `right`, `split`, `substring`, `toLower`, `toUpper`, `trim`,
`toString`).
* Added `timestamp` function to openCypher.
### Bug Fixes and Other Changes

View File

@ -695,14 +695,29 @@ functions.
`startsWith` | Check if the first argument starts with the second.
`endsWith` | Check if the first argument ends with the second.
`contains` | Check if the first argument has an element which is equal to the second argument.
`left` | Returns a string containing the specified number of leftmost characters of the original string.
`lTrim` | Returns the original string with leading whitespace removed.
`replace` | Returns a string in which all occurrences of a specified string in the original string have been replaced by another (specified) string.
`reverse` | Returns a string in which the order of all characters in the original string have been reversed.
`right` | Returns a string containing the specified number of rightmost characters of the original string.
`rTrim` | Returns the original string with trailing whitespace removed.
`split` | Returns a list of strings resulting from the splitting of the original string around matches of the given delimiter.
`substring` | Returns a substring of the original string, beginning with a 0-based index start and length.
`toLower` | Returns the original string in lowercase.
`toString` | Converts an integer, float or boolean value to a string.
`toUpper` | Returns the original string in uppercase.
`trim` | Returns the original string with leading and trailing whitespace removed.
`all` | Check if all elements of a list satisfy a predicate.<br/>The syntax is: `all(variable IN list WHERE predicate)`.<br/> NOTE: Whenever possible, use Memgraph's lambda functions when [matching](#filtering-variable-length-paths) instead.
`single` | Check if only one element of a list satisfies a predicate.<br/>The syntax is: `single(variable IN list WHERE predicate)`.
`reduce` | Accumulate list elements into a single result by applying an expression. The syntax is:<br/>`reduce(accumulator = initial_value, variable IN list | expression)`.
`extract` | A list of values obtained by evaluating an expression for each element in list. The syntax is:<br>`extract(variable IN list | expression)`.
`assert` | Raises an exception reported to the client if the given argument is not `true`.
`counter` | Generates integers that are guaranteed to be unique on the database level, for the given counter name.
`counterSet` | Sets the counter with the given name to the given value.
`indexInfo` | Returns a list of all the indexes available in the database. The list includes indexes that are not yet ready for use (they are concurrently being built by another transaction).
`id` | Returns identifier for a given node or edge. To enable automatic generation of the identifiers, `--generate-vertex-ids` and `--generate-edge-ids` parameters have to be set on `true` (enabled in the configuration by default).
`timestamp` | Returns the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.
#### String Operators
@ -825,9 +840,6 @@ General purpose functions:
* `exists(n.property)` - This can be expressed using `n.property IS NOT NULL`.
* `length()` is named `size()` in Memgraph.
Path functions:
* `extract()`
Aggregation functions:
* `count(DISTINCT variable)` - This can be expressed using `WITH DISTINCT variable RETURN count(variable)`.
@ -838,20 +850,6 @@ Mathematical functions:
* `distance()`
* `degrees()`
String functions:
* `replace()`
* `substring()`
* `left()`
* `trim()`
* `toupper()`
* `tolower()`
* `split()`
* `reverse()`
List functions:
* `any()`
* `none()`
* `single()`
* `head()`
* `last()`
* `tail()`

View File

@ -30,7 +30,7 @@ namespace {
// number of results of this pattern. I don't think we will ever do this.
// TODO: Implement rest of the list functions.
// TODO: Implement degrees, haversin, radians
// TODO: Implement string and spatial functions
// TODO: Implement spatial functions
TypedValue Coalesce(const std::vector<TypedValue> &args, Context *) {
if (args.size() == 0U) {
@ -54,7 +54,7 @@ TypedValue EndNode(const std::vector<TypedValue> &args, Context *) {
case TypedValue::Type::Edge:
return args[0].Value<EdgeAccessor>().to();
default:
throw QueryRuntimeException("endNode called with incompatible type");
throw QueryRuntimeException("endNode argument should be an edge");
}
}
@ -71,7 +71,7 @@ TypedValue Head(const std::vector<TypedValue> &args, Context *) {
return list[0];
}
default:
throw QueryRuntimeException("head called with incompatible type");
throw QueryRuntimeException("head argument should be a list");
}
}
@ -88,7 +88,7 @@ TypedValue Last(const std::vector<TypedValue> &args, Context *) {
return list.back();
}
default:
throw QueryRuntimeException("last called with incompatible type");
throw QueryRuntimeException("last argument should be a list");
}
}
@ -112,7 +112,8 @@ TypedValue Properties(const std::vector<TypedValue> &args, Context *ctx) {
case TypedValue::Type::Edge:
return get_properties(args[0].Value<EdgeAccessor>());
default:
throw QueryRuntimeException("properties called with incompatible type");
throw QueryRuntimeException(
"properties argument should be a vertex or an edge");
}
}
@ -136,7 +137,8 @@ TypedValue Size(const std::vector<TypedValue> &args, Context *) {
case TypedValue::Type::Path:
return static_cast<int64_t>(args[0].ValuePath().edges().size());
default:
throw QueryRuntimeException("size called with incompatible type");
throw QueryRuntimeException(
"size argument should be a string, a collection or a path");
}
}
@ -150,7 +152,7 @@ TypedValue StartNode(const std::vector<TypedValue> &args, Context *) {
case TypedValue::Type::Edge:
return args[0].Value<EdgeAccessor>().from();
default:
throw QueryRuntimeException("startNode called with incompatible type");
throw QueryRuntimeException("startNode argument should be an edge");
}
}
@ -166,7 +168,7 @@ TypedValue Degree(const std::vector<TypedValue> &args, Context *) {
return static_cast<int64_t>(vertex.out_degree() + vertex.in_degree());
}
default:
throw QueryRuntimeException("degree called with incompatible type");
throw QueryRuntimeException("degree argument should be a vertex");
}
}
@ -190,7 +192,8 @@ TypedValue ToBoolean(const std::vector<TypedValue> &args, Context *) {
return TypedValue::Null;
}
default:
throw QueryRuntimeException("toBoolean called with incompatible type");
throw QueryRuntimeException(
"toBoolean argument should be an integer, a string or a boolean");
}
}
@ -212,7 +215,8 @@ TypedValue ToFloat(const std::vector<TypedValue> &args, Context *) {
return TypedValue::Null;
}
default:
throw QueryRuntimeException("toFloat called with incompatible type");
throw QueryRuntimeException(
"toFloat argument should be a string or a number");
}
}
@ -239,7 +243,8 @@ TypedValue ToInteger(const std::vector<TypedValue> &args, Context *) {
return TypedValue::Null;
}
default:
throw QueryRuntimeException("toInteger called with incompatible type");
throw QueryRuntimeException(
"toInteger argument should be a string, a boolean or a number");
}
}
@ -254,7 +259,7 @@ TypedValue Type(const std::vector<TypedValue> &args, Context *ctx) {
return ctx->db_accessor_.EdgeTypeName(
args[0].Value<EdgeAccessor>().EdgeType());
default:
throw QueryRuntimeException("type called with incompatible type");
throw QueryRuntimeException("type argument should be an edge");
}
}
@ -277,7 +282,8 @@ TypedValue Keys(const std::vector<TypedValue> &args, Context *ctx) {
case TypedValue::Type::Edge:
return get_keys(args[0].Value<EdgeAccessor>());
default:
throw QueryRuntimeException("keys called with incompatible type");
throw QueryRuntimeException(
"keys argument should be a vertex or an edge");
}
}
@ -296,7 +302,7 @@ TypedValue Labels(const std::vector<TypedValue> &args, Context *ctx) {
return labels;
}
default:
throw QueryRuntimeException("labels called with incompatible type");
throw QueryRuntimeException("labels argument should be a vertex");
}
}
@ -306,7 +312,7 @@ TypedValue Nodes(const std::vector<TypedValue> &args, Context *) {
}
if (args[0].IsNull()) return TypedValue::Null;
if (!args[0].IsPath()) {
throw QueryRuntimeException("nodes called with incompatible type");
throw QueryRuntimeException("nodes argument should be a path");
}
auto &vertices = args[0].ValuePath().vertices();
return std::vector<TypedValue>(vertices.begin(), vertices.end());
@ -318,7 +324,7 @@ TypedValue Relationships(const std::vector<TypedValue> &args, Context *) {
}
if (args[0].IsNull()) return TypedValue::Null;
if (!args[0].IsPath()) {
throw QueryRuntimeException("relationships called with incompatible type");
throw QueryRuntimeException("relationships argument should be a path");
}
auto &edges = args[0].ValuePath().edges();
return std::vector<TypedValue>(edges.begin(), edges.end());
@ -333,7 +339,7 @@ TypedValue Range(const std::vector<TypedValue> &args, Context *) {
if (t.IsNull()) {
has_null = true;
} else if (t.type() != TypedValue::Type::Int) {
throw QueryRuntimeException("range called with incompatible type");
throw QueryRuntimeException("arguments of range should be integers");
}
};
std::for_each(args.begin(), args.end(), check_type);
@ -371,7 +377,7 @@ TypedValue Tail(const std::vector<TypedValue> &args, Context *) {
return list;
}
default:
throw QueryRuntimeException("tail called with incompatible type");
throw QueryRuntimeException("tail argument should be a list");
}
}
@ -388,7 +394,7 @@ TypedValue Abs(const std::vector<TypedValue> &args, Context *) {
case TypedValue::Type::Double:
return std::abs(args[0].Value<double>());
default:
throw QueryRuntimeException("abs called with incompatible type");
throw QueryRuntimeException("abs argument should be a number");
}
}
@ -406,7 +412,7 @@ TypedValue Abs(const std::vector<TypedValue> &args, Context *) {
return lowercased_name(args[0].Value<double>()); \
default: \
throw QueryRuntimeException(#lowercased_name \
" called with incompatible type"); \
" argument should be a number"); \
} \
}
@ -441,7 +447,7 @@ TypedValue Atan2(const std::vector<TypedValue> &args, Context *) {
case TypedValue::Type::Double:
return t.Value<double>();
default:
throw QueryRuntimeException("atan2 called with incompatible types");
throw QueryRuntimeException("arguments of atan2 should be numbers");
}
};
double y = to_double(args[0]);
@ -462,20 +468,20 @@ TypedValue Sign(const std::vector<TypedValue> &args, Context *) {
case TypedValue::Type::Double:
return sign(args[0].Value<double>());
default:
throw QueryRuntimeException("sign called with incompatible type");
throw QueryRuntimeException("sign argument should be a number");
}
}
TypedValue E(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 0U) {
throw QueryRuntimeException("e shouldn't be called with arguments");
throw QueryRuntimeException("e requires no arguments");
}
return M_E;
}
TypedValue Pi(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 0U) {
throw QueryRuntimeException("pi shouldn't be called with arguments");
throw QueryRuntimeException("pi requires no arguments");
}
return M_PI;
}
@ -484,7 +490,7 @@ TypedValue Rand(const std::vector<TypedValue> &args, Context *) {
static thread_local std::mt19937 pseudo_rand_gen_{std::random_device{}()};
static thread_local std::uniform_real_distribution<> rand_dist_{0, 1};
if (args.size() != 0U) {
throw QueryRuntimeException("rand shouldn't be called with arguments");
throw QueryRuntimeException("rand requires no arguments");
}
return rand_dist_(pseudo_rand_gen_);
}
@ -493,14 +499,15 @@ template <bool (*Predicate)(const std::string &s1, const std::string &s2)>
TypedValue StringMatchOperator(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 2U) {
throw QueryRuntimeException(
"startsWith shouldn't be called with 2 arguments");
"startsWith and endsWith require two arguments");
}
bool has_null = false;
auto check_arg = [&](const TypedValue &t) {
if (t.IsNull()) {
has_null = true;
} else if (t.type() != TypedValue::Type::String) {
throw QueryRuntimeException("startsWith called with incompatible type");
throw QueryRuntimeException(
"arguments of startsWith and endsWith should be strings");
}
};
check_arg(args[0]);
@ -534,12 +541,12 @@ auto Contains = StringMatchOperator<ContainsPredicate>;
TypedValue Assert(const std::vector<TypedValue> &args, Context *) {
if (args.size() < 1U || args.size() > 2U) {
throw QueryRuntimeException("assert takes one or two arguments");
throw QueryRuntimeException("assert requires one or two arguments");
}
if (args[0].type() != TypedValue::Type::Bool)
throw QueryRuntimeException("first assert argument must be bool");
throw QueryRuntimeException("first argument of assert must be a boolean");
if (args.size() == 2U && args[1].type() != TypedValue::Type::String)
throw QueryRuntimeException("second assert argument must be a string");
throw QueryRuntimeException("second argument of assert must be a string");
if (!args[0].ValueBool()) {
std::string message("assertion failed");
if (args.size() == 2U) message += ": " + args[1].ValueString();
@ -550,30 +557,31 @@ TypedValue Assert(const std::vector<TypedValue> &args, Context *) {
TypedValue Counter(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
throw QueryRuntimeException("counter takes one argument");
throw QueryRuntimeException("counter requires one argument");
}
if (!args[0].IsString())
throw QueryRuntimeException("first counter argument must be a string");
throw QueryRuntimeException("counter argument must be a string");
return ctx->db_accessor_.Counter(args[0].ValueString());
}
TypedValue CounterSet(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2U) {
throw QueryRuntimeException("counterSet takes two arguments");
throw QueryRuntimeException("counterSet requires two arguments");
}
if (!args[0].IsString())
throw QueryRuntimeException("first counterSet argument must be a string");
throw QueryRuntimeException(
"first argument of counterSet must be a string");
if (!args[1].IsInt())
throw QueryRuntimeException("first counterSet argument must be an int");
throw QueryRuntimeException(
"second argument of counterSet must be an integer");
ctx->db_accessor_.CounterSet(args[0].ValueString(), args[1].ValueInt());
return TypedValue::Null;
}
TypedValue IndexInfo(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 0U)
throw QueryRuntimeException("indexInfo takes zero arguments");
throw QueryRuntimeException("indexInfo requires no arguments");
auto info = ctx->db_accessor_.IndexInfo();
return std::vector<TypedValue>(info.begin(), info.end());
@ -581,7 +589,7 @@ TypedValue IndexInfo(const std::vector<TypedValue> &args, Context *ctx) {
TypedValue WorkerId(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
throw QueryRuntimeException("workerId takes one argument");
throw QueryRuntimeException("workerId requires one argument");
}
auto &arg = args[0];
switch (arg.type()) {
@ -590,13 +598,14 @@ TypedValue WorkerId(const std::vector<TypedValue> &args, Context *) {
case TypedValue::Type::Edge:
return arg.ValueEdge().GlobalAddress().worker_id();
default:
throw QueryRuntimeException("workerId argument must be a vertex or edge");
throw QueryRuntimeException(
"workerId argument must be a vertex or an edge");
}
}
TypedValue Id(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
throw QueryRuntimeException("Id takes one argument");
throw QueryRuntimeException("id requires one argument");
}
auto &arg = args[0];
switch (arg.type()) {
@ -605,8 +614,8 @@ TypedValue Id(const std::vector<TypedValue> &args, Context *ctx) {
ctx->db_accessor_.Property(PropertyValueStore::IdPropertyName));
if (id.IsNull()) {
throw QueryRuntimeException(
"Ids are not set on vertices, have a look at flags to "
"automatically generate them.");
"IDs are not set on vertices, --generate-vertex-ids flag must be "
"set on startup to automatically generate them");
}
return id.Value<int64_t>();
}
@ -615,19 +624,19 @@ TypedValue Id(const std::vector<TypedValue> &args, Context *ctx) {
ctx->db_accessor_.Property(PropertyValueStore::IdPropertyName));
if (id.IsNull()) {
throw QueryRuntimeException(
"Ids are not set on edges, have a look at flags to "
"automatically generate them.");
"IDs are not set on edges, --generate-edge-ids flag must be set on "
"startup to automatically generate them");
}
return id.Value<int64_t>();
}
default:
throw QueryRuntimeException("Id argument must be a vertex or edge");
throw QueryRuntimeException("id argument must be a vertex or an edge");
}
}
TypedValue ToString(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
throw QueryRuntimeException("toString takes one argument");
throw QueryRuntimeException("toString requires one argument");
}
auto &arg = args[0];
switch (arg.type()) {
@ -643,69 +652,234 @@ TypedValue ToString(const std::vector<TypedValue> &args, Context *) {
return arg.ValueBool() ? "true" : "false";
default:
throw QueryRuntimeException(
"toString argument must be a number, string or boolean");
"toString argument must be a number, a string or a boolean");
}
}
TypedValue Timestamp(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 0) {
throw QueryRuntimeException("timestamp takes zero arguments");
throw QueryRuntimeException("timestamp requires no arguments");
}
return ctx->timestamp_;
}
TypedValue Left(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2) {
throw QueryRuntimeException("left requires two arguments");
}
switch (args[0].type()) {
case TypedValue::Type::Null:
if (args[1].IsNull() || (args[1].IsInt() && args[1].ValueInt() >= 0)) {
return TypedValue::Null;
}
throw QueryRuntimeException(
"second argument of left must be a non-negative integer");
case TypedValue::Type::String:
if (args[1].IsInt() && args[1].ValueInt() >= 0) {
return args[0].ValueString().substr(0, args[1].ValueInt());
}
throw QueryRuntimeException(
"second argument of left must be a non-negative integer");
default:
throw QueryRuntimeException("first argument of left must be a string");
}
}
TypedValue Right(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2) {
throw QueryRuntimeException("right requires two arguments");
}
switch (args[0].type()) {
case TypedValue::Type::Null:
if (args[1].IsNull() || (args[1].IsInt() && args[1].ValueInt() >= 0)) {
return TypedValue::Null;
}
throw QueryRuntimeException(
"second argument of right must be a non-negative integer");
case TypedValue::Type::String: {
const auto &str = args[0].ValueString();
if (args[1].IsInt() && args[1].ValueInt() >= 0) {
int len = args[1].ValueInt();
return len <= str.size() ? str.substr(str.size() - len, len) : str;
}
throw QueryRuntimeException(
"second argument of right must be a non-negative integer");
}
default:
throw QueryRuntimeException("first argument of right must be a string");
}
}
#define WRAP_STRING_FUNCTION(name, lowercased_name, function) \
TypedValue name(const std::vector<TypedValue> &args, Context *) { \
if (args.size() != 1U) { \
throw QueryRuntimeException(#lowercased_name " requires one argument"); \
} \
switch (args[0].type()) { \
case TypedValue::Type::Null: \
return TypedValue::Null; \
case TypedValue::Type::String: \
return function(args[0].ValueString()); \
default: \
throw QueryRuntimeException(#lowercased_name \
" argument should be a string"); \
} \
}
WRAP_STRING_FUNCTION(LTrim, lTrim, utils::LTrim);
WRAP_STRING_FUNCTION(RTrim, rTrim, utils::RTrim);
WRAP_STRING_FUNCTION(Trim, trim, utils::Trim);
WRAP_STRING_FUNCTION(Reverse, reverse, utils::Reversed);
WRAP_STRING_FUNCTION(ToLower, toLower, utils::ToLowerCase);
WRAP_STRING_FUNCTION(ToUpper, toUpper, utils::ToUpperCase);
TypedValue Replace(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 3U) {
throw QueryRuntimeException("replace requires three arguments");
}
if (!args[0].IsNull() && !args[0].IsString()) {
throw QueryRuntimeException("first argument of replace should be a string");
}
if (!args[1].IsNull() && !args[1].IsString()) {
throw QueryRuntimeException(
"second argument of replace should be a string");
}
if (!args[2].IsNull() && !args[2].IsString()) {
throw QueryRuntimeException("third argument of replace should be a string");
}
if (args[0].IsNull() || args[1].IsNull() || args[2].IsNull()) {
return TypedValue::Null;
}
return utils::Replace(args[0].ValueString(), args[1].ValueString(),
args[2].ValueString());
}
TypedValue Split(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2U) {
throw QueryRuntimeException("split requires two arguments");
}
if (!args[0].IsNull() && !args[0].IsString()) {
throw QueryRuntimeException("first argument of split should be a string");
}
if (!args[1].IsNull() && !args[1].IsString()) {
throw QueryRuntimeException("second argument of split should be a string");
}
if (args[0].IsNull() || args[1].IsNull()) {
return TypedValue::Null;
}
std::vector<TypedValue> result;
for (const auto &str :
utils::Split(args[0].ValueString(), args[1].ValueString())) {
result.emplace_back(str);
}
return result;
}
TypedValue Substring(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 2U && args.size() != 3U) {
throw QueryRuntimeException("substring requires two or three arguments");
}
if (!args[0].IsNull() && !args[0].IsString()) {
throw QueryRuntimeException(
"first argument of substring should be a string");
}
if (!args[1].IsInt() || args[1].ValueInt() < 0) {
throw QueryRuntimeException(
"second argument of substring should be a non-negative integer");
}
if (args.size() == 3U && (!args[2].IsInt() || args[2].ValueInt() < 0)) {
throw QueryRuntimeException(
"third argument of substring should be a non-negative integer");
}
if (args[0].IsNull()) {
return TypedValue::Null;
}
const auto &str = args[0].ValueString();
int start = args[1].ValueInt();
if (args.size() == 2U) {
return start < str.size() ? str.substr(start) : "";
}
int len = args[2].ValueInt();
return start < str.size() ? str.substr(start, len) : "";
}
} // namespace
std::function<TypedValue(const std::vector<TypedValue> &, Context *)>
NameToFunction(const std::string &function_name) {
// Scalar functions
if (function_name == "COALESCE") return Coalesce;
if (function_name == "DEGREE") return Degree;
if (function_name == "ENDNODE") return EndNode;
if (function_name == "HEAD") return Head;
if (function_name == "ID") return Id;
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 == "DEGREE") return Degree;
if (function_name == "TIMESTAMP") return Timestamp;
if (function_name == "TOBOOLEAN") return ToBoolean;
if (function_name == "TOFLOAT") return ToFloat;
if (function_name == "TOINTEGER") return ToInteger;
if (function_name == "TYPE") return Type;
// List functions
if (function_name == "KEYS") return Keys;
if (function_name == "LABELS") return Labels;
if (function_name == "NODES") return Nodes;
if (function_name == "RELATIONSHIPS") return Relationships;
if (function_name == "RANGE") return Range;
if (function_name == "RELATIONSHIPS") return Relationships;
if (function_name == "TAIL") return Tail;
// Mathematical functions - numeric
if (function_name == "ABS") return Abs;
if (function_name == "CEIL") return Ceil;
if (function_name == "FLOOR") return Floor;
if (function_name == "RAND") return Rand;
if (function_name == "ROUND") return Round;
if (function_name == "SIGN") return Sign;
// Mathematical functions - logarithmic
if (function_name == "E") return E;
if (function_name == "EXP") return Exp;
if (function_name == "LOG") return Log;
if (function_name == "LOG10") return Log10;
if (function_name == "SQRT") return Sqrt;
// Mathematical functions - trigonometric
if (function_name == "ACOS") return Acos;
if (function_name == "ASIN") return Asin;
if (function_name == "ATAN") return Atan;
if (function_name == "ATAN2") return Atan2;
if (function_name == "COS") return Cos;
if (function_name == "PI") return Pi;
if (function_name == "SIN") return Sin;
if (function_name == "TAN") return Tan;
if (function_name == "SIGN") return Sign;
if (function_name == "E") return E;
if (function_name == "PI") return Pi;
if (function_name == "RAND") return Rand;
if (function_name == kStartsWith) return StartsWith;
if (function_name == kEndsWith) return EndsWith;
// String functions
if (function_name == kContains) return Contains;
if (function_name == kEndsWith) return EndsWith;
if (function_name == "LEFT") return Left;
if (function_name == "LTRIM") return LTrim;
if (function_name == "REPLACE") return Replace;
if (function_name == "REVERSE") return Reverse;
if (function_name == "RIGHT") return Right;
if (function_name == "RTRIM") return RTrim;
if (function_name == "SPLIT") return Split;
if (function_name == kStartsWith) return StartsWith;
if (function_name == "SUBSTRING") return Substring;
if (function_name == "TOLOWER") return ToLower;
if (function_name == "TOSTRING") return ToString;
if (function_name == "TOUPPER") return ToUpper;
if (function_name == "TRIM") return Trim;
// Memgraph specific functions
if (function_name == "ASSERT") return Assert;
if (function_name == "COUNTER") return Counter;
if (function_name == "COUNTERSET") return CounterSet;
if (function_name == "INDEXINFO") return IndexInfo;
if (function_name == "WORKERID") return WorkerId;
if (function_name == "ID") return Id;
if (function_name == "TOSTRING") return ToString;
if (function_name == "TIMESTAMP") return Timestamp;
return nullptr;
}
} // namespace query

View File

@ -105,6 +105,16 @@ inline bool Contains(const TIterable &iterable, const TElement &element) {
return std::find(iterable.begin(), iterable.end(), element) != iterable.end();
}
/**
* Returns a *copy* of a collection with its elements in reversed order.
*
* @param collection Collection to be reversed.
*/
template <class TCollection>
TCollection Reversed(const TCollection &collection) {
return TCollection(std::rbegin(collection), std::rend(collection));
}
/**
* Converts a (beginning, end) pair of iterators into an iterable that can be
* passed on to itertools.

View File

@ -15,6 +15,88 @@
namespace utils {
/**
* Removes whitespace characters from the start of a string.
*
* @param str string that is going to be trimmed
*
* @return trimmed string
*/
inline std::string LTrim(const std::string &s) {
auto begin = s.begin();
auto end = s.end();
if (begin == end) {
// Need to check this to be sure that std::prev(end) exists.
return s;
}
while (begin < end && isspace(*begin)) {
++begin;
}
return std::string(begin, end);
}
/**
* Removes characters contained in chars from the start of a string.
*
* @param s string that is going to be trimmed
* @param chars string that contains chars that are to be removed
*
* @return trimmed string
*/
inline std::string LTrim(const std::string &s, const std::string &chars) {
auto begin = s.begin();
auto end = s.end();
if (begin == end) {
// Need to check this to be sure that std::prev(end) exists.
return s;
}
while (begin < end && chars.find(*begin) != std::string::npos) {
++begin;
}
return std::string(begin, end);
}
/**
* Removes whitespace characters from the end of a string.
*
* @param str string that is going to be trimmed
*
* @return trimmed string
*/
inline std::string RTrim(const std::string &s) {
auto begin = s.begin();
auto end = s.end();
if (begin == end) {
// Need to check this to be sure that std::prev(end) exists.
return s;
}
while (end > begin && isspace(*std::prev(end))) {
--end;
}
return std::string(begin, end);
}
/**
* Removes characters contained in chars from the end of a string.
*
* @param s string that is going to be trimmed
* @param chars string that contains chars that are to be removed
*
* @return trimmed string
*/
inline std::string RTrim(const std::string &s, const std::string &chars) {
auto begin = s.begin();
auto end = s.end();
if (begin == end) {
// Need to check this to be sure that std::prev(end) exists.
return s;
}
while (end > begin && chars.find(*std::prev(end)) != std::string::npos) {
--end;
}
return std::string(begin, end);
}
/**
* Removes whitespace characters from the start and from the end of a string.
*

View File

@ -259,6 +259,9 @@ target_link_libraries(${test_prefix}network_utils mg-io)
# Test mg-utils
add_unit_test(utils_algorithm.cpp)
target_link_libraries(${test_prefix}utils_algorithm mg-utils)
add_unit_test(utils_demangle.cpp)
target_link_libraries(${test_prefix}utils_demangle mg-utils)

View File

@ -1522,4 +1522,144 @@ TEST(ExpressionEvaluator, FunctionTimestampExceptions) {
EXPECT_THROW(EvaluateFunction("TIMESTAMP", {1}, &eval.ctx).ValueInt(),
QueryRuntimeException);
}
TEST(ExpressionEvaluator, FunctionLeft) {
EXPECT_THROW(EvaluateFunction("LEFT", {}), QueryRuntimeException);
EXPECT_TRUE(
EvaluateFunction("LEFT", {TypedValue::Null, TypedValue::Null}).IsNull());
EXPECT_TRUE(EvaluateFunction("LEFT", {TypedValue::Null, 10}).IsNull());
EXPECT_THROW(EvaluateFunction("LEFT", {TypedValue::Null, -10}),
QueryRuntimeException);
EXPECT_EQ(EvaluateFunction("LEFT", {"memgraph", 0}).ValueString(), "");
EXPECT_EQ(EvaluateFunction("LEFT", {"memgraph", 3}).ValueString(), "mem");
EXPECT_EQ(EvaluateFunction("LEFT", {"memgraph", 1000}).ValueString(),
"memgraph");
EXPECT_THROW(EvaluateFunction("LEFT", {"memgraph", -10}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("LEFT", {"memgraph", "graph"}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("LEFT", {132, 10}), QueryRuntimeException);
}
TEST(ExpressionEvaluator, FunctionRight) {
EXPECT_THROW(EvaluateFunction("RIGHT", {}), QueryRuntimeException);
EXPECT_TRUE(
EvaluateFunction("RIGHT", {TypedValue::Null, TypedValue::Null}).IsNull());
EXPECT_TRUE(EvaluateFunction("RIGHT", {TypedValue::Null, 10}).IsNull());
EXPECT_THROW(EvaluateFunction("RIGHT", {TypedValue::Null, -10}),
QueryRuntimeException);
EXPECT_EQ(EvaluateFunction("RIGHT", {"memgraph", 0}).ValueString(), "");
EXPECT_EQ(EvaluateFunction("RIGHT", {"memgraph", 3}).ValueString(), "aph");
EXPECT_EQ(EvaluateFunction("RIGHT", {"memgraph", 1000}).ValueString(),
"memgraph");
EXPECT_THROW(EvaluateFunction("RIGHT", {"memgraph", -10}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("RIGHT", {"memgraph", "graph"}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("RIGHT", {132, 10}), QueryRuntimeException);
}
TEST(ExpressionEvaluator, Trimming) {
EXPECT_TRUE(EvaluateFunction("LTRIM", {TypedValue::Null}).IsNull());
EXPECT_TRUE(EvaluateFunction("RTRIM", {TypedValue::Null}).IsNull());
EXPECT_TRUE(EvaluateFunction("TRIM", {TypedValue::Null}).IsNull());
EXPECT_EQ(EvaluateFunction("LTRIM", {" abc "}).ValueString(), "abc ");
EXPECT_EQ(EvaluateFunction("RTRIM", {" abc "}).ValueString(), " abc");
EXPECT_EQ(EvaluateFunction("TRIM", {"abc"}).ValueString(), "abc");
EXPECT_THROW(EvaluateFunction("LTRIM", {"x", "y"}), QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("RTRIM", {"x", "y"}), QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("TRIM", {"x", "y"}), QueryRuntimeException);
}
TEST(ExpressionEvaluator, FunctionReverse) {
EXPECT_TRUE(EvaluateFunction("REVERSE", {TypedValue::Null}).IsNull());
EXPECT_EQ(EvaluateFunction("REVERSE", {"abc"}).ValueString(), "cba");
EXPECT_THROW(EvaluateFunction("REVERSE", {"x", "y"}), QueryRuntimeException);
}
TEST(ExpressionEvaluator, FunctionReplace) {
EXPECT_THROW(EvaluateFunction("REPLACE", {}), QueryRuntimeException);
EXPECT_TRUE(
EvaluateFunction("REPLACE", {TypedValue::Null, "l", "w"}).IsNull());
EXPECT_TRUE(
EvaluateFunction("REPLACE", {"hello", TypedValue::Null, "w"}).IsNull());
EXPECT_TRUE(
EvaluateFunction("REPLACE", {"hello", "l", TypedValue::Null}).IsNull());
EXPECT_EQ(EvaluateFunction("REPLACE", {"hello", "l", "w"}).ValueString(),
"hewwo");
EXPECT_THROW(EvaluateFunction("REPLACE", {1, "l", "w"}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("REPLACE", {"hello", 1, "w"}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("REPLACE", {"hello", "l", 1}),
QueryRuntimeException);
}
TEST(ExpressionEvaluator, FunctionSplit) {
EXPECT_THROW(EvaluateFunction("SPLIT", {}), QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("SPLIT", {"one,two", 1}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("SPLIT", {1, "one,two"}),
QueryRuntimeException);
EXPECT_TRUE(
EvaluateFunction("SPLIT", {TypedValue::Null, TypedValue::Null}).IsNull());
EXPECT_TRUE(
EvaluateFunction("SPLIT", {"one,two", TypedValue::Null}).IsNull());
EXPECT_TRUE(EvaluateFunction("SPLIT", {TypedValue::Null, ","}).IsNull());
auto result = EvaluateFunction("SPLIT", {"one,two", ","});
EXPECT_TRUE(result.IsList());
EXPECT_EQ(result.ValueList()[0].ValueString(), "one");
EXPECT_EQ(result.ValueList()[1].ValueString(), "two");
}
TEST(ExpressionEvaluator, FunctionSubstring) {
EXPECT_THROW(EvaluateFunction("SUBSTRING", {}), QueryRuntimeException);
EXPECT_TRUE(
EvaluateFunction("SUBSTRING", {TypedValue::Null, 0, 10}).IsNull());
EXPECT_THROW(
EvaluateFunction("SUBSTRING", {TypedValue::Null, TypedValue::Null}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("SUBSTRING", {TypedValue::Null, -10}),
QueryRuntimeException);
EXPECT_THROW(
EvaluateFunction("SUBSTRING", {TypedValue::Null, 0, TypedValue::Null}),
QueryRuntimeException);
EXPECT_THROW(EvaluateFunction("SUBSTRING", {TypedValue::Null, 0, -10}),
QueryRuntimeException);
EXPECT_EQ(EvaluateFunction("SUBSTRING", {"hello", 2}).ValueString(), "llo");
EXPECT_EQ(EvaluateFunction("SUBSTRING", {"hello", 10}).ValueString(), "");
EXPECT_EQ(EvaluateFunction("SUBSTRING", {"hello", 2, 0}).ValueString(), "");
EXPECT_EQ(EvaluateFunction("SUBSTRING", {"hello", 1, 3}).ValueString(),
"ell");
EXPECT_EQ(EvaluateFunction("SUBSTRING", {"hello", 1, 4}).ValueString(),
"ello");
EXPECT_EQ(EvaluateFunction("SUBSTRING", {"hello", 1, 10}).ValueString(),
"ello");
}
TEST(ExpressionEvaluator, FunctionToLower) {
EXPECT_THROW(EvaluateFunction("TOLOWER", {}), QueryRuntimeException);
EXPECT_TRUE(EvaluateFunction("TOLOWER", {TypedValue::Null}).IsNull());
EXPECT_EQ(EvaluateFunction("TOLOWER", {"Ab__C"}).ValueString(), "ab__c");
}
TEST(ExpressionEvaluator, FunctionToUpper) {
EXPECT_THROW(EvaluateFunction("TOUPPER", {}), QueryRuntimeException);
EXPECT_TRUE(EvaluateFunction("TOUPPER", {TypedValue::Null}).IsNull());
EXPECT_EQ(EvaluateFunction("TOUPPER", {"Ab__C"}).ValueString(), "AB__C");
}
} // namespace

View File

@ -0,0 +1,22 @@
#include <list>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "utils/algorithm.hpp"
using vec = std::vector<std::string>;
using namespace std::string_literals;
using namespace utils;
TEST(Algorithm, Reversed) {
EXPECT_EQ(Reversed(""s), ""s);
EXPECT_EQ(Reversed("abc"s), "cba"s);
EXPECT_EQ(Reversed(std::vector<int>({1, 2, 3, 4})),
std::vector<int>({4, 3, 2, 1}));
EXPECT_EQ(Reversed(std::list<std::string>({"ab"s, "cd"s})),
std::list<std::string>({"cd"s, "ab"s}));
}

View File

@ -8,6 +8,20 @@ using vec = std::vector<std::string>;
using namespace utils;
TEST(String, LTrim) {
EXPECT_EQ(LTrim(" \t\n\r ab\r\n\t ab \r\t "), "ab\r\n\t ab \r\t ");
EXPECT_EQ(LTrim(" \t\n\r"), "");
EXPECT_EQ(LTrim("run()"), "run()");
EXPECT_EQ(LTrim(""), "");
}
TEST(String, RTrim) {
EXPECT_EQ(RTrim(" \t\n\r ab\r\n\t ab \r\t "), " \t\n\r ab\r\n\t ab");
EXPECT_EQ(RTrim(" \t\n\r"), "");
EXPECT_EQ(RTrim("run()"), "run()");
EXPECT_EQ(RTrim(""), "");
}
TEST(String, Trim) {
EXPECT_EQ(Trim(" \t\n\r ab\r\n\t ab \r\t "), "ab\r\n\t ab");
EXPECT_EQ(Trim(" \t\n\r"), "");
@ -118,8 +132,7 @@ TEST(String, RandomString) {
EXPECT_EQ(RandomString(42).size(), 42);
std::set<std::string> string_set;
for (int i = 0 ; i < 20 ; ++i)
string_set.emplace(RandomString(256));
for (int i = 0; i < 20; ++i) string_set.emplace(RandomString(256));
EXPECT_EQ(string_set.size(), 20);
}