From 38c6625e2c7ea5745526476776e32bcd64f3c005 Mon Sep 17 00:00:00 2001 From: Teon Banek Date: Wed, 29 May 2019 13:37:11 +0200 Subject: [PATCH] utils: Support std::basic_string with allocators Reviewers: mtomic, mferencevic, msantl Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2102 --- .../interpret/awesome_memgraph_functions.cpp | 2 +- src/utils/algorithm.hpp | 14 +- src/utils/string.hpp | 559 ++++++++++-------- tests/manual/ha_proxy.cpp | 5 +- tests/manual/interactive_planning.cpp | 4 +- .../snapshot_generation/graph_state.hpp | 2 +- tests/unit/utils_string.cpp | 2 + tools/src/mg_client/main.cpp | 2 +- tools/src/mg_import_csv/main.cpp | 8 +- 9 files changed, 338 insertions(+), 260 deletions(-) diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index eb747eb91..01fd3e6a8 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -807,7 +807,7 @@ TypedValue Right(TypedValue *args, int64_t nargs, const EvaluationContext &, case TypedValue::Type::Null: \ return TypedValue::Null; \ case TypedValue::Type::String: \ - return function(args[0].ValueString()); \ + return std::string(function(args[0].ValueString())); \ default: \ throw QueryRuntimeException("'" #lowercased_name \ "' argument should be a string."); \ diff --git a/src/utils/algorithm.hpp b/src/utils/algorithm.hpp index 2ecce1c99..8ff667907 100644 --- a/src/utils/algorithm.hpp +++ b/src/utils/algorithm.hpp @@ -142,15 +142,23 @@ inline bool Contains(const TIterable &iterable, const TElement &element) { } /** - * Returns a *copy* of a collection with its elements in reversed order. - * - * @param collection Collection to be reversed. + * Return a reversed copy of the given collection. + * The copy is allocated using the default allocator. */ template TCollection Reversed(const TCollection &collection) { return TCollection(std::rbegin(collection), std::rend(collection)); } +/** + * Return a reversed copy of the given collection. + * The copy is allocated with the given `alloc`. + */ +template +TCollection Reversed(const TCollection &collection, const TAllocator &alloc) { + return TCollection(std::rbegin(collection), std::rend(collection), alloc); +} + /** * Converts a (beginning, end) pair of iterators into an iterable that can be * passed on to itertools. diff --git a/src/utils/string.hpp b/src/utils/string.hpp index a2a5d28c0..173a0fadc 100644 --- a/src/utils/string.hpp +++ b/src/utils/string.hpp @@ -1,3 +1,4 @@ +/** @file */ #pragma once #include @@ -9,284 +10,328 @@ #include #include #include +#include #include #include "utils/exceptions.hpp" 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; +/** Remove whitespace characters from the start of a string. */ +inline std::string_view LTrim(const std::string_view &s) { + size_t start = 0; + while (start < s.size() && isspace(s[start])) { + ++start; } - while (begin < end && isspace(*begin)) { - ++begin; + return std::string_view(s.data() + start, s.size() - start); +} + +/** Remove characters found in `chars` from the start of a string. */ +inline std::string_view LTrim(const std::string_view &s, + const std::string_view &chars) { + size_t start = 0; + while (start < s.size() && chars.find(s[start]) != std::string::npos) { + ++start; } - return std::string(begin, end); + return std::string_view(s.data() + start, s.size() - start); +} + +/** Remove whitespace characters from the end of a string. */ +inline std::string_view RTrim(const std::string_view &s) { + size_t count = s.size(); + while (count > static_cast(0) && isspace(s[count - 1])) { + --count; + } + return std::string_view(s.data(), count); +} + +/** Remove characters found in `chars` from the end of a string. */ +inline std::string_view RTrim(const std::string_view &s, + const std::string_view &chars) { + size_t count = s.size(); + while (count > static_cast(0) && + chars.find(s[count - 1]) != std::string::npos) { + --count; + } + return std::string_view(s.data(), count); +} + +/** Remove whitespace characters from the start and from the end of a string. */ +inline std::string_view Trim(const std::string_view &s) { + size_t start = 0; + size_t count = s.size(); + while (start < s.size() && isspace(s[start])) { + ++start; + } + while (count > start && isspace(s[count - 1])) { + --count; + } + return std::string_view(s.data() + start, count - start); +} + +/** Remove characters found in `chars` from the start and the end of `s`. */ +inline std::string_view Trim(const std::string_view &s, + const std::string_view &chars) { + size_t start = 0; + size_t count = s.size(); + while (start < s.size() && chars.find(s[start]) != std::string::npos) { + ++start; + } + while (count > start && chars.find(s[count - 1]) != std::string::npos) { + --count; + } + return std::string_view(s.data() + start, count - start); } /** - * 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 + * Lowercase all characters of a string and store the result in `out`. + * Transformation is locale independent. + * @return pointer to `out`. */ -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. - * - * @param str string that is going to be trimmed - * - * @return trimmed string - */ -inline std::string Trim(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; - } - while (end > begin && isspace(*std::prev(end))) { - --end; - } - return std::string(begin, end); -} - -/** - * Removes characters contained in chars from the start and 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 Trim(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; - } - while (end > begin && chars.find(*std::prev(end)) != std::string::npos) { - --end; - } - return std::string(begin, end); -} - -/** - * Return string with all lowercased characters (locale independent). - */ -inline std::string ToLowerCase(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), +template +std::basic_string, TAllocator> *ToLowerCase( + std::basic_string, TAllocator> *out, + const std::string_view &s) { + out->resize(s.size()); + std::transform(s.begin(), s.end(), out->begin(), [](char c) { return tolower(c); }); - return s; + return out; } /** - * Return string with all uppercased characters (locale independent). + * Lowercase all characters of a string. + * Transformation is locale independent. */ -inline std::string ToUpperCase(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), +inline std::string ToLowerCase(const std::string_view &s) { + std::string res; + ToLowerCase(&res, s); + return res; +} + +/** + * Uppercase all characters of a string and store the result in `out`. + * Transformation is locale independent. + * @return pointer to `out`. + */ +template +std::basic_string, TAllocator> *ToUpperCase( + std::basic_string, TAllocator> *out, + const std::string_view &s) { + out->resize(s.size()); + std::transform(s.begin(), s.end(), out->begin(), [](char c) { return toupper(c); }); - return s; + return out; } /** - * Join strings in vector separated by a given separator. + * Uppercase all characters of a string and store the result in `out`. + * Transformation is locale independent. */ -inline std::string Join(const std::vector &strings, - const std::string &separator) { - if (strings.size() == 0U) return ""; +inline std::string ToUpperCase(const std::string_view &s) { + std::string res; + ToUpperCase(&res, s); + return res; +} + +/** + * Join the `strings` collection separated by a given separator into `out`. + * @return pointer to `out`. + */ +template +std::basic_string, TAllocator> *Join( + std::basic_string, TAllocator> *out, + const TCollection &strings, const std::string_view &separator) { + out->clear(); + if (strings.empty()) return out; int64_t total_size = 0; for (const auto &x : strings) { total_size += x.size(); } total_size += separator.size() * (static_cast(strings.size()) - 1); - std::string s; - s.reserve(total_size); - s += strings[0]; + out->reserve(total_size); + *out += strings[0]; for (auto it = strings.begin() + 1; it != strings.end(); ++it) { - s += separator; - s += *it; + *out += separator; + *out += *it; } - return s; + return out; } /** - * Replaces all occurences of in with . + * Join the `strings` collection separated by a given separator. */ -// TODO: This could be implemented much more efficiently. -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); - } - return src; +inline std::string Join(const std::vector &strings, + const std::string_view &separator) { + std::string res; + Join(&res, strings, separator); + return res; } /** - * Split string by delimeter and return vector of results. - * - * @param src - The string to split. - * @param delimitier - The delimiter to split on. - * @param splits - The maximum number of splits. For the given value N the - * returned vector will contain at most (N + 1) elements. If given a negative - * value, all possible splits are performed. - * @return - a vector of splits. + * Replace all occurrences of `match` in `src` with `replacement`. + * @return pointer to `out`. */ -inline std::vector Split(const std::string &src, - const std::string &delimiter, - int splits = -1) { - std::vector res; - if (src.empty()) { - return res; +template +std::basic_string, TAllocator> *Replace( + std::basic_string, TAllocator> *out, + const std::string_view &src, const std::string_view &match, + const std::string_view &replacement, const TAllocator &alloc) { + // TODO: This could be implemented much more efficiently. + *out = src; + for (size_t pos = out->find(match); pos != std::string::npos; + pos = out->find(match, pos + replacement.size())) { + out->erase(pos, match.length()).insert(pos, replacement); } + return out; +} + +/** Replace all occurrences of `match` in `src` with `replacement`. */ +inline std::string Replace(const std::string_view &src, + const std::string_view &match, + const std::string_view &replacement) { + std::string res; + Replace(&res, src, match, replacement, std::allocator()); + return res; +} + +/** + * Split a string by `delimiter` with a maximum of `splits` into a vector. + * The vector will have at most `splits` + 1 elements. Negative value of + * `splits` indicates to perform all possible splits. + * @return pointer to `out`. + */ +template +std::vector *Split(std::vector *out, + const std::string_view &src, + const std::string_view &delimiter, + int splits = -1) { + out->clear(); + if (src.empty()) return out; size_t index = 0; while (splits < 0 || splits-- != 0) { auto n = src.find(delimiter, index); if (n == std::string::npos) break; - res.emplace_back(src.substr(index, n - index)); + out->emplace_back(src.substr(index, n - index)); index = n + delimiter.size(); } - - res.emplace_back(src.substr(index)); - return res; + out->emplace_back(src.substr(index)); + return out; } /** - * Split string by delimiter, from right to left, and return vector of results. - * For example, RSplit("a.b.c.", ".", 1) results in {"a.b", "c"}. - * - * @param src - The string to split. - * @param delimitier - The delimiter to split on. - * @param splits - The maximum number of splits. For the given value N the - * returned vector will contain at most (N + 1) elements. If given a negative - * value, all possible splits are performed. + * Split a string by `delimiter` with a maximum of `splits` into a vector. + * The vector will have at most `splits` + 1 elements. Negative value of + * `splits` indicates to perform all possible splits. */ -inline std::vector RSplit(const std::string &src, - const std::string &delimiter, - int splits = -1) { +inline std::vector Split(const std::string_view &src, + const std::string_view &delimiter, + int splits = -1) { std::vector res; - if (src.empty()) { - return res; - } - size_t index = src.size(); - while (splits < 0 || splits-- != 0) { - auto n = src.rfind(delimiter, index - 1); - if (n == std::string::npos) break; - res.emplace_back( - src.substr(n + delimiter.size(), index - n - delimiter.size())); - index = n; - if (n == 0) break; - } - - res.emplace_back(src.substr(0, index)); - std::reverse(res.begin(), res.end()); + Split(&res, src, delimiter, splits); return res; } /** - * Split string by whitespace and return vector of results. + * Split a string by whitespace into a vector. + * Runs of consecutive whitespace are regarded as a single delimiter. + * Additionally, the result will not contain empty strings at the start or end + * as if the string was trimmed before splitting. + * @return pointer to `out`. + */ +template +std::vector *Split(std::vector *out, + const std::string_view &src) { + out->clear(); + if (src.empty()) return out; + // TODO: Investigate how much regex allocate and perhaps replace with custom + // solution doing no allocations. + std::regex not_whitespace("[^\\s]+"); + auto matches_begin = + std::cregex_iterator(src.data(), src.data() + src.size(), not_whitespace); + auto matches_end = std::cregex_iterator(); + out->reserve(std::distance(matches_begin, matches_end)); + for (auto match = matches_begin; match != matches_end; ++match) { + std::string_view match_view(&src[match->position()], match->length()); + out->emplace_back(match_view); + } + return out; +} + +/** + * Split a string by whitespace into a vector. * Runs of consecutive whitespace are regarded as a single delimiter. * Additionally, the result will not contain empty strings at the start or end * as if the string was trimmed before splitting. */ -inline std::vector Split(const std::string &src) { - if (src.empty()) { - return {}; - } - std::regex not_whitespace("[^\\s]+"); - auto matches_begin = - std::sregex_iterator(src.begin(), src.end(), not_whitespace); - auto matches_end = std::sregex_iterator(); +inline std::vector Split(const std::string_view &src) { std::vector res; - res.reserve(std::distance(matches_begin, matches_end)); - for (auto match = matches_begin; match != matches_end; ++match) { - res.emplace_back(match->str()); - } + Split(&res, src); return res; } /** - * Parse double using classic locale, throws BasicException if it wasn't able to - * parse whole string. + * Like `Split` but string is processed from right to left. + * For example, RSplit("a.b.c.", ".", 1) results in {"a.b", "c"}. + * The returned vector and its elements use `std::allocator<>`. The vector will + * have at most `splits` + 1 elements. Negative value of `splits` indicates to + * perform all possible splits. + * @return pointer to `out`. */ -inline double ParseDouble(const std::string &s) { +template +std::vector *RSplit(std::vector *out, + const std::string_view &src, + const std::string_view &delimiter, + int splits = -1) { + out->clear(); + if (src.empty()) return out; + size_t index = src.size(); + while (splits < 0 || splits-- != 0) { + auto n = src.rfind(delimiter, index - 1); + if (n == std::string::npos) break; + out->emplace_back( + src.substr(n + delimiter.size(), index - n - delimiter.size())); + index = n; + if (n == 0) break; + } + out->emplace_back(src.substr(0, index)); + std::reverse(out->begin(), out->end()); + return out; +} + +/** + * Like `Split` but string is processed from right to left. + * For example, RSplit("a.b.c.", ".", 1) results in {"a.b", "c"}. + * The returned vector and its elements use `std::allocator<>`. The vector will + * have at most `splits` + 1 elements. Negative value of `splits` indicates to + * perform all possible splits. + */ +inline std::vector RSplit(const std::string_view &src, + const std::string_view &delimiter, + int splits = -1) { + std::vector res; + RSplit(&res, src, delimiter, splits); + return res; +} + +/** + * Parse a double floating point value from a string using classic locale. + * Note, the current implementation copies the given string which may perform a + * heap allocation if the string is big enough. + * + * @throw BasicException if unable to parse the whole string. + */ +inline double ParseDouble(const std::string_view &s) { // stod would be nicer but it uses current locale so we shouldn't use it. double t = 0.0; - std::istringstream iss(s); + // NOTE: Constructing std::istringstream will make a copy of the string, which + // may make a heap allocation if string is large enough. There is no + // std::istringstream constructor accepting a custom allocator. We could pass + // a std::basic_string with a custom allocator, but std::istringstream will + // probably invoke + // std::allocator_traits<>::select_on_container_copy_construction which + // doesn't really help as most allocators default to global new/delete + // allocator. + std::istringstream iss(std::string(s.data(), s.size())); iss.imbue(std::locale::classic()); iss >> t; if (iss.fail() || !iss.eof()) { @@ -295,25 +340,21 @@ inline double ParseDouble(const std::string &s) { return t; } -/** - * Checks if the given string `s` ends with the given `suffix`. - */ -inline bool EndsWith(const std::string &s, const std::string &suffix) { +/** Check if the given string `s` ends with the given `suffix`. */ +inline bool EndsWith(const std::string_view &s, + const std::string_view &suffix) { return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), std::string::npos, suffix) == 0; } -/** - * Checks if the given string `s` starts with the given `prefix`. - */ -inline bool StartsWith(const std::string &s, const std::string &prefix) { +/** Check if the given string `s` starts with the given `prefix`. */ +inline bool StartsWith(const std::string_view &s, + const std::string_view &prefix) { return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0; } -/** - * Case-insensitive string comparison. - */ -inline bool IEquals(const std::string &lhs, const std::string &rhs) { +/** Perform case-insensitive string equality test. */ +inline bool IEquals(const std::string_view &lhs, const std::string_view &rhs) { if (lhs.size() != rhs.size()) return false; for (size_t i = 0; i < lhs.size(); ++i) { if (tolower(lhs[i]) != tolower(rhs[i])) return false; @@ -321,8 +362,14 @@ inline bool IEquals(const std::string &lhs, const std::string &rhs) { return true; } -/** Creates a random alphanumeric string of the given length. */ -inline std::string RandomString(size_t length) { +/** + * Create a random alphanumeric string of the given length. + * @return pointer to `out`. + */ +template +std::basic_string, TAllocator> *RandomString( + std::basic_string, TAllocator> *out, + size_t length) { static const char charset[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -330,37 +377,57 @@ inline std::string RandomString(size_t length) { static thread_local std::mt19937 pseudo_rand_gen{std::random_device{}()}; static thread_local std::uniform_int_distribution rand_dist{ 0, strlen(charset) - 1}; - std::string str(length, 0); + out->resize(length); for (size_t i = 0; i < length; ++i) - str[i] = charset[rand_dist(pseudo_rand_gen)]; - return str; + (*out)[i] = charset[rand_dist(pseudo_rand_gen)]; + return out; } -/// Escapes all whitespace and quotation characters to produce a string -/// which can be used as a string literal. -inline std::string Escape(const std::string &src) { - std::string ret; - ret.reserve(src.size() + 2); - ret.append(1, '"'); +/** Create a random alphanumeric string of the given length. */ +inline std::string RandomString(size_t length) { + std::string res; + RandomString(&res, length); + return res; +} + +/** + * Escape all whitespace and quotation characters in the given string. + * @return pointer to `out`. + */ +template +std::basic_string, TAllocator> *Escape( + std::basic_string, TAllocator> *out, + const std::string_view &src) { + out->clear(); + out->reserve(src.size() + 2); + out->append(1, '"'); for (auto c : src) { if (c == '\\' || c == '\'' || c == '"') { - ret.append(1, '\\'); - ret.append(1, c); + out->append(1, '\\'); + out->append(1, c); } else if (c == '\b') { - ret.append("\\b"); + out->append("\\b"); } else if (c == '\f') { - ret.append("\\f"); + out->append("\\f"); } else if (c == '\n') { - ret.append("\\n"); + out->append("\\n"); } else if (c == '\r') { - ret.append("\\r"); + out->append("\\r"); } else if (c == '\t') { - ret.append("\\t"); + out->append("\\t"); } else { - ret.append(1, c); + out->append(1, c); } } - ret.append(1, '"'); - return ret; + out->append(1, '"'); + return out; } + +/** Escape all whitespace and quotation characters in the given string. */ +inline std::string Escape(const std::string_view &src) { + std::string res; + Escape(&res, src); + return res; +} + } // namespace utils diff --git a/tests/manual/ha_proxy.cpp b/tests/manual/ha_proxy.cpp index 61e3fb7e0..e986ee432 100644 --- a/tests/manual/ha_proxy.cpp +++ b/tests/manual/ha_proxy.cpp @@ -158,8 +158,9 @@ std::vector GetEndpoints() { for (const auto &endpoint : utils::Split(FLAGS_endpoints, ",")) { auto split = utils::Split(utils::Trim(endpoint), ":"); CHECK(split.size() == 2) << "Invalid endpoint!"; - ret.emplace_back(io::network::ResolveHostname(utils::Trim(split[0])), - static_cast(std::stoi(utils::Trim(split[1])))); + ret.emplace_back( + io::network::ResolveHostname(std::string(utils::Trim(split[0]))), + static_cast(std::stoi(std::string(utils::Trim(split[1]))))); } return ret; } diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp index 558221f20..4363d3588 100644 --- a/tests/manual/interactive_planning.cpp +++ b/tests/manual/interactive_planning.cpp @@ -489,7 +489,7 @@ auto MakeLogicalPlans(query::CypherQuery *query, query::AstStorage &ast, } void RunInteractivePlanning(database::GraphDbAccessor *dba) { - auto in_db_filename = utils::Trim(FLAGS_load_mock_db_file); + std::string in_db_filename(utils::Trim(FLAGS_load_mock_db_file)); if (!in_db_filename.empty() && !std::filesystem::exists(in_db_filename)) { std::cerr << "File '" << in_db_filename << "' does not exist!" << std::endl; std::exit(EXIT_FAILURE); @@ -529,7 +529,7 @@ void RunInteractivePlanning(database::GraphDbAccessor *dba) { std::cout << "Error: " << e.what() << std::endl; } } - auto db_filename = utils::Trim(FLAGS_save_mock_db_file); + std::string db_filename(utils::Trim(FLAGS_save_mock_db_file)); if (!db_filename.empty()) { std::ofstream db_file(db_filename); interactive_db.Save(db_file); diff --git a/tests/manual/snapshot_generation/graph_state.hpp b/tests/manual/snapshot_generation/graph_state.hpp index 893cfd718..8d1c5ea5e 100644 --- a/tests/manual/snapshot_generation/graph_state.hpp +++ b/tests/manual/snapshot_generation/graph_state.hpp @@ -171,7 +171,7 @@ GraphState BuildFromConfig(int num_workers, const nlohmann::json &config) { GraphState state(num_workers); for (const auto &index : GetWithDefault(config, "indexes", {})) { - auto index_parts = utils::Split(index, "."); + auto index_parts = utils::Split(std::string(index), "."); CHECK(index_parts.size() == 2) << "Index format should be Label.Property"; state.CreateIndex(index_parts[0], index_parts[1]); } diff --git a/tests/unit/utils_string.cpp b/tests/unit/utils_string.cpp index 14051d47c..712794116 100644 --- a/tests/unit/utils_string.cpp +++ b/tests/unit/utils_string.cpp @@ -12,6 +12,7 @@ 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("run()", "rn"), "un()"); EXPECT_EQ(LTrim(""), ""); } @@ -19,6 +20,7 @@ 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("run()", "u()"), "run"); EXPECT_EQ(RTrim(""), ""); } diff --git a/tools/src/mg_client/main.cpp b/tools/src/mg_client/main.cpp index 7b7694588..52e66969d 100644 --- a/tools/src/mg_client/main.cpp +++ b/tools/src/mg_client/main.cpp @@ -344,7 +344,7 @@ static std::optional GetQuery() { PrintHelp(); return ""; } else { - EchoFailure("Unsupported command", trimmed_line); + EchoFailure("Unsupported command", std::string(trimmed_line)); PrintHelp(); return ""; } diff --git a/tools/src/mg_import_csv/main.cpp b/tools/src/mg_import_csv/main.cpp index c32f60ba1..29c2da7e5 100644 --- a/tools/src/mg_import_csv/main.cpp +++ b/tools/src/mg_import_csv/main.cpp @@ -258,7 +258,7 @@ communication::bolt::Value StringToValue(const std::string &str, std::vector array; array.reserve(elems.size()); for (const auto &elem : elems) { - array.push_back(convert(utils::Trim(elem), elem_type)); + array.push_back(convert(std::string(utils::Trim(elem)), elem_type)); } return array; } @@ -279,7 +279,7 @@ void WriteNodeRow( std::map properties; for (int i = 0; i < row.size(); ++i) { const auto &field = fields[i]; - auto value = utils::Trim(row[i]); + std::string value(utils::Trim(row[i])); if (utils::StartsWith(field.type, "id")) { CHECK(!id) << "Only one node ID must be specified"; NodeId node_id{value, GetIdSpace(field.type)}; @@ -340,7 +340,7 @@ void WriteRelationshipsRow( std::map properties; for (int i = 0; i < row.size(); ++i) { const auto &field = fields[i]; - auto value = utils::Trim(row[i]); + std::string value(utils::Trim(row[i])); if (utils::StartsWith(field.type, "start_id")) { CHECK(!start_id) << "Only one node ID must be specified"; NodeId node_id{value, GetIdSpace(field.type)}; @@ -460,7 +460,7 @@ std::string GetOutputPath() { // other flags which are defined in this file. LoadConfig(); // Without durability_directory, we have to require 'out' flag. - auto durability_dir = utils::Trim(FLAGS_durability_directory); + std::string durability_dir(utils::Trim(FLAGS_durability_directory)); if (durability_dir.empty()) LOG(FATAL) << "Unable to determine snapshot output location. Please, " "provide the 'out' flag";