Skip to content

Commit

Permalink
add concat & concat_ws (#2540)
Browse files Browse the repository at this point in the history
* add concat & concat_ws

* format

* address comment

Co-authored-by: Yee <2520865+yixinglu@users.noreply.github.com>
  • Loading branch information
nevermore3 and yixinglu authored Aug 22, 2021
1 parent 35fddd9 commit 312cfef
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 2 deletions.
94 changes: 94 additions & 0 deletions src/common/function/FunctionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ FunctionManager &FunctionManager::instance() {
return instance;
}

std::unordered_map<std::string, Value::Type> FunctionManager::variadicFunReturnType_ = {
{"concat", Value::Type::STRING},
{"concat_ws", Value::Type::STRING},
{"cos_similarity", Value::Type::FLOAT},
};

std::unordered_map<std::string, std::vector<TypeSignature>> FunctionManager::typeSignature_ = {
{"abs",
{TypeSignature({Value::Type::INT}, Value::Type::INT),
Expand Down Expand Up @@ -303,6 +309,9 @@ StatusOr<Value::Type> FunctionManager::getReturnType(const std::string &funcName
const std::vector<Value::Type> &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());
Expand Down Expand Up @@ -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<std::string> 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
Expand Down
3 changes: 3 additions & 0 deletions src/common/function/FunctionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class FunctionManager final {

static std::unordered_map<std::string, std::vector<TypeSignature>> typeSignature_;

// the return type of a variadic function
static std::unordered_map<std::string, Value::Type> variadicFunReturnType_;

std::unordered_map<std::string, FunctionAttributes> functions_;
};

Expand Down
22 changes: 22 additions & 0 deletions src/common/function/test/FunctionManagerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ TEST_F(FunctionManagerTest, testNull) {
TEST_FUNCTION(substr, std::vector<Value>({"hello", 2, Value::kNullValue}), Value::kNullBadType);
TEST_FUNCTION(substr, std::vector<Value>({"hello", -1, 10}), Value::kNullBadData);
TEST_FUNCTION(substr, std::vector<Value>({"hello", 1, -2}), Value::kNullBadData);
TEST_FUNCTION(concat, std::vector<Value>({"hello", Value::kNullValue, -2}), Value::kNullValue);
TEST_FUNCTION(concat, args_["nullvalue"], Value::kNullValue);
TEST_FUNCTION(concat_ws, std::vector<Value>({Value::kNullValue, 1, 2}), Value::kNullValue);
TEST_FUNCTION(concat_ws, std::vector<Value>({1, 1, 2}), Value::kNullValue);
}

TEST_F(FunctionManagerTest, functionCall) {
Expand Down Expand Up @@ -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<Value>({"hello", 1, "world"}), "hello1world");
TEST_FUNCTION(concat, std::vector<Value>({true, 2, date}), "true22021-10-31");
TEST_FUNCTION(concat, std::vector<Value>({true, dateTime}), "true2021-10-31T08:05:34.29");
TEST_FUNCTION(concat, std::vector<Value>({2.3, time}), "2.309:39:21.000012");
TEST_FUNCTION(concat, args_["two"], "24");
TEST_FUNCTION(concat_ws, std::vector<Value>({",", 1}), "1");
TEST_FUNCTION(concat_ws, std::vector<Value>({"@", 1, "world"}), "1@world");
TEST_FUNCTION(concat_ws,
std::vector<Value>({"AB", 1, true, Value::kNullValue, "world"}),
"1ABtrueABworld");
TEST_FUNCTION(concat_ws,
std::vector<Value>({".", 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);
Expand Down
85 changes: 83 additions & 2 deletions tests/tck/features/expression/FunctionCall.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |

0 comments on commit 312cfef

Please sign in to comment.