Skip to content

Commit 4497042

Browse files
committed
opt: normalize LIKE ... ESCAPE
An expression in the form `a LIKE b ESCAPE '/'`, which is parsed as `like_escape(a, b, '/')`, is now normalized to `a LIKE b`. This is valid because `\` is the default escape character, so the expressions are equivalent. This normalization allows for further optimization, for example: ```sql CREATE TABLE t (s STRING, INDEX (s)); EXPLAIN SELECT s FROM t WHERE s LIKE 'foo\_bar' ESCAPE '\'; -- BEFORE: -- • filter -- │ filter: like_escape(s, e'foo\\_bar', e'\\') -- │ -- └── • scan -- table: t@t_pkey -- spans: FULL SCAN -- AFTER: -- • scan -- table: t@t_s_idx -- spans: [/'foo_bar' - /'foo_bar'] ``` Informs #30192 Release note (performance improvement): Queries with filters in the form `a LIKE b ESCAPE '\'` are now index-accelerated in some cases which they were not before.
1 parent 3066741 commit 4497042

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

pkg/sql/opt/norm/rules/scalar.opt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,21 @@ $udf
452452
(Indirection $input:* $index:* & (IsJSON $input))
453453
=>
454454
(FetchVal $input $index)
455+
456+
# ConvertLikeEscapeToLike converts a LIKE x ESCAPE '\' expression, which is
457+
# parsed as a like_escape function call, to a LIKE expression. This is valid
458+
# because the default escape character is '\'.
459+
#
460+
# Note that the FunctionExpr match pattern is enough to ensure that we are not
461+
# transforming a UDF because UDF invocations are always built as UDFCallExprs.
462+
[ConvertLikeEscapeToLike, Normalize]
463+
(Function
464+
$args:*
465+
$private:(FunctionPrivate "like_escape") &
466+
(Let ($input $iOk):(ScalarExprAt $args 0) $iOk) &
467+
(Let ($pattern $pOk):(ScalarExprAt $args 1) $pOk) &
468+
(Let ($escape $eOk):(ScalarExprAt $args 2) $eOk) &
469+
(ConstStringEquals $escape "\\")
470+
)
471+
=>
472+
(Like $input $pattern)

pkg/sql/opt/norm/testdata/rules/scalar

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2689,3 +2689,60 @@ project
26892689
└── projections
26902690
├── b.arr:5[1] [as=arr:8, outer=(5)]
26912691
└── b.arr:5[2] [as=arr:9, outer=(5)]
2692+
2693+
# --------------------------------------------------
2694+
# ConvertLikeEscapeToLike
2695+
# --------------------------------------------------
2696+
2697+
norm expect=ConvertLikeEscapeToLike
2698+
SELECT s LIKE 'foo%' ESCAPE '\' FROM a
2699+
----
2700+
project
2701+
├── columns: like_escape:8
2702+
├── scan a
2703+
│ └── columns: s:4
2704+
└── projections
2705+
└── s:4 LIKE 'foo%' [as=like_escape:8, outer=(4)]
2706+
2707+
norm expect=ConvertLikeEscapeToLike
2708+
SELECT like_escape(s, 'foo%', '\') FROM a
2709+
----
2710+
project
2711+
├── columns: like_escape:8
2712+
├── scan a
2713+
│ └── columns: s:4
2714+
└── projections
2715+
└── s:4 LIKE 'foo%' [as=like_escape:8, outer=(4)]
2716+
2717+
norm expect-not=ConvertLikeEscapeToLike
2718+
SELECT s LIKE 'foo%' ESCAPE '/' FROM a
2719+
----
2720+
project
2721+
├── columns: like_escape:8
2722+
├── immutable
2723+
├── scan a
2724+
│ └── columns: s:4
2725+
└── projections
2726+
└── like_escape(s:4, 'foo%', '/') [as=like_escape:8, outer=(4), immutable]
2727+
2728+
norm expect-not=ConvertLikeEscapeToLike
2729+
SELECT s LIKE 'foo%' ESCAPE 'f' FROM a
2730+
----
2731+
project
2732+
├── columns: like_escape:8
2733+
├── immutable
2734+
├── scan a
2735+
│ └── columns: s:4
2736+
└── projections
2737+
└── like_escape(s:4, 'foo%', 'f') [as=like_escape:8, outer=(4), immutable]
2738+
2739+
norm expect-not=ConvertLikeEscapeToLike
2740+
SELECT not_like_escape(s, 'foo%', '\') FROM a
2741+
----
2742+
project
2743+
├── columns: not_like_escape:8
2744+
├── immutable
2745+
├── scan a
2746+
│ └── columns: s:4
2747+
└── projections
2748+
└── not_like_escape(s:4, 'foo%', e'\\') [as=not_like_escape:8, outer=(4), immutable]

0 commit comments

Comments
 (0)