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:
parent
843aa4f92a
commit
18d8129b99
@ -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
|
||||
|
||||
|
@ -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()`
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
22
tests/unit/utils_algorithm.cpp
Normal file
22
tests/unit/utils_algorithm.cpp
Normal 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}));
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user