Skip to content

Commit ad154d2

Browse files
Crystrixjvictorhuguenin
authored andcommitted
ARROW-11960: [C++][Gandiva] Support escape in LIKE
Add gdv_fn_like_utf8_utf8_int8 function in Gandiva to support escape char in LIKE. An escape char is stored in an int8 type which is compatible with char type in C++. Closes apache#9700 from Crystrix/arrow-11960 Authored-by: crystrix <chenxi.li@live.com> Signed-off-by: Sutou Kouhei <kou@clear-code.com> (cherry picked from commit ca66567)
1 parent 86c72de commit ad154d2

7 files changed

Lines changed: 197 additions & 4 deletions

File tree

cpp/src/gandiva/function_registry_string.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ std::vector<NativeFunction> GetStringFunctionRegistry() {
124124
kResultNullIfNull, "gdv_fn_like_utf8_utf8",
125125
NativeFunction::kNeedsFunctionHolder),
126126

127+
NativeFunction("like", {}, DataTypeVector{utf8(), utf8(), utf8()}, boolean(),
128+
kResultNullIfNull, "gdv_fn_like_utf8_utf8_utf8",
129+
NativeFunction::kNeedsFunctionHolder),
130+
127131
NativeFunction("ltrim", {}, DataTypeVector{utf8(), utf8()}, utf8(),
128132
kResultNullIfNull, "ltrim_utf8_utf8", NativeFunction::kNeedsContext),
129133

cpp/src/gandiva/gdv_function_stubs.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ bool gdv_fn_like_utf8_utf8(int64_t ptr, const char* data, int data_len,
4545
return (*holder)(std::string(data, data_len));
4646
}
4747

48+
bool gdv_fn_like_utf8_utf8_utf8(int64_t ptr, const char* data, int data_len,
49+
const char* pattern, int pattern_len,
50+
const char* escape_char, int escape_char_len) {
51+
gandiva::LikeHolder* holder = reinterpret_cast<gandiva::LikeHolder*>(ptr);
52+
return (*holder)(std::string(data, data_len));
53+
}
54+
4855
double gdv_fn_random(int64_t ptr) {
4956
gandiva::RandomGeneratorHolder* holder =
5057
reinterpret_cast<gandiva::RandomGeneratorHolder*>(ptr);
@@ -732,6 +739,19 @@ void ExportedStubFunctions::AddMappings(Engine* engine) const {
732739
types->i1_type() /*return_type*/, args,
733740
reinterpret_cast<void*>(gdv_fn_like_utf8_utf8));
734741

742+
// gdv_fn_like_utf8_utf8_utf8
743+
args = {types->i64_type(), // int64_t ptr
744+
types->i8_ptr_type(), // const char* data
745+
types->i32_type(), // int data_len
746+
types->i8_ptr_type(), // const char* pattern
747+
types->i32_type(), // int pattern_len
748+
types->i8_ptr_type(), // const char* escape_char
749+
types->i32_type()}; // int escape_char_len
750+
751+
engine->AddGlobalMappingForFunc("gdv_fn_like_utf8_utf8_utf8",
752+
types->i1_type() /*return_type*/, args,
753+
reinterpret_cast<void*>(gdv_fn_like_utf8_utf8_utf8));
754+
735755
// gdv_fn_to_date_utf8_utf8
736756
args = {types->i64_type(), // int64_t execution_context
737757
types->i64_type(), // int64_t holder_ptr

cpp/src/gandiva/gdv_function_stubs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ using gdv_day_time_interval = int64_t;
4646
bool gdv_fn_like_utf8_utf8(int64_t ptr, const char* data, int data_len,
4747
const char* pattern, int pattern_len);
4848

49+
bool gdv_fn_like_utf8_utf8_utf8(int64_t ptr, const char* data, int data_len,
50+
const char* pattern, int pattern_len,
51+
const char* escape_char, int escape_char_len);
52+
4953
int64_t gdv_fn_to_date_utf8_utf8_int32(int64_t context, int64_t ptr, const char* data,
5054
int data_len, bool in1_validity,
5155
const char* pattern, int pattern_len,

cpp/src/gandiva/like_holder.cc

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ static bool IsArrowStringLiteral(arrow::Type::type type) {
6767
}
6868

6969
Status LikeHolder::Make(const FunctionNode& node, std::shared_ptr<LikeHolder>* holder) {
70-
ARROW_RETURN_IF(node.children().size() != 2,
71-
Status::Invalid("'like' function requires two parameters"));
70+
ARROW_RETURN_IF(node.children().size() != 2 && node.children().size() != 3,
71+
Status::Invalid("'like' function requires two or three parameters"));
7272

7373
auto literal = dynamic_cast<LiteralNode*>(node.children().at(1).get());
7474
ARROW_RETURN_IF(
@@ -80,8 +80,22 @@ Status LikeHolder::Make(const FunctionNode& node, std::shared_ptr<LikeHolder>* h
8080
!IsArrowStringLiteral(literal_type),
8181
Status::Invalid(
8282
"'like' function requires a string literal as the second parameter"));
83-
84-
return Make(arrow::util::get<std::string>(literal->holder()), holder);
83+
if (node.children().size() == 2) {
84+
return Make(arrow::util::get<std::string>(literal->holder()), holder);
85+
} else {
86+
auto escape_char = dynamic_cast<LiteralNode*>(node.children().at(2).get());
87+
ARROW_RETURN_IF(
88+
escape_char == nullptr,
89+
Status::Invalid("'like' function requires a literal as the third parameter"));
90+
91+
auto escape_char_type = escape_char->return_type()->id();
92+
ARROW_RETURN_IF(
93+
!IsArrowStringLiteral(escape_char_type),
94+
Status::Invalid(
95+
"'like' function requires a string literal as the third parameter"));
96+
return Make(arrow::util::get<std::string>(literal->holder()),
97+
arrow::util::get<std::string>(escape_char->holder()), holder);
98+
}
8599
}
86100

87101
Status LikeHolder::Make(const std::string& sql_pattern,
@@ -97,4 +111,25 @@ Status LikeHolder::Make(const std::string& sql_pattern,
97111
return Status::OK();
98112
}
99113

114+
Status LikeHolder::Make(const std::string& sql_pattern, const std::string& escape_char,
115+
std::shared_ptr<LikeHolder>* holder) {
116+
ARROW_RETURN_IF(escape_char.length() > 1,
117+
Status::Invalid("The length of escape char ", escape_char,
118+
" in 'like' function is greater than 1"));
119+
std::string pcre_pattern;
120+
if (escape_char.length() == 1) {
121+
ARROW_RETURN_NOT_OK(
122+
RegexUtil::SqlLikePatternToPcre(sql_pattern, escape_char.at(0), pcre_pattern));
123+
} else {
124+
ARROW_RETURN_NOT_OK(RegexUtil::SqlLikePatternToPcre(sql_pattern, pcre_pattern));
125+
}
126+
127+
auto lholder = std::shared_ptr<LikeHolder>(new LikeHolder(pcre_pattern));
128+
ARROW_RETURN_IF(!lholder->regex_.ok(),
129+
Status::Invalid("Building RE2 pattern '", pcre_pattern, "' failed"));
130+
131+
*holder = lholder;
132+
return Status::OK();
133+
}
134+
100135
} // namespace gandiva

cpp/src/gandiva/like_holder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class GANDIVA_EXPORT LikeHolder : public FunctionHolder {
3939

4040
static Status Make(const std::string& sql_pattern, std::shared_ptr<LikeHolder>* holder);
4141

42+
static Status Make(const std::string& sql_pattern, const std::string& escape_char,
43+
std::shared_ptr<LikeHolder>* holder);
44+
4245
// Try and optimise a function node with a "like" pattern.
4346
static const FunctionNode TryOptimize(const FunctionNode& node);
4447

cpp/src/gandiva/like_holder_test.cc

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ class TestLikeHolder : public ::testing::Test {
3333
std::make_shared<LiteralNode>(arrow::utf8(), LiteralHolder(pattern), false);
3434
return FunctionNode("like", {field, pattern_node}, arrow::boolean());
3535
}
36+
37+
FunctionNode BuildLike(std::string pattern, char escape_char) {
38+
auto field = std::make_shared<FieldNode>(arrow::field("in", arrow::utf8()));
39+
auto pattern_node =
40+
std::make_shared<LiteralNode>(arrow::utf8(), LiteralHolder(pattern), false);
41+
auto escape_char_node = std::make_shared<LiteralNode>(
42+
arrow::int8(), LiteralHolder((int8_t)escape_char), false);
43+
return FunctionNode("like", {field, pattern_node, escape_char_node},
44+
arrow::boolean());
45+
}
3646
};
3747

3848
TEST_F(TestLikeHolder, TestMatchAny) {
@@ -125,6 +135,80 @@ TEST_F(TestLikeHolder, TestOptimise) {
125135

126136
fnode = LikeHolder::TryOptimize(BuildLike("x_yz%"));
127137
EXPECT_EQ(fnode.descriptor()->name(), "like");
138+
139+
// no optimisation for escaped pattern.
140+
fnode = LikeHolder::TryOptimize(BuildLike("\\%xyz", '\\'));
141+
EXPECT_EQ(fnode.descriptor()->name(), "like");
142+
EXPECT_EQ(fnode.ToString(),
143+
"bool like((string) in, (const string) \\%xyz, (const int8) \\)");
144+
}
145+
146+
TEST_F(TestLikeHolder, TestMatchOneEscape) {
147+
std::shared_ptr<LikeHolder> like_holder;
148+
149+
auto status = LikeHolder::Make("ab\\_", "\\", &like_holder);
150+
EXPECT_EQ(status.ok(), true) << status.message();
151+
152+
auto& like = *like_holder;
153+
154+
EXPECT_TRUE(like("ab_"));
155+
156+
EXPECT_FALSE(like("abc"));
157+
EXPECT_FALSE(like("abd"));
158+
EXPECT_FALSE(like("a"));
159+
EXPECT_FALSE(like("abcd"));
160+
EXPECT_FALSE(like("dabc"));
161+
}
162+
163+
TEST_F(TestLikeHolder, TestMatchManyEscape) {
164+
std::shared_ptr<LikeHolder> like_holder;
165+
166+
auto status = LikeHolder::Make("ab\\%", "\\", &like_holder);
167+
EXPECT_EQ(status.ok(), true) << status.message();
168+
169+
auto& like = *like_holder;
170+
171+
EXPECT_TRUE(like("ab%"));
172+
173+
EXPECT_FALSE(like("abc"));
174+
EXPECT_FALSE(like("abd"));
175+
EXPECT_FALSE(like("a"));
176+
EXPECT_FALSE(like("abcd"));
177+
EXPECT_FALSE(like("dabc"));
178+
}
179+
180+
TEST_F(TestLikeHolder, TestMatchEscape) {
181+
std::shared_ptr<LikeHolder> like_holder;
182+
183+
auto status = LikeHolder::Make("ab\\\\", "\\", &like_holder);
184+
EXPECT_EQ(status.ok(), true) << status.message();
185+
186+
auto& like = *like_holder;
187+
188+
EXPECT_TRUE(like("ab\\"));
189+
190+
EXPECT_FALSE(like("abc"));
128191
}
129192

193+
TEST_F(TestLikeHolder, TestEmptyEscapeChar) {
194+
std::shared_ptr<LikeHolder> like_holder;
195+
196+
auto status = LikeHolder::Make("ab\\_", "", &like_holder);
197+
EXPECT_EQ(status.ok(), true) << status.message();
198+
199+
auto& like = *like_holder;
200+
201+
EXPECT_TRUE(like("ab\\c"));
202+
EXPECT_TRUE(like("ab\\_"));
203+
204+
EXPECT_FALSE(like("ab\\_d"));
205+
EXPECT_FALSE(like("ab__"));
206+
}
207+
208+
TEST_F(TestLikeHolder, TestMultipleEscapeChar) {
209+
std::shared_ptr<LikeHolder> like_holder;
210+
211+
auto status = LikeHolder::Make("ab\\_", "\\\\", &like_holder);
212+
EXPECT_EQ(status.ok(), false) << status.message();
213+
}
130214
} // namespace gandiva

cpp/src/gandiva/tests/utf8_test.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,49 @@ TEST_F(TestUtf8, TestLike) {
221221
EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
222222
}
223223

224+
TEST_F(TestUtf8, TestLikeWithEscape) {
225+
// schema for input fields
226+
auto field_a = field("a", utf8());
227+
auto schema = arrow::schema({field_a});
228+
229+
// output fields
230+
auto res = field("res", boolean());
231+
232+
// build expressions.
233+
// like(literal(s), a, '\')
234+
235+
auto node_a = TreeExprBuilder::MakeField(field_a);
236+
auto literal_s = TreeExprBuilder::MakeStringLiteral("%pa\\%rk%");
237+
auto escape_char = TreeExprBuilder::MakeStringLiteral("\\");
238+
auto is_like =
239+
TreeExprBuilder::MakeFunction("like", {node_a, literal_s, escape_char}, boolean());
240+
auto expr = TreeExprBuilder::MakeExpression(is_like, res);
241+
242+
// Build a projector for the expressions.
243+
std::shared_ptr<Projector> projector;
244+
auto status = Projector::Make(schema, {expr}, TestConfiguration(), &projector);
245+
EXPECT_TRUE(status.ok()) << status.message();
246+
247+
// Create a row-batch with some sample data
248+
int num_records = 4;
249+
auto array_a = MakeArrowArrayUtf8(
250+
{"park", "spa%rkle", "bright spa%rk and fire", "spark"}, {true, true, true, true});
251+
252+
// expected output
253+
auto exp = MakeArrowArrayBool({false, true, true, false}, {true, true, true, true});
254+
255+
// prepare input record batch
256+
auto in_batch = arrow::RecordBatch::Make(schema, num_records, {array_a});
257+
258+
// Evaluate expression
259+
arrow::ArrayVector outputs;
260+
status = projector->Evaluate(*in_batch, pool_, &outputs);
261+
EXPECT_TRUE(status.ok()) << status.message();
262+
263+
// Validate results
264+
EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
265+
}
266+
224267
TEST_F(TestUtf8, TestBeginsEnds) {
225268
// schema for input fields
226269
auto field_a = field("a", utf8());

0 commit comments

Comments
 (0)