Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -830,11 +830,13 @@ object LikeSimplification extends Rule[LogicalPlan] with PredicateHelper {
case endsWith(postfix) =>
Some(EndsWith(input, Literal.create(postfix, input.dataType)))
// 'a%a' pattern is basically same with 'a%' && '%a'.
// However, the additional `Length` condition is required to prevent 'a' match 'a%a'.
// However, the additional length condition is required to prevent 'a' match 'a%a'.
// Since StartsWith/EndsWith already pin the prefix and suffix at code-point boundaries,
// a byte-length guard (OctetLength, O(1)) is equivalent to a char-length guard and cheaper.
case startsAndEndsWith(prefix, postfix) =>
Some(And(GreaterThanOrEqual(Length(input),
Literal.create(prefix.codePointCount(0, prefix.length)
+ postfix.codePointCount(0, postfix.length))),
Some(And(GreaterThanOrEqual(OctetLength(input),
Literal.create(UTF8String.fromString(prefix).numBytes
+ UTF8String.fromString(postfix).numBytes)),
And(StartsWith(input, Literal.create(prefix, input.dataType)),
EndsWith(input, Literal.create(postfix, input.dataType)))))
case contains(infix) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class LikeSimplificationSuite extends PlanTest {
val optimized = Optimize.execute(originalQuery.analyze)
val correctAnswer = testRelation
.where(($"a" like "abc\\%def") ||
(Length($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))))
(OctetLength($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))))
.analyze

comparePlans(optimized, correctAnswer)
Expand Down Expand Up @@ -142,7 +142,7 @@ class LikeSimplificationSuite extends PlanTest {
val optimized3 = Optimize.execute(originalQuery3.analyze)
val correctAnswer3 = testRelation
.where(($"a" like ("@bc%def", '@')) ||
(Length($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))))
(OctetLength($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))))
.analyze
comparePlans(optimized3, correctAnswer3)

Expand Down Expand Up @@ -190,7 +190,7 @@ class LikeSimplificationSuite extends PlanTest {
val optimized3 = Optimize.execute(originalQuery3.analyze)
val correctAnswer3 = testRelation
.where(
(Length($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))))
(OctetLength($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))))
.analyze
comparePlans(optimized3, correctAnswer3)

Expand Down Expand Up @@ -222,7 +222,7 @@ class LikeSimplificationSuite extends PlanTest {
val optimized = Optimize.execute(originalQuery.analyze)
val correctAnswer = testRelation
.where((((((StartsWith($"a", "abc") && EndsWith($"a", "xyz")) &&
(Length($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def")))) &&
(OctetLength($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def")))) &&
Contains($"a", "mn")) && ($"a" === "")) && ($"a" === "abc")) &&
($"a" likeAll("abc\\%", "abc\\%def", "%mn\\%")))
.analyze
Expand All @@ -239,7 +239,7 @@ class LikeSimplificationSuite extends PlanTest {
val optimized = Optimize.execute(originalQuery.analyze)
val correctAnswer = testRelation
.where((((((Not(StartsWith($"a", "abc")) && Not(EndsWith($"a", "xyz"))) &&
Not(Length($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def")))) &&
Not(OctetLength($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def")))) &&
Not(Contains($"a", "mn"))) && Not($"a" === "")) && Not($"a" === "abc")) &&
($"a" notLikeAll("abc\\%", "abc\\%def", "%mn\\%")))
.analyze
Expand All @@ -256,7 +256,7 @@ class LikeSimplificationSuite extends PlanTest {
val optimized = Optimize.execute(originalQuery.analyze)
val correctAnswer = testRelation
.where(((StartsWith($"a", "abc") || EndsWith($"a", "xyz")) ||
(Length($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def")) ||
(OctetLength($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def")) ||
Contains($"a", "mn")) || (($"a" === "") || ($"a" === "abc")) ||
($"a" likeAny("abc\\%", "abc\\%def", "%mn\\%"))))
.analyze
Expand All @@ -273,7 +273,7 @@ class LikeSimplificationSuite extends PlanTest {
val optimized = Optimize.execute(originalQuery.analyze)
val correctAnswer = testRelation
.where((((Not(StartsWith($"a", "abc")) || Not(EndsWith($"a", "xyz"))) ||
(Not(Length($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))) ||
(Not(OctetLength($"a") >= 6 && (StartsWith($"a", "abc") && EndsWith($"a", "def"))) ||
Not(Contains($"a", "mn")))) || (Not($"a" === "") || Not($"a" === "abc"))) ||
($"a" notLikeAny("abc\\%", "abc\\%def", "%mn\\%")))
.analyze
Expand Down Expand Up @@ -319,7 +319,10 @@ class LikeSimplificationSuite extends PlanTest {
val optimized = Optimize.execute(originalQuery.analyze)

val correctAnswer = testRelation
.where(Length($"a") >= 2 && (StartsWith($"a", "😀") && EndsWith($"a", "🥑")))
// Byte-length guard: '😀' and '🥑' are 4 UTF-8 bytes each, so the threshold is 8
// bytes rather than 2 code points. This is equivalent to the char-length guard
// because StartsWith/EndsWith already pin the prefix and suffix at byte boundaries.
.where(OctetLength($"a") >= 8 && (StartsWith($"a", "😀") && EndsWith($"a", "🥑")))
.analyze
comparePlans(optimized, correctAnswer)
}
Expand Down