diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search/270_wildcard_fieldtype_queries.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search/270_wildcard_fieldtype_queries.yml index cd7ac1450563f..05b6b2e5ed712 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search/270_wildcard_fieldtype_queries.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search/270_wildcard_fieldtype_queries.yml @@ -50,6 +50,12 @@ setup: id: 5 body: my_field: "AbCd" + - do: + index: + index: test + id: 6 + body: + other_field: "test" - do: indices.refresh: {} @@ -198,3 +204,26 @@ setup: my_field: value: ".*06-08.*Cluster-Manager Node.*" - match: { hits.total.value: 0 } + +--- +"wildcard match-all works": + - do: + search: + index: test + body: + query: + wildcard: + my_field: + value: "*" + - match: { hits.total.value: 5 } +--- +"regexp match-all works": + - do: + search: + index: test + body: + query: + regexp: + my_field: + value: ".*" + - match: { hits.total.value: 5 } diff --git a/server/src/main/java/org/opensearch/index/mapper/WildcardFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/WildcardFieldMapper.java index 3739a07e425bd..4998a822917b4 100644 --- a/server/src/main/java/org/opensearch/index/mapper/WildcardFieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/WildcardFieldMapper.java @@ -23,6 +23,7 @@ import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.QueryVisitor; @@ -448,14 +449,20 @@ public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, bo }; } - return new WildcardMatchingQuery( - name(), - matchAllTermsQuery(name(), getRequiredNGrams(finalValue)), - matchPredicate, - value, - context, - this - ); + Set requiredNGrams = getRequiredNGrams(finalValue); + Query approximation; + if (requiredNGrams.isEmpty()) { + // This only happens when all characters are wildcard characters (* or ?), + // or it's the empty string. + if (value.length() == 0 || value.contains("?")) { + approximation = this.existsQuery(context); + } else { + return existsQuery(context); + } + } else { + approximation = matchAllTermsQuery(name(), requiredNGrams); + } + return new WildcardMatchingQuery(name(), approximation, matchPredicate, value, context, this); } // Package-private for testing @@ -540,10 +547,23 @@ public Query regexpQuery( Automaton automaton = regExp.toAutomaton(maxDeterminizedStates); CompiledAutomaton compiledAutomaton = new CompiledAutomaton(automaton); - return new WildcardMatchingQuery(name(), regexpToQuery(name(), regExp), s -> { - BytesRef valueBytes = BytesRefs.toBytesRef(s); - return compiledAutomaton.runAutomaton.run(valueBytes.bytes, valueBytes.offset, valueBytes.length); - }, "/" + value + "/", context, this); + Predicate regexpPredicate; + if (compiledAutomaton.type == CompiledAutomaton.AUTOMATON_TYPE.ALL) { + return existsQuery(context); + } else if (compiledAutomaton.type == CompiledAutomaton.AUTOMATON_TYPE.NONE) { + return new MatchNoDocsQuery("Regular expression matches nothing"); + } else { + regexpPredicate = s -> { + BytesRef valueBytes = BytesRefs.toBytesRef(s); + return compiledAutomaton.runAutomaton.run(valueBytes.bytes, valueBytes.offset, valueBytes.length); + }; + } + + Query approximation = regexpToQuery(name(), regExp); + if (approximation instanceof MatchAllDocsQuery) { + approximation = existsQuery(context); + } + return new WildcardMatchingQuery(name(), approximation, regexpPredicate, "/" + value + "/", context, this); } /** @@ -602,6 +622,8 @@ private static Query regexpToQuery(String fieldName, RegExp regExp) { } if (query.clauses().size() == 1) { return query.iterator().next().getQuery(); + } else if (query.clauses().size() == 0) { + return new MatchAllDocsQuery(); } return query; } @@ -704,7 +726,7 @@ private WildcardMatchingQuery( @Override public String toString(String s) { - return "WildcardMatchingQuery(" + fieldName + "\"" + patternString + "\")"; + return "WildcardMatchingQuery(" + fieldName + ":\"" + patternString + "\")"; } @Override diff --git a/server/src/test/java/org/opensearch/index/mapper/WildcardFieldTypeTests.java b/server/src/test/java/org/opensearch/index/mapper/WildcardFieldTypeTests.java index f03364054c166..cd2a23cf94c37 100644 --- a/server/src/test/java/org/opensearch/index/mapper/WildcardFieldTypeTests.java +++ b/server/src/test/java/org/opensearch/index/mapper/WildcardFieldTypeTests.java @@ -149,4 +149,28 @@ public void testRegexpQuery() { assertTrue(actualMatchingQuery.getSecondPhaseMatcher().test("abcdjk")); assertTrue(actualMatchingQuery.getSecondPhaseMatcher().test("abefqwertyhi")); } + + public void testWildcardMatchAll() { + String pattern = "???"; + MappedFieldType ft = new WildcardFieldMapper.WildcardFieldType("field"); + Query actual = ft.wildcardQuery(pattern, null, null); + assertEquals(new WildcardFieldMapper.WildcardMatchingQuery("field", ft.existsQuery(null), "???"), actual); + + pattern = "*"; + actual = ft.wildcardQuery(pattern, null, null); + assertEquals(ft.existsQuery(null), actual); + } + + public void testRegexpMatchAll() { + // The following matches any string of length exactly 3. We do need to evaluate the predicate. + String pattern = "..."; + MappedFieldType ft = new WildcardFieldMapper.WildcardFieldType("field"); + Query actual = ft.regexpQuery(pattern, 0, 0, 1000, null, null); + assertEquals(new WildcardFieldMapper.WildcardMatchingQuery("field", ft.existsQuery(null), "/.../"), actual); + + // The following pattern has a predicate that matches everything. We can just return the field exists query. + pattern = ".*"; + actual = ft.regexpQuery(pattern, 0, 0, 1000, null, null); + assertEquals(ft.existsQuery(null), actual); + } }