-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add str_to_map Spark function #5842
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
#pragma once | ||
|
||
#include "folly/container/F14Set.h" | ||
#include "velox/functions/Udf.h" | ||
|
||
namespace facebook::velox::functions::sparksql { | ||
|
||
template <typename T> | ||
struct StringToMapFunction { | ||
VELOX_DEFINE_FUNCTION_TYPES(T); | ||
|
||
// Results refer to strings in the first argument. | ||
static constexpr int32_t reuse_strings_from_arg = 0; | ||
|
||
void call( | ||
out_type<Map<Varchar, Varchar>>& out, | ||
const arg_type<Varchar>& input, | ||
const arg_type<Varchar>& entryDelimiter, | ||
const arg_type<Varchar>& keyValueDelimiter) { | ||
VELOX_USER_CHECK_EQ( | ||
entryDelimiter.size(), 1, "entryDelimiter's size should be 1."); | ||
VELOX_USER_CHECK_EQ( | ||
keyValueDelimiter.size(), 1, "keyValueDelimiter's size should be 1."); | ||
|
||
callImpl( | ||
out, | ||
toStringView(input), | ||
toStringView(entryDelimiter), | ||
toStringView(keyValueDelimiter)); | ||
} | ||
|
||
private: | ||
static std::string_view toStringView(const arg_type<Varchar>& input) { | ||
return std::string_view(input.data(), input.size()); | ||
} | ||
|
||
void callImpl( | ||
out_type<Map<Varchar, Varchar>>& out, | ||
std::string_view input, | ||
std::string_view entryDelimiter, | ||
std::string_view keyValueDelimiter) const { | ||
size_t pos = 0; | ||
folly::F14FastSet<std::string_view> keys; | ||
|
||
auto nextEntryPos = input.find(entryDelimiter, pos); | ||
while (nextEntryPos != std::string::npos) { | ||
processEntry( | ||
out, | ||
std::string_view(input.data() + pos, nextEntryPos - pos), | ||
keyValueDelimiter, | ||
keys); | ||
|
||
pos = nextEntryPos + 1; | ||
nextEntryPos = input.find(entryDelimiter, pos); | ||
} | ||
|
||
processEntry( | ||
out, | ||
std::string_view(input.data() + pos, input.size() - pos), | ||
keyValueDelimiter, | ||
keys); | ||
} | ||
|
||
void processEntry( | ||
out_type<Map<Varchar, Varchar>>& out, | ||
std::string_view entry, | ||
std::string_view keyValueDelimiter, | ||
folly::F14FastSet<std::string_view>& keys) const { | ||
const auto delimiterPos = entry.find(keyValueDelimiter, 0); | ||
// Allows keyValue delimiter not found. | ||
if (delimiterPos == std::string::npos) { | ||
out.add_null().setNoCopy(StringView(entry)); | ||
return; | ||
} | ||
const auto key = std::string_view(entry.data(), delimiterPos); | ||
VELOX_USER_CHECK( | ||
keys.insert(key).second, "Duplicate keys are not allowed: '{}'.", key); | ||
const auto value = StringView( | ||
entry.data() + delimiterPos + 1, entry.size() - delimiterPos - 1); | ||
|
||
auto [keyWriter, valueWriter] = out.add_item(); | ||
keyWriter.setNoCopy(StringView(key)); | ||
valueWriter.setNoCopy(value); | ||
} | ||
}; | ||
|
||
} // namespace facebook::velox::functions::sparksql |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
#include "velox/common/base/tests/GTestUtils.h" | ||
#include "velox/functions/sparksql/tests/SparkFunctionBaseTest.h" | ||
|
||
using namespace facebook::velox::test; | ||
|
||
namespace facebook::velox::functions::sparksql::test { | ||
namespace { | ||
class StringToMapTest : public SparkFunctionBaseTest { | ||
protected: | ||
VectorPtr evaluateStringToMap(const std::vector<StringView>& inputs) { | ||
const std::string expr = | ||
fmt::format("str_to_map(c0, '{}', '{}')", inputs[1], inputs[2]); | ||
return evaluate<MapVector>( | ||
expr, makeRowVector({makeFlatVector<StringView>({inputs[0]})})); | ||
} | ||
|
||
void testStringToMap( | ||
const std::vector<StringView>& inputs, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why using vector if only one string view is expected? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just to make map vector from a vector whose element is a pair of key/value. |
||
const std::vector<std::pair<StringView, std::optional<StringView>>>& | ||
expect) { | ||
auto result = evaluateStringToMap(inputs); | ||
auto expectVector = makeMapVector<StringView, StringView>({expect}); | ||
assertEqualVectors(result, expectVector); | ||
} | ||
}; | ||
|
||
TEST_F(StringToMapTest, basic) { | ||
testStringToMap( | ||
{"a:1,b:2,c:3", ",", ":"}, {{"a", "1"}, {"b", "2"}, {"c", "3"}}); | ||
testStringToMap({"a: ,b:2", ",", ":"}, {{"a", " "}, {"b", "2"}}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe also add test case for |
||
testStringToMap({"a:,b:2", ",", ":"}, {{"a", ""}, {"b", "2"}}); | ||
testStringToMap({"", ",", ":"}, {{"", std::nullopt}}); | ||
testStringToMap({"a", ",", ":"}, {{"a", std::nullopt}}); | ||
testStringToMap( | ||
{"a=1,b=2,c=3", ",", "="}, {{"a", "1"}, {"b", "2"}, {"c", "3"}}); | ||
testStringToMap({"", ",", "="}, {{"", std::nullopt}}); | ||
testStringToMap( | ||
{"a::1,b::2,c::3", ",", "c"}, | ||
{{"a::1", std::nullopt}, {"b::2", std::nullopt}, {"", "::3"}}); | ||
testStringToMap( | ||
{"a:1_b:2_c:3", "_", ":"}, {{"a", "1"}, {"b", "2"}, {"c", "3"}}); | ||
|
||
// Same delimiters. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty line above. |
||
testStringToMap( | ||
{"a:1,b:2,c:3", ",", ","}, | ||
{{"a:1", std::nullopt}, {"b:2", std::nullopt}, {"c:3", std::nullopt}}); | ||
testStringToMap( | ||
{"a:1_b:2_c:3", "_", "_"}, | ||
{{"a:1", std::nullopt}, {"b:2", std::nullopt}, {"c:3", std::nullopt}}); | ||
|
||
// Exception for illegal delimiters. | ||
// Empty string is used. | ||
VELOX_ASSERT_THROW( | ||
evaluateStringToMap({"a:1,b:2", "", ":"}), | ||
"entryDelimiter's size should be 1."); | ||
VELOX_ASSERT_THROW( | ||
evaluateStringToMap({"a:1,b:2", ",", ""}), | ||
"keyValueDelimiter's size should be 1."); | ||
// Delimiter's length > 1. | ||
VELOX_ASSERT_THROW( | ||
evaluateStringToMap({"a:1,b:2", ";;", ":"}), | ||
"entryDelimiter's size should be 1."); | ||
VELOX_ASSERT_THROW( | ||
evaluateStringToMap({"a:1,b:2", ",", "::"}), | ||
"keyValueDelimiter's size should be 1."); | ||
// Unicode character is used. | ||
VELOX_ASSERT_THROW( | ||
evaluateStringToMap({"a:1,b:2", "å", ":"}), | ||
"entryDelimiter's size should be 1."); | ||
VELOX_ASSERT_THROW( | ||
evaluateStringToMap({"a:1,b:2", ",", "æ"}), | ||
"keyValueDelimiter's size should be 1."); | ||
|
||
// Exception for duplicated keys. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty line above. |
||
VELOX_ASSERT_THROW( | ||
evaluateStringToMap({"a:1,b:2,a:3", ",", ":"}), | ||
"Duplicate keys are not allowed: 'a'."); | ||
VELOX_ASSERT_THROW( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you also check for empty, non-ASCII, non-singlechar delimiters? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just added some test cases. Thanks! |
||
evaluateStringToMap({":1,:2", ",", ":"}), | ||
"Duplicate keys are not allowed: ''."); | ||
} | ||
} // namespace | ||
} // namespace facebook::velox::functions::sparksql::test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to describe this behavior in the documentation.