Skip to content

Commit 51037b6

Browse files
jckingcopybara-github
authored andcommitted
Make <string>.startsWith, <string>.endsWith, and <string>.contains cheap again
PiperOrigin-RevId: 738829770
1 parent 16b3c6f commit 51037b6

File tree

4 files changed

+142
-4
lines changed

4 files changed

+142
-4
lines changed

common/values/string_value.cc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "absl/log/absl_check.h"
2323
#include "absl/status/status.h"
2424
#include "absl/strings/cord.h"
25+
#include "absl/strings/match.h"
2526
#include "absl/strings/str_cat.h"
2627
#include "absl/strings/string_view.h"
2728
#include "common/value.h"
@@ -204,4 +205,79 @@ int StringValue::Compare(const StringValue& string) const {
204205
[this](const auto& alternative) -> int { return Compare(alternative); });
205206
}
206207

208+
bool StringValue::StartsWith(absl::string_view string) const {
209+
return value_.Visit(absl::Overload(
210+
[&](absl::string_view lhs) -> bool {
211+
return absl::StartsWith(lhs, string);
212+
},
213+
[&](const absl::Cord& lhs) -> bool { return lhs.StartsWith(string); }));
214+
}
215+
216+
bool StringValue::StartsWith(const absl::Cord& string) const {
217+
return value_.Visit(absl::Overload(
218+
[&](absl::string_view lhs) -> bool {
219+
return lhs.size() >= string.size() &&
220+
lhs.substr(0, string.size()) == string;
221+
},
222+
[&](const absl::Cord& lhs) -> bool { return lhs.StartsWith(string); }));
223+
}
224+
225+
bool StringValue::StartsWith(const StringValue& string) const {
226+
return string.value_.Visit(absl::Overload(
227+
[&](absl::string_view rhs) -> bool { return StartsWith(rhs); },
228+
[&](const absl::Cord& rhs) -> bool { return StartsWith(rhs); }));
229+
}
230+
231+
bool StringValue::EndsWith(absl::string_view string) const {
232+
return value_.Visit(absl::Overload(
233+
[&](absl::string_view lhs) -> bool {
234+
return absl::EndsWith(lhs, string);
235+
},
236+
[&](const absl::Cord& lhs) -> bool { return lhs.EndsWith(string); }));
237+
}
238+
239+
bool StringValue::EndsWith(const absl::Cord& string) const {
240+
return value_.Visit(absl::Overload(
241+
[&](absl::string_view lhs) -> bool {
242+
return lhs.size() >= string.size() &&
243+
lhs.substr(lhs.size() - string.size()) == string;
244+
},
245+
[&](const absl::Cord& lhs) -> bool { return lhs.EndsWith(string); }));
246+
}
247+
248+
bool StringValue::EndsWith(const StringValue& string) const {
249+
return string.value_.Visit(absl::Overload(
250+
[&](absl::string_view rhs) -> bool { return EndsWith(rhs); },
251+
[&](const absl::Cord& rhs) -> bool { return EndsWith(rhs); }));
252+
}
253+
254+
bool StringValue::Contains(absl::string_view string) const {
255+
return value_.Visit(absl::Overload(
256+
[&](absl::string_view lhs) -> bool {
257+
return absl::StrContains(lhs, string);
258+
},
259+
[&](const absl::Cord& lhs) -> bool { return lhs.Contains(string); }));
260+
}
261+
262+
bool StringValue::Contains(const absl::Cord& string) const {
263+
return value_.Visit(absl::Overload(
264+
[&](absl::string_view lhs) -> bool {
265+
if (auto flat = string.TryFlat(); flat) {
266+
return absl::StrContains(lhs, *flat);
267+
}
268+
// There is no nice way to do this. We cannot use std::search due to
269+
// absl::Cord::CharIterator being an input iterator instead of a forward
270+
// iterator. So just make an external cord with a noop releaser. We know
271+
// the external cord will not outlive this function.
272+
return absl::MakeCordFromExternal(lhs, []() {}).Contains(string);
273+
},
274+
[&](const absl::Cord& lhs) -> bool { return lhs.Contains(string); }));
275+
}
276+
277+
bool StringValue::Contains(const StringValue& string) const {
278+
return string.value_.Visit(absl::Overload(
279+
[&](absl::string_view rhs) -> bool { return Contains(rhs); },
280+
[&](const absl::Cord& rhs) -> bool { return Contains(rhs); }));
281+
}
282+
207283
} // namespace cel

common/values/string_value.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,18 @@ class StringValue final : private common_internal::ValueMixin<StringValue> {
196196
int Compare(const absl::Cord& string) const;
197197
int Compare(const StringValue& string) const;
198198

199+
bool StartsWith(absl::string_view string) const;
200+
bool StartsWith(const absl::Cord& string) const;
201+
bool StartsWith(const StringValue& string) const;
202+
203+
bool EndsWith(absl::string_view string) const;
204+
bool EndsWith(const absl::Cord& string) const;
205+
bool EndsWith(const StringValue& string) const;
206+
207+
bool Contains(absl::string_view string) const;
208+
bool Contains(const absl::Cord& string) const;
209+
bool Contains(const StringValue& string) const;
210+
199211
absl::optional<absl::string_view> TryFlat() const
200212
ABSL_ATTRIBUTE_LIFETIME_BOUND {
201213
return value_.TryFlat();

common/values/string_value_test.cc

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,5 +157,56 @@ TEST_F(StringValueTest, LessThan) {
157157
EXPECT_LT(absl::Cord("bar"), StringValue("foo"));
158158
}
159159

160+
TEST_F(StringValueTest, StartsWith) {
161+
EXPECT_TRUE(
162+
StringValue("This string is large enough to not be stored inline!")
163+
.StartsWith(StringValue("This string is large enough")));
164+
EXPECT_TRUE(
165+
StringValue("This string is large enough to not be stored inline!")
166+
.StartsWith(StringValue(absl::Cord("This string is large enough"))));
167+
EXPECT_TRUE(
168+
StringValue(
169+
absl::Cord("This string is large enough to not be stored inline!"))
170+
.StartsWith(StringValue("This string is large enough")));
171+
EXPECT_TRUE(
172+
StringValue(
173+
absl::Cord("This string is large enough to not be stored inline!"))
174+
.StartsWith(StringValue(absl::Cord("This string is large enough"))));
175+
}
176+
177+
TEST_F(StringValueTest, EndsWith) {
178+
EXPECT_TRUE(
179+
StringValue("This string is large enough to not be stored inline!")
180+
.EndsWith(StringValue("to not be stored inline!")));
181+
EXPECT_TRUE(
182+
StringValue("This string is large enough to not be stored inline!")
183+
.EndsWith(StringValue(absl::Cord("to not be stored inline!"))));
184+
EXPECT_TRUE(
185+
StringValue(
186+
absl::Cord("This string is large enough to not be stored inline!"))
187+
.EndsWith(StringValue("to not be stored inline!")));
188+
EXPECT_TRUE(
189+
StringValue(
190+
absl::Cord("This string is large enough to not be stored inline!"))
191+
.EndsWith(StringValue(absl::Cord("to not be stored inline!"))));
192+
}
193+
194+
TEST_F(StringValueTest, Contains) {
195+
EXPECT_TRUE(
196+
StringValue("This string is large enough to not be stored inline!")
197+
.Contains(StringValue("string is large enough")));
198+
EXPECT_TRUE(
199+
StringValue("This string is large enough to not be stored inline!")
200+
.Contains(StringValue(absl::Cord("string is large enough"))));
201+
EXPECT_TRUE(
202+
StringValue(
203+
absl::Cord("This string is large enough to not be stored inline!"))
204+
.Contains(StringValue("string is large enough")));
205+
EXPECT_TRUE(
206+
StringValue(
207+
absl::Cord("This string is large enough to not be stored inline!"))
208+
.Contains(StringValue(absl::Cord("string is large enough"))));
209+
}
210+
160211
} // namespace
161212
} // namespace cel

runtime/standard/string_functions.cc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include "absl/base/nullability.h"
2020
#include "absl/status/status.h"
2121
#include "absl/status/statusor.h"
22-
#include "absl/strings/match.h"
2322
#include "absl/strings/str_cat.h"
2423
#include "absl/strings/string_view.h"
2524
#include "base/builtins.h"
@@ -60,15 +59,15 @@ absl::StatusOr<BytesValue> ConcatBytes(
6059
}
6160

6261
bool StringContains(const StringValue& value, const StringValue& substr) {
63-
return absl::StrContains(value.ToString(), substr.ToString());
62+
return value.Contains(substr);
6463
}
6564

6665
bool StringEndsWith(const StringValue& value, const StringValue& suffix) {
67-
return absl::EndsWith(value.ToString(), suffix.ToString());
66+
return value.EndsWith(suffix);
6867
}
6968

7069
bool StringStartsWith(const StringValue& value, const StringValue& prefix) {
71-
return absl::StartsWith(value.ToString(), prefix.ToString());
70+
return value.StartsWith(prefix);
7271
}
7372

7473
absl::Status RegisterSizeFunctions(FunctionRegistry& registry) {

0 commit comments

Comments
 (0)