diff --git a/src/common/function/FunctionManager.cpp b/src/common/function/FunctionManager.cpp index 8d5bf928db0..cd068125e6c 100644 --- a/src/common/function/FunctionManager.cpp +++ b/src/common/function/FunctionManager.cpp @@ -29,6 +29,12 @@ FunctionManager &FunctionManager::instance() { return instance; } +std::unordered_map FunctionManager::variadicFunReturnType_ = { + {"concat", Value::Type::STRING}, + {"concat_ws", Value::Type::STRING}, + {"cos_similarity", Value::Type::FLOAT}, +}; + std::unordered_map> FunctionManager::typeSignature_ = { {"abs", {TypeSignature({Value::Type::INT}, Value::Type::INT), @@ -303,6 +309,9 @@ StatusOr FunctionManager::getReturnType(const std::string &funcName const std::vector &argsType) { auto func = funcName; std::transform(func.begin(), func.end(), func.begin(), ::tolower); + if (variadicFunReturnType_.find(func) != variadicFunReturnType_.end()) { + return variadicFunReturnType_[func]; + } auto iter = typeSignature_.find(func); if (iter == typeSignature_.end()) { return Status::Error("Function `%s' not defined", funcName.c_str()); @@ -2145,6 +2154,91 @@ FunctionManager::FunctionManager() { return ds.rows[rowIndex][colIndex]; }; } + { + auto &attr = functions_["concat"]; + attr.minArity_ = 1; + attr.maxArity_ = INT64_MAX; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + std::stringstream os; + for (size_t i = 0; i < args.size(); ++i) { + switch (args[i].get().type()) { + case Value::Type::NULLVALUE: { + return Value::kNullValue; + } + case Value::Type::BOOL: { + os << (args[i].get().getBool() ? "true" : "false"); + break; + } + case Value::Type::INT: { + os << args[i].get().getInt(); + break; + } + case Value::Type::FLOAT: { + os << args[i].get().getFloat(); + break; + } + case Value::Type::STRING: { + os << args[i].get().getStr(); + break; + } + case Value::Type::DATETIME: { + os << args[i].get().getDateTime(); + break; + } + case Value::Type::DATE: { + os << args[i].get().getDate(); + break; + } + case Value::Type::TIME: { + os << args[i].get().getTime(); + break; + } + default: { + return Value::kNullBadData; + } + } + } + return os.str(); + }; + } + { + auto &attr = functions_["concat_ws"]; + attr.minArity_ = 2; + attr.maxArity_ = INT64_MAX; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (args[0].get().isNull() || !args[0].get().isStr()) { + return Value::kNullValue; + } + std::vector result; + result.reserve(args.size() - 1); + for (size_t i = 1; i < args.size(); ++i) { + switch (args[i].get().type()) { + case Value::Type::NULLVALUE: { + continue; + } + case Value::Type::BOOL: + case Value::Type::INT: + case Value::Type::FLOAT: + case Value::Type::DATE: + case Value::Type::DATETIME: + case Value::Type::TIME: { + result.emplace_back(args[i].get().toString()); + break; + } + case Value::Type::STRING: { + result.emplace_back(args[i].get().getStr()); + break; + } + default: { + return Value::kNullBadData; + } + } + } + return folly::join(args[0].get().getStr(), result); + }; + } } // NOLINT // static diff --git a/src/common/function/FunctionManager.h b/src/common/function/FunctionManager.h index 9936e989cd3..9285c96d7bf 100644 --- a/src/common/function/FunctionManager.h +++ b/src/common/function/FunctionManager.h @@ -89,6 +89,9 @@ class FunctionManager final { static std::unordered_map> typeSignature_; + // the return type of a variadic function + static std::unordered_map variadicFunReturnType_; + std::unordered_map functions_; }; diff --git a/src/common/function/test/FunctionManagerTest.cpp b/src/common/function/test/FunctionManagerTest.cpp index 8f60e1121c3..817eb6ee15e 100644 --- a/src/common/function/test/FunctionManagerTest.cpp +++ b/src/common/function/test/FunctionManagerTest.cpp @@ -201,6 +201,10 @@ TEST_F(FunctionManagerTest, testNull) { TEST_FUNCTION(substr, std::vector({"hello", 2, Value::kNullValue}), Value::kNullBadType); TEST_FUNCTION(substr, std::vector({"hello", -1, 10}), Value::kNullBadData); TEST_FUNCTION(substr, std::vector({"hello", 1, -2}), Value::kNullBadData); + TEST_FUNCTION(concat, std::vector({"hello", Value::kNullValue, -2}), Value::kNullValue); + TEST_FUNCTION(concat, args_["nullvalue"], Value::kNullValue); + TEST_FUNCTION(concat_ws, std::vector({Value::kNullValue, 1, 2}), Value::kNullValue); + TEST_FUNCTION(concat_ws, std::vector({1, 1, 2}), Value::kNullValue); } TEST_F(FunctionManagerTest, functionCall) { @@ -308,6 +312,24 @@ TEST_F(FunctionManagerTest, functionCall) { TEST_FUNCTION(toString, args_["datetime"], "1984-10-11T12:31:14.341"); TEST_FUNCTION(toString, args_["nullvalue"], Value::kNullValue); } + { + Time time(9, 39, 21, 12); + Date date(2021, 10, 31); + DateTime dateTime(2021, 10, 31, 8, 5, 34, 29); + TEST_FUNCTION(concat, std::vector({"hello", 1, "world"}), "hello1world"); + TEST_FUNCTION(concat, std::vector({true, 2, date}), "true22021-10-31"); + TEST_FUNCTION(concat, std::vector({true, dateTime}), "true2021-10-31T08:05:34.29"); + TEST_FUNCTION(concat, std::vector({2.3, time}), "2.309:39:21.000012"); + TEST_FUNCTION(concat, args_["two"], "24"); + TEST_FUNCTION(concat_ws, std::vector({",", 1}), "1"); + TEST_FUNCTION(concat_ws, std::vector({"@", 1, "world"}), "1@world"); + TEST_FUNCTION(concat_ws, + std::vector({"AB", 1, true, Value::kNullValue, "world"}), + "1ABtrueABworld"); + TEST_FUNCTION(concat_ws, + std::vector({".", 1, true, Value::kNullValue, "world", time}), + "1.true.world.09:39:21.000012"); + } { TEST_FUNCTION(toBoolean, args_["int"], Value::kNullBadType); TEST_FUNCTION(toBoolean, args_["float"], Value::kNullBadType); diff --git a/tests/tck/features/expression/FunctionCall.feature b/tests/tck/features/expression/FunctionCall.feature index aac914b0cb8..17ba3613233 100644 --- a/tests/tck/features/expression/FunctionCall.feature +++ b/tests/tck/features/expression/FunctionCall.feature @@ -20,8 +20,7 @@ Feature: Function Call Expression Scenario: date related When executing query: """ - YIELD timestamp("2000-10-10T10:00:00") AS a, date() AS b, time() AS c, - datetime() AS d + YIELD timestamp("2000-10-10T10:00:00") AS a, date() AS b, time() AS c, datetime() AS d """ Then the result should be, in any order: | a | b | c | d | @@ -40,3 +39,85 @@ Feature: Function Call Expression Then the result should be, in any order: | dt | | '2019-03-02T22:00:30.000000' | + + Scenario: concat + When executing query: + """ + GO FROM "Tim Duncan" over like YIELD concat(like._src, $^.player.age, $$.player.name, like.likeness) AS A + """ + Then the result should be, in any order: + | A | + | "Tim Duncan42Manu Ginobili95" | + | "Tim Duncan42Tony Parker95" | + When executing query: + """ + GO FROM "Tim Duncan" over like YIELD concat(like._src, $^.player.age, null, like.likeness) AS A + """ + Then the result should be, in any order: + | A | + | NULL | + | NULL | + When executing query: + """ + MATCH (a:player)-[b:serve]-(c:team{name: "Lakers"}) + WHERE a.age > 45 + RETURN concat(a.name,c.name) + """ + Then the result should be, in any order: + | concat(a.name,c.name) | + | "Shaquile O'NealLakers" | + When executing query: + """ + MATCH (a:player)-[b:serve]-(c:team{name: "Lakers"}) + WHERE a.age > 45 + RETURN concat(a.name, "hello") + """ + Then the result should be, in any order: + | concat(a.name,"hello") | + | "Shaquile O'Nealhello" | + + Scenario: concat_ws + When executing query: + """ + GO FROM "Tim Duncan" over like YIELD concat_ws("-",like._src, $^.player.age, $$.player.name, like.likeness) AS A + """ + Then the result should be, in any order: + | A | + | "Tim Duncan-42-Manu Ginobili-95" | + | "Tim Duncan-42-Tony Parker-95" | + When executing query: + """ + MATCH (a:player)-[b:serve]-(c:team{name: "Lakers"}) + WHERE a.age > 45 + RETURN concat_ws("@",a.name, "hello", b.likeness, c.name) as result + """ + Then the result should be, in any order: + | result | + | "Shaquile O'Neal@hello@Lakers" | + When executing query: + """ + MATCH (a:player)-[b:serve]-(c:team{name: "Lakers"}) + WHERE a.age > 45 + RETURN concat_ws("@",a.name, NULL, "hello", b.likeness, c.name) as result + """ + Then the result should be, in any order: + | result | + | "Shaquile O'Neal@hello@Lakers" | + When executing query: + """ + MATCH (a:player)-[b:serve]-(c:team{name: "Lakers"}) + WHERE a.age > 45 + RETURN concat_ws(1,a.name, NULL, "hello", b.likeness, c.name) as result + """ + Then the result should be, in any order: + | result | + | NULL | + When executing query: + """ + MATCH (a:player)-[b:serve]-(c:team{name: "Lakers"}) + WHERE a.age > 45 + RETURN concat_ws(NULL ,a.name, NULL, "hello", b.likeness, c.name) as result + """ + Then the result should be, in any order: + | result | + | NULL |