|  | @@ -39,7 +39,9 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Case;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.expression.function.scalar.string.StartsWith;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.RLike;
 | 
	
		
			
				|  |  | +import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.RLikeList;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLike;
 | 
	
		
			
				|  |  | +import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLikeList;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.expression.predicate.logical.And;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNotNull;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add;
 | 
	
	
		
			
				|  | @@ -673,6 +675,26 @@ public class LocalLogicalPlanOptimizerTests extends ESTestCase {
 | 
	
		
			
				|  |  |          var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /*
 | 
	
		
			
				|  |  | +     *Limit[1000[INTEGER],false]
 | 
	
		
			
				|  |  | +     * \_Filter[RLikeList(first_name{f}#4, "("VALÜ*", "TEST*")", true)]
 | 
	
		
			
				|  |  | +     *  \_EsRelation[test][_meta_field{f}#9, emp_no{f}#3, first_name{f}#4, gen..]
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public void testReplaceUpperStringCasinqWithInsensitiveRLikeList() {
 | 
	
		
			
				|  |  | +        var plan = localPlan("FROM test | WHERE TO_UPPER(TO_LOWER(TO_UPPER(first_name))) RLIKE (\"VALÜ*\", \"TEST*\")");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var limit = as(plan, Limit.class);
 | 
	
		
			
				|  |  | +        var filter = as(limit.child(), Filter.class);
 | 
	
		
			
				|  |  | +        var rLikeList = as(filter.condition(), RLikeList.class);
 | 
	
		
			
				|  |  | +        var field = as(rLikeList.field(), FieldAttribute.class);
 | 
	
		
			
				|  |  | +        assertThat(field.fieldName().string(), is("first_name"));
 | 
	
		
			
				|  |  | +        assertEquals(2, rLikeList.pattern().patternList().size());
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.pattern().patternList().get(0).pattern(), is("VALÜ*"));
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.pattern().patternList().get(1).pattern(), is("TEST*"));
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.caseInsensitive(), is(true));
 | 
	
		
			
				|  |  | +        var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // same plan as above, but lower case pattern
 | 
	
		
			
				|  |  |      public void testReplaceLowerStringCasingWithInsensitiveRLike() {
 | 
	
		
			
				|  |  |          var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) RLIKE \"valü*\"");
 | 
	
	
		
			
				|  | @@ -687,6 +709,35 @@ public class LocalLogicalPlanOptimizerTests extends ESTestCase {
 | 
	
		
			
				|  |  |          var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // same plan as above, but lower case pattern and list of patterns
 | 
	
		
			
				|  |  | +    public void testReplaceLowerStringCasingWithInsensitiveRLikeList() {
 | 
	
		
			
				|  |  | +        var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) RLIKE (\"valü*\", \"test*\")");
 | 
	
		
			
				|  |  | +        var limit = as(plan, Limit.class);
 | 
	
		
			
				|  |  | +        var filter = as(limit.child(), Filter.class);
 | 
	
		
			
				|  |  | +        var rLikeList = as(filter.condition(), RLikeList.class);
 | 
	
		
			
				|  |  | +        var field = as(rLikeList.field(), FieldAttribute.class);
 | 
	
		
			
				|  |  | +        assertThat(field.fieldName().string(), is("first_name"));
 | 
	
		
			
				|  |  | +        assertEquals(2, rLikeList.pattern().patternList().size());
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.pattern().patternList().get(0).pattern(), is("valü*"));
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.pattern().patternList().get(1).pattern(), is("test*"));
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.caseInsensitive(), is(true));
 | 
	
		
			
				|  |  | +        var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // same plan as above, but lower case pattern and list of patterns, one of which is upper case
 | 
	
		
			
				|  |  | +    public void testReplaceLowerStringCasingWithMixedCaseRLikeList() {
 | 
	
		
			
				|  |  | +        var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) RLIKE (\"valü*\", \"TEST*\")");
 | 
	
		
			
				|  |  | +        var limit = as(plan, Limit.class);
 | 
	
		
			
				|  |  | +        var filter = as(limit.child(), Filter.class);
 | 
	
		
			
				|  |  | +        var rLikeList = as(filter.condition(), RLikeList.class);
 | 
	
		
			
				|  |  | +        var field = as(rLikeList.field(), FieldAttribute.class);
 | 
	
		
			
				|  |  | +        assertThat(field.fieldName().string(), is("first_name"));
 | 
	
		
			
				|  |  | +        assertEquals(1, rLikeList.pattern().patternList().size());
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.pattern().patternList().get(0).pattern(), is("valü*"));
 | 
	
		
			
				|  |  | +        assertThat(rLikeList.caseInsensitive(), is(true));
 | 
	
		
			
				|  |  | +        var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * LocalRelation[[_meta_field{f}#9, emp_no{f}#3, first_name{f}#4, gender{f}#5, hire_date{f}#10, job{f}#11, job.raw{f}#12, langu
 | 
	
		
			
				|  |  |       *   ages{f}#6, last_name{f}#7, long_noidx{f}#13, salary{f}#8],EMPTY]
 | 
	
	
		
			
				|  | @@ -712,6 +763,37 @@ public class LocalLogicalPlanOptimizerTests extends ESTestCase {
 | 
	
		
			
				|  |  |          var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // same plan as in testReplaceUpperStringCasingWithInsensitiveRLikeList, but with LIKE instead of RLIKE
 | 
	
		
			
				|  |  | +    public void testReplaceUpperStringCasingWithInsensitiveLikeList() {
 | 
	
		
			
				|  |  | +        var plan = localPlan("FROM test | WHERE TO_UPPER(TO_LOWER(TO_UPPER(first_name))) LIKE (\"VALÜ*\", \"TEST*\")");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var limit = as(plan, Limit.class);
 | 
	
		
			
				|  |  | +        var filter = as(limit.child(), Filter.class);
 | 
	
		
			
				|  |  | +        var likeList = as(filter.condition(), WildcardLikeList.class);
 | 
	
		
			
				|  |  | +        var field = as(likeList.field(), FieldAttribute.class);
 | 
	
		
			
				|  |  | +        assertThat(field.fieldName().string(), is("first_name"));
 | 
	
		
			
				|  |  | +        assertEquals(2, likeList.pattern().patternList().size());
 | 
	
		
			
				|  |  | +        assertThat(likeList.pattern().patternList().get(0).pattern(), is("VALÜ*"));
 | 
	
		
			
				|  |  | +        assertThat(likeList.pattern().patternList().get(1).pattern(), is("TEST*"));
 | 
	
		
			
				|  |  | +        assertThat(likeList.caseInsensitive(), is(true));
 | 
	
		
			
				|  |  | +        var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // same plan as above, but mixed case pattern and list of patterns
 | 
	
		
			
				|  |  | +    public void testReplaceLowerStringCasingWithMixedCaseLikeList() {
 | 
	
		
			
				|  |  | +        var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) LIKE (\"TEST*\", \"valü*\", \"vaLü*\")");
 | 
	
		
			
				|  |  | +        var limit = as(plan, Limit.class);
 | 
	
		
			
				|  |  | +        var filter = as(limit.child(), Filter.class);
 | 
	
		
			
				|  |  | +        var likeList = as(filter.condition(), WildcardLikeList.class);
 | 
	
		
			
				|  |  | +        var field = as(likeList.field(), FieldAttribute.class);
 | 
	
		
			
				|  |  | +        assertThat(field.fieldName().string(), is("first_name"));
 | 
	
		
			
				|  |  | +        // only the all lowercase pattern is kept, the mixed case and all uppercase patterns are ignored
 | 
	
		
			
				|  |  | +        assertEquals(1, likeList.pattern().patternList().size());
 | 
	
		
			
				|  |  | +        assertThat(likeList.pattern().patternList().get(0).pattern(), is("valü*"));
 | 
	
		
			
				|  |  | +        assertThat(likeList.caseInsensitive(), is(true));
 | 
	
		
			
				|  |  | +        var source = as(filter.child(), EsRelation.class);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // same plan as above, but lower case pattern
 | 
	
		
			
				|  |  |      public void testReplaceLowerStringCasingWithInsensitiveLike() {
 | 
	
		
			
				|  |  |          var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) LIKE \"valü*\"");
 | 
	
	
		
			
				|  | @@ -737,6 +819,28 @@ public class LocalLogicalPlanOptimizerTests extends ESTestCase {
 | 
	
		
			
				|  |  |          assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * LocalRelation[[_meta_field{f}#9, emp_no{f}#3, first_name{f}#4, gender{f}#5, hire_date{f}#10, job{f}#11, job.raw{f}#12, langu
 | 
	
		
			
				|  |  | +     *   ages{f}#6, last_name{f}#7, long_noidx{f}#13, salary{f}#8],EMPTY]
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public void testReplaceStringCasingAndLikeListWithLocalRelation() {
 | 
	
		
			
				|  |  | +        var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) LIKE (\"VALÜ*\", \"TEST*\")");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var local = as(plan, LocalRelation.class);
 | 
	
		
			
				|  |  | +        assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * LocalRelation[[_meta_field{f}#9, emp_no{f}#3, first_name{f}#4, gender{f}#5, hire_date{f}#10, job{f}#11, job.raw{f}#12, langu
 | 
	
		
			
				|  |  | +     *   ages{f}#6, last_name{f}#7, long_noidx{f}#13, salary{f}#8],EMPTY]
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public void testReplaceStringCasingAndRLikeListWithLocalRelation() {
 | 
	
		
			
				|  |  | +        var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) RLIKE (\"VALÜ*\", \"TEST*\")");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var local = as(plan, LocalRelation.class);
 | 
	
		
			
				|  |  | +        assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Limit[1000[INTEGER],false]
 | 
	
		
			
				|  |  |       * \_Aggregate[[],[SUM($$integer_long_field$converted_to$long{f$}#5,true[BOOLEAN]) AS sum(integer_long_field::long)#3]]
 |