2727import com .starrocks .sql .optimizer .operator .scalar .ConstantOperator ;
2828import com .starrocks .sql .optimizer .operator .scalar .InPredicateOperator ;
2929import com .starrocks .sql .optimizer .operator .scalar .IsNullPredicateOperator ;
30+ import com .starrocks .sql .optimizer .operator .scalar .LikePredicateOperator ;
3031import com .starrocks .sql .optimizer .operator .scalar .ScalarOperator ;
3132import com .starrocks .sql .optimizer .rewrite .ScalarOperatorRewriteContext ;
3233import com .starrocks .type .BooleanType ;
@@ -210,4 +211,194 @@ public void applyBinary() {
210211 assertEquals (OB_FALSE , rule .apply (bpo10 , null ));
211212
212213 }
214+
215+ @ Test
216+ public void applyLikeFolding () {
217+ // Values below are what the FE Parser produces after the first layer of string-literal
218+ // escaping (AstBuilder.escapeBackSlash). Java string mapping:
219+ // SQL 'a\\b' → Java "a\\b" (3 chars: a, \, b)
220+ // SQL 'a\\\\b' → Java "a\\\\b" (4 chars: a, \, \, b)
221+ // SQL '100\\%' → Java "100\\%" (5 chars: 1,0,0,\,%) — Parser keeps \% intact
222+ // SQL 'a\\_b' → Java "a\\_b" (4 chars: a,\,_,b) — Parser keeps \_ intact
223+
224+ // Bug regression: 'a\\b' LIKE 'a\\\\b' must return 1 (MySQL), was 0 before fix
225+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
226+ LikePredicateOperator .LikeType .LIKE ,
227+ ConstantOperator .createVarchar ("a\\ b" ),
228+ ConstantOperator .createVarchar ("a\\ \\ b" )), null ));
229+
230+ // Bug regression: 'a\\b' LIKE 'a\\b' must return 0 (MySQL), was 1 before fix
231+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
232+ LikePredicateOperator .LikeType .LIKE ,
233+ ConstantOperator .createVarchar ("a\\ b" ),
234+ ConstantOperator .createVarchar ("a\\ b" )), null ));
235+
236+ // Escaped % as literal: '100%' LIKE '100\%' → 1 (\% matches the literal % in text)
237+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
238+ LikePredicateOperator .LikeType .LIKE ,
239+ ConstantOperator .createVarchar ("100%" ),
240+ ConstantOperator .createVarchar ("100\\ %" )), null ));
241+
242+ // Escaped % must NOT act as wildcard: '100abc' LIKE '100\%' → 0
243+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
244+ LikePredicateOperator .LikeType .LIKE ,
245+ ConstantOperator .createVarchar ("100abc" ),
246+ ConstantOperator .createVarchar ("100\\ %" )), null ));
247+
248+ // Plain equals (no backslash) — existing behavior preserved
249+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
250+ LikePredicateOperator .LikeType .LIKE ,
251+ ConstantOperator .createVarchar ("hello" ),
252+ ConstantOperator .createVarchar ("hello" )), null ));
253+
254+ // Plain startsWith — existing behavior preserved
255+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
256+ LikePredicateOperator .LikeType .LIKE ,
257+ ConstantOperator .createVarchar ("abcdef" ),
258+ ConstantOperator .createVarchar ("abc%" )), null ));
259+
260+ // Pattern with _ wildcard: not a fast-path pattern, returned to BE as-is
261+ LikePredicateOperator withUnderscore = new LikePredicateOperator (
262+ LikePredicateOperator .LikeType .LIKE ,
263+ ConstantOperator .createVarchar ("abc" ),
264+ ConstantOperator .createVarchar ("a_c" ));
265+ assertEquals (withUnderscore , rule .apply (withUnderscore , null ));
266+
267+ // Non-constant text: not folded, returned as-is
268+ LikePredicateOperator nonConst = new LikePredicateOperator (
269+ LikePredicateOperator .LikeType .LIKE ,
270+ new ColumnRefOperator (1 , VarcharType .VARCHAR , "col" , true ),
271+ ConstantOperator .createVarchar ("a\\ \\ b" ));
272+ assertEquals (nonConst , rule .apply (nonConst , null ));
273+
274+ // endsWith path: 'xyzabc' LIKE '%abc' → 1
275+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
276+ LikePredicateOperator .LikeType .LIKE ,
277+ ConstantOperator .createVarchar ("xyzabc" ),
278+ ConstantOperator .createVarchar ("%abc" )), null ));
279+
280+ // contains path: 'xyzabcdef' LIKE '%abc%' → 1
281+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
282+ LikePredicateOperator .LikeType .LIKE ,
283+ ConstantOperator .createVarchar ("xyzabcdef" ),
284+ ConstantOperator .createVarchar ("%abc%" )), null ));
285+
286+ // Trailing lone backslash — invalid LIKE escape, not folded → return predicate
287+ LikePredicateOperator trailingSlash = new LikePredicateOperator (
288+ LikePredicateOperator .LikeType .LIKE ,
289+ ConstantOperator .createVarchar ("a\\ " ),
290+ ConstantOperator .createVarchar ("a\\ " ));
291+ assertEquals (trailingSlash , rule .apply (trailingSlash , null ));
292+
293+ // startsWith with escaped % in body: text must start with literal "a%xy"
294+ // SQL pattern 'a\%xy%': Parser keeps \% → Java "a\\%xy%"; trailing % is the real wildcard
295+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
296+ LikePredicateOperator .LikeType .LIKE ,
297+ ConstantOperator .createVarchar ("a%xyz" ),
298+ ConstantOperator .createVarchar ("a\\ %xy%" )), null ));
299+
300+ // \% in body is NOT a wildcard: "aXxyz" does not start with "a%xy"
301+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
302+ LikePredicateOperator .LikeType .LIKE ,
303+ ConstantOperator .createVarchar ("aXxyz" ),
304+ ConstantOperator .createVarchar ("a\\ %xy%" )), null ));
305+
306+ // --- Escaped wildcard directly adjacent to a real wildcard (regression for the
307+ // stripStart/stripEnd over-strip bug; only passes with the capture-group fix) ---
308+
309+ // startsWith: SQL 'abc\%%' → Java "abc\\%%": literal "abc%" + trailing wildcard %.
310+ // Must match text starting with "abc%".
311+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
312+ LikePredicateOperator .LikeType .LIKE ,
313+ ConstantOperator .createVarchar ("abc%xyz" ),
314+ ConstantOperator .createVarchar ("abc\\ %%" )), null ));
315+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
316+ LikePredicateOperator .LikeType .LIKE ,
317+ ConstantOperator .createVarchar ("abcXyz" ),
318+ ConstantOperator .createVarchar ("abc\\ %%" )), null ));
319+
320+ // endsWith: SQL '%\%abc' → Java "%\\%abc": leading wildcard % + literal "%abc".
321+ // Must match text ending with "%abc".
322+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
323+ LikePredicateOperator .LikeType .LIKE ,
324+ ConstantOperator .createVarchar ("xyz%abc" ),
325+ ConstantOperator .createVarchar ("%\\ %abc" )), null ));
326+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
327+ LikePredicateOperator .LikeType .LIKE ,
328+ ConstantOperator .createVarchar ("xyzabc" ),
329+ ConstantOperator .createVarchar ("%\\ %abc" )), null ));
330+
331+ // contains: SQL '%a\%b%' → Java "%a\\%b%": wildcards on both ends, literal "a%b" inside.
332+ // Must match text containing "a%b".
333+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
334+ LikePredicateOperator .LikeType .LIKE ,
335+ ConstantOperator .createVarchar ("xa%by" ),
336+ ConstantOperator .createVarchar ("%a\\ %b%" )), null ));
337+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
338+ LikePredicateOperator .LikeType .LIKE ,
339+ ConstantOperator .createVarchar ("xaXby" ),
340+ ConstantOperator .createVarchar ("%a\\ %b%" )), null ));
341+
342+ // --- Other escape forms ---
343+
344+ // Double backslash + wildcard: SQL 'a\\%' → Java "a\\\\%": literal "a\" + wildcard %.
345+ // Must match text starting with "a\".
346+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
347+ LikePredicateOperator .LikeType .LIKE ,
348+ ConstantOperator .createVarchar ("a\\ xyz" ),
349+ ConstantOperator .createVarchar ("a\\ \\ %" )), null ));
350+
351+ // Trailing escaped backslash (EQUALS): SQL 'abc\\' → Java "abc\\\\": literal "abc\".
352+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
353+ LikePredicateOperator .LikeType .LIKE ,
354+ ConstantOperator .createVarchar ("abc\\ " ),
355+ ConstantOperator .createVarchar ("abc\\ \\ " )), null ));
356+
357+ // Escaped underscore as a literal: SQL 'a\_b' → Java "a\\_b": literal "a_b".
358+ assertEquals (OB_TRUE , rule .apply (new LikePredicateOperator (
359+ LikePredicateOperator .LikeType .LIKE ,
360+ ConstantOperator .createVarchar ("a_b" ),
361+ ConstantOperator .createVarchar ("a\\ _b" )), null ));
362+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
363+ LikePredicateOperator .LikeType .LIKE ,
364+ ConstantOperator .createVarchar ("aXb" ),
365+ ConstantOperator .createVarchar ("a\\ _b" )), null ));
366+
367+ // --- Cases that are intentionally NOT folded (returned to BE) ---
368+
369+ // Pure wildcards have no literal body → not folded
370+ LikePredicateOperator pureWildcard = new LikePredicateOperator (
371+ LikePredicateOperator .LikeType .LIKE ,
372+ ConstantOperator .createVarchar ("anything" ),
373+ ConstantOperator .createVarchar ("%" ));
374+ assertEquals (pureWildcard , rule .apply (pureWildcard , null ));
375+
376+ // Empty pattern → not folded
377+ LikePredicateOperator emptyPattern = new LikePredicateOperator (
378+ LikePredicateOperator .LikeType .LIKE ,
379+ ConstantOperator .createVarchar ("x" ),
380+ ConstantOperator .createVarchar ("" ));
381+ assertEquals (emptyPattern , rule .apply (emptyPattern , null ));
382+
383+ // REGEXP (not LIKE) type → not folded by this rule
384+ LikePredicateOperator regexp = new LikePredicateOperator (
385+ LikePredicateOperator .LikeType .REGEXP ,
386+ ConstantOperator .createVarchar ("abc" ),
387+ ConstantOperator .createVarchar ("abc" ));
388+ assertEquals (regexp , rule .apply (regexp , null ));
389+
390+ // --- Other correctness checks ---
391+
392+ // NULL operand → NULL
393+ assertEquals (OB_NULL , rule .apply (new LikePredicateOperator (
394+ LikePredicateOperator .LikeType .LIKE ,
395+ ConstantOperator .createNull (VarcharType .VARCHAR ),
396+ ConstantOperator .createVarchar ("abc" )), null ));
397+
398+ // LIKE folding is case-sensitive (binary collation): 'ABC' LIKE 'abc' → 0
399+ assertEquals (OB_FALSE , rule .apply (new LikePredicateOperator (
400+ LikePredicateOperator .LikeType .LIKE ,
401+ ConstantOperator .createVarchar ("ABC" ),
402+ ConstantOperator .createVarchar ("abc" )), null ));
403+ }
213404}
0 commit comments