Merge pull request #657 from nfsantos/OAK-9881

OAK-9881 - Unreachable code in the logic that processes like constraints in Elastic
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
index d5dc804..38296f8 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
@@ -765,6 +765,12 @@
             }
 
             if (isLike) {
+                // Note: the code below has two problems:
+                // - Does not deal with escaped wildcard characters (OAK-9885).
+                // - Does not apply the prefix query optimization: the first block of the if condition below is
+                // never executed because the guard condition is always false (OAK-9881).
+                // The correct logic is in LucenePropertyIndex#createLikeQuery.
+                // Leaving the code as it is, because this class is deprecated, as it is just for compatVersion=1
                 first = first.replace('%', WildcardQuery.WILDCARD_STRING);
                 first = first.replace('_', WildcardQuery.WILDCARD_CHAR);
 
@@ -773,7 +779,7 @@
                 int len = first.length();
 
                 if (indexOfWS == len || indexOfWC == len) {
-                    // remove trailing "*" for prefixquery
+                    // remove trailing "*" for prefix query
                     first = first.substring(0, first.length() - 1);
                     if (JCR_PATH.equals(name)) {
                         qs.add(new PrefixQuery(newPathTerm(first)));
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
index 930512e..4c3de80 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
@@ -1193,76 +1193,6 @@
     }
 
     @Test
-    public void likeQueriesWithString() throws Exception {
-        Tree idx = createIndex("test1", of("propa", "propb"));
-        idx.addChild(PROP_NODE).addChild("propa");
-        root.commit();
-
-        Tree test = root.getTree("/").addChild("test");
-        test.addChild("a").setProperty("propa", "humpty");
-        test.addChild("b").setProperty("propa", "dumpty");
-        test.addChild("c").setProperty("propa", "humpy");
-        root.commit();
-
-        assertQuery("select [jcr:path] from [nt:base] where propa like 'hum%'",
-                asList("/test/a", "/test/c"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%ty'",
-                asList("/test/a", "/test/b"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%ump%'",
-                asList("/test/a", "/test/b", "/test/c"));
-    }
-
-    @Test
-    public void likeQueriesWithEscapedChars() throws Exception {
-        Tree idx = createIndex("test1", of("propa", "propb"));
-        idx.addChild(PROP_NODE).addChild("propa");
-        root.commit();
-
-        Tree test = root.getTree("/").addChild("test");
-        test.addChild("a").setProperty("propa", "foo%");
-        test.addChild("b").setProperty("propa", "%bar");
-        test.addChild("c").setProperty("propa", "foo%bar");
-        test.addChild("d").setProperty("propa", "foo_");
-        test.addChild("e").setProperty("propa", "_foo");
-        test.addChild("f").setProperty("propa", "foo_bar");
-        test.addChild("g").setProperty("propa", "foo%_bar");
-        test.addChild("h").setProperty("propa", "foo\\bar");
-        test.addChild("i").setProperty("propa", "foo\\\\%bar");
-        root.commit();
-
-        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo%'",
-                asList("/test/a", "/test/c", "/test/d", "/test/f", "/test/g", "/test/h", "/test/i"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo%'",
-                asList("/test/a", "/test/c", "/test/d", "/test/e", "/test/f", "/test/g", "/test/h", "/test/i"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo\\%'",
-                asList("/test/a"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\%'",
-                asList("/test/a"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\%%'",
-                asList("/test/a", "/test/c", "/test/g"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '\\%b%'",
-                asList("/test/b"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo_'",
-                asList("/test/a", "/test/d"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '_oo_'",
-                asList("/test/a", "/test/d"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo\\_'",
-                asList("/test/d"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\_'",
-                asList("/test/d"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\_%'",
-                asList("/test/d", "/test/f"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\%\\_%'",
-                asList("/test/g"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo\\\\bar'",
-                asList("/test/h"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%\\\\%'",
-                asList("/test/h", "/test/i"));
-        assertQuery("select [jcr:path] from [nt:base] where propa like '%\\\\\\%%'",
-                asList("/test/i"));
-    }
-
-    @Test
     public void nativeQueries() throws Exception {
         Tree idx = createIndex("test1", of("propa", "propb"));
         idx.addChild(PROP_NODE).addChild("propa");
diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
index 717dbec..3e51a86 100644
--- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
+++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
@@ -41,7 +41,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
 import java.util.function.BiPredicate;
@@ -208,7 +207,7 @@
                 }
         );
     }
-    
+
     public @NotNull List<SortOptions> baseSorts() {
         List<QueryIndex.OrderEntry> sortOrder = indexPlan.getSortOrder();
         if (sortOrder == null || sortOrder.isEmpty()) {
@@ -275,7 +274,7 @@
         return filter.getPropertyRestrictions().stream()
                 .anyMatch(pr -> QueryConstants.REP_FACET.equals(pr.propertyName));
     }
-    
+
     public Map<String, Aggregation> aggregations() {
         return facetFields().collect(Collectors.toMap(Function.identity(), facetProp -> Aggregation.of(af ->
                 af.terms(tf -> tf.field(elasticIndexDefinition.getElasticKeyword(facetProp))
@@ -757,16 +756,16 @@
         first = first.replace('%', WildcardQuery.WILDCARD_STRING);
         first = first.replace('_', WildcardQuery.WILDCARD_CHAR);
 
-        int indexOfWS = first.indexOf(WildcardQuery.WILDCARD_STRING);
-        int indexOfWC = first.indexOf(WildcardQuery.WILDCARD_CHAR);
-        int len = first.length();
+        // If the query ends in a wildcard string (*) and has no other wildcard characters, use a prefix match query
+        boolean hasSingleWildcardStringAtEnd = first.indexOf(WildcardQuery.WILDCARD_STRING) == first.length() - 1;
+        boolean doesNotContainWildcardChar = first.indexOf(WildcardQuery.WILDCARD_CHAR) == -1;
 
         // Non full text (Non analyzed) properties are keyword types in ES. For those field would be equal to name.
         // Analyzed properties, however are of text type on which we can't perform wildcard or prefix queries so we use the keyword (sub) field
         // by appending .keyword to the name here.
         String field = elasticIndexDefinition.getElasticKeyword(name);
 
-        if (indexOfWS == len || indexOfWC == len) {
+        if (hasSingleWildcardStringAtEnd && doesNotContainWildcardChar) {
             // remove trailing "*" for prefix query
             first = first.substring(0, first.length() - 1);
             if (JCR_PATH.equals(name)) {
diff --git a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexCommonTest.java b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexCommonTest.java
index a5b4980..5f9a985 100644
--- a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexCommonTest.java
+++ b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticPropertyIndexCommonTest.java
@@ -19,6 +19,8 @@
 import org.apache.jackrabbit.oak.api.ContentRepository;
 import org.apache.jackrabbit.oak.plugins.index.PropertyIndexCommonTest;
 import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
 
 public class ElasticPropertyIndexCommonTest extends PropertyIndexCommonTest {
 
@@ -41,4 +43,11 @@
         setTraversalEnabled(false);
     }
 
+    @Override
+    @Test
+    @Ignore("OAK-9885")
+    public void likeQueriesWithEscapedChars() throws Exception {
+        super.likeQueriesWithEscapedChars();
+    }
+
 }
diff --git a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexCommonTest.java b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexCommonTest.java
index 8ea99ec..87f41d0 100644
--- a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexCommonTest.java
+++ b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexCommonTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.index;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.Tree;
@@ -28,7 +29,6 @@
 
 import java.util.Arrays;
 
-import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static javax.jcr.PropertyType.TYPENAME_DATE;
 import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROPDEF_PROP_NODE_NAME;
@@ -202,7 +202,7 @@
         String query = "select [jcr:path] from [oak:TestNode] where [propa] is not null";
         String explanation = explain(query);
         assertThat(explanation, containsString(propertyExistenceQueryWithNullCheckExpectedExplain()));
-        assertEventually(() -> assertQuery(query, asList("/test/a", "/test/b")));
+        assertEventually(() -> assertQuery(query, Arrays.asList("/test/a", "/test/b")));
     }
 
     protected String propertyExistenceQueryWithNullCheckExpectedExplain() {
@@ -269,12 +269,101 @@
                 Arrays.asList("/test/a", "/test/b", "/test/d")));
     }
 
+    @Test
+    public void likeQueriesWithString() throws Exception {
+        indexOptions.setIndex(
+                root,
+                "test1",
+                indexOptions.createIndex(indexOptions.createIndexDefinitionBuilder(), false, "propa")
+        );
+
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("propa", "humpty");
+        test.addChild("b").setProperty("propa", "dumpty");
+        test.addChild("c").setProperty("propa", "humpy");
+        test.addChild("d").setProperty("propa", "alice");
+        root.commit();
+
+        assertEventually(() -> assertQuery("select [jcr:path] from [nt:base] where propa like 'hum%'",
+                ImmutableList.of("/test/a", "/test/c")));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%ty'",
+                ImmutableList.of("/test/a", "/test/b"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%ump%'",
+                ImmutableList.of("/test/a", "/test/b", "/test/c"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '_ump%'",
+                ImmutableList.of("/test/a", "/test/b", "/test/c"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'a_ice%'",
+                ImmutableList.of("/test/d"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'a_i_e%'",
+                ImmutableList.of("/test/d"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '_____'",
+                ImmutableList.of("/test/c", "/test/d"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'h%y'",
+                ImmutableList.of("/test/a", "/test/c"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'humpy'",
+                ImmutableList.of("/test/c"));
+    }
+
+    @Test
+    public void likeQueriesWithEscapedChars() throws Exception {
+        indexOptions.setIndex(
+                root,
+                "test1",
+                indexOptions.createIndex(indexOptions.createIndexDefinitionBuilder(), false, "propa")
+        );
+        Tree test = root.getTree("/").addChild("test");
+        test.addChild("a").setProperty("propa", "foo%");
+        test.addChild("b").setProperty("propa", "%bar");
+        test.addChild("c").setProperty("propa", "foo%bar");
+        test.addChild("d").setProperty("propa", "foo_");
+        test.addChild("e").setProperty("propa", "_foo");
+        test.addChild("f").setProperty("propa", "foo_bar");
+        test.addChild("g").setProperty("propa", "foo%_bar");
+        test.addChild("h").setProperty("propa", "foo\\bar");
+        test.addChild("i").setProperty("propa", "foo\\\\%bar");
+        root.commit();
+
+        assertEventually(() ->
+                assertQuery("select [jcr:path] from [nt:base] where propa like 'foo%'",
+                        ImmutableList.of("/test/a", "/test/c", "/test/d", "/test/f", "/test/g", "/test/h", "/test/i"))
+        );
+
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo%'",
+                ImmutableList.of("/test/a", "/test/c", "/test/d", "/test/e", "/test/f", "/test/g", "/test/h", "/test/i"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo\\%'",
+                ImmutableList.of("/test/a"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\%'",
+                ImmutableList.of("/test/a"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\%%'",
+                ImmutableList.of("/test/a", "/test/c", "/test/g"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '\\%b%'",
+                ImmutableList.of("/test/b"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo_'",
+                ImmutableList.of("/test/a", "/test/d"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '_oo_'",
+                ImmutableList.of("/test/a", "/test/d"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo\\_'",
+                ImmutableList.of("/test/d"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\_'",
+                ImmutableList.of("/test/d"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\_%'",
+                ImmutableList.of("/test/d", "/test/f"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%oo\\%\\_%'",
+                ImmutableList.of("/test/g"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like 'foo\\\\bar'",
+                ImmutableList.of("/test/h"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%\\\\%'",
+                ImmutableList.of("/test/h", "/test/i"));
+        assertQuery("select [jcr:path] from [nt:base] where propa like '%\\\\\\%%'",
+                ImmutableList.of("/test/i"));
+    }
+
     protected String explain(String query) {
         String explain = "explain " + query;
         return executeQuery(explain, "JCR-SQL2").get(0);
     }
 
-    protected static Tree createNodeWithType(Tree t, String nodeName, String typeName){
+    protected static Tree createNodeWithType(Tree t, String nodeName, String typeName) {
         t = t.addChild(nodeName);
         t.setProperty(JcrConstants.JCR_PRIMARYTYPE, typeName, Type.NAME);
         return t;