Просмотр исходного кода

[8.19] Use binarySearch over terms instead of automaton in termsQueries on Wildcard fields (#128978)

Ignacio Vera 4 месяцев назад
Родитель
Сommit
99fdebf447

+ 0 - 151
x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/BinaryDvConfirmedAutomatonQuery.java

@@ -1,151 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-package org.elasticsearch.xpack.wildcard.mapper;
-
-import org.apache.lucene.index.BinaryDocValues;
-import org.apache.lucene.index.DocValues;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.ConstantScoreScorer;
-import org.apache.lucene.search.ConstantScoreWeight;
-import org.apache.lucene.search.DocIdSetIterator;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.QueryVisitor;
-import org.apache.lucene.search.ScoreMode;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.TwoPhaseIterator;
-import org.apache.lucene.search.Weight;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.automaton.Automaton;
-import org.apache.lucene.util.automaton.ByteRunAutomaton;
-import org.elasticsearch.common.io.stream.ByteArrayStreamInput;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Query that runs an Automaton across all binary doc values (but only for docs that also
- * match a provided approximation query which is key to getting good performance).
- */
-public class BinaryDvConfirmedAutomatonQuery extends Query {
-
-    private final String field;
-    private final String matchPattern;
-    private final ByteRunAutomaton bytesMatcher;
-    private final Query approxQuery;
-
-    public BinaryDvConfirmedAutomatonQuery(Query approximation, String field, String matchPattern, Automaton automaton) {
-        this.approxQuery = approximation;
-        this.field = field;
-        this.matchPattern = matchPattern;
-        bytesMatcher = new ByteRunAutomaton(automaton);
-    }
-
-    private BinaryDvConfirmedAutomatonQuery(Query approximation, String field, String matchPattern, ByteRunAutomaton bytesMatcher) {
-        this.approxQuery = approximation;
-        this.field = field;
-        this.matchPattern = matchPattern;
-        this.bytesMatcher = bytesMatcher;
-    }
-
-    @Override
-    public Query rewrite(IndexSearcher searcher) throws IOException {
-        Query approxRewrite = approxQuery.rewrite(searcher);
-        if (approxQuery != approxRewrite) {
-            return new BinaryDvConfirmedAutomatonQuery(approxRewrite, field, matchPattern, bytesMatcher);
-        }
-        return this;
-    }
-
-    @Override
-    public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
-        final Weight approxWeight = approxQuery.createWeight(searcher, scoreMode, boost);
-
-        return new ConstantScoreWeight(this, boost) {
-
-            @Override
-            public Scorer scorer(LeafReaderContext context) throws IOException {
-                ByteArrayStreamInput bytes = new ByteArrayStreamInput();
-                final BinaryDocValues values = DocValues.getBinary(context.reader(), field);
-                Scorer approxScorer = approxWeight.scorer(context);
-                if (approxScorer == null) {
-                    // No matches to be had
-                    return null;
-                }
-                DocIdSetIterator approxDisi = approxScorer.iterator();
-                TwoPhaseIterator twoPhase = new TwoPhaseIterator(approxDisi) {
-                    @Override
-                    public boolean matches() throws IOException {
-                        if (values.advanceExact(approxDisi.docID()) == false) {
-                            // Can happen when approxQuery resolves to some form of MatchAllDocs expression
-                            return false;
-                        }
-                        BytesRef arrayOfValues = values.binaryValue();
-                        bytes.reset(arrayOfValues.bytes);
-                        bytes.setPosition(arrayOfValues.offset);
-
-                        int size = bytes.readVInt();
-                        for (int i = 0; i < size; i++) {
-                            int valLength = bytes.readVInt();
-                            if (bytesMatcher.run(arrayOfValues.bytes, bytes.getPosition(), valLength)) {
-                                return true;
-                            }
-                            bytes.skipBytes(valLength);
-                        }
-                        return false;
-                    }
-
-                    @Override
-                    public float matchCost() {
-                        // TODO: how can we compute this?
-                        return 1000f;
-                    }
-                };
-                return new ConstantScoreScorer(this, score(), scoreMode, twoPhase);
-            }
-
-            @Override
-            public boolean isCacheable(LeafReaderContext ctx) {
-                return true;
-            }
-        };
-    }
-
-    @Override
-    public String toString(String field) {
-        return field + ":" + matchPattern;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (sameClassAs(obj) == false) {
-            return false;
-        }
-        BinaryDvConfirmedAutomatonQuery other = (BinaryDvConfirmedAutomatonQuery) obj;
-        return Objects.equals(field, other.field)
-            && Objects.equals(matchPattern, other.matchPattern)
-            && Objects.equals(bytesMatcher, other.bytesMatcher)
-            && Objects.equals(approxQuery, other.approxQuery);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(classHash(), field, matchPattern, bytesMatcher, approxQuery);
-    }
-
-    Query getApproximationQuery() {
-        return approxQuery;
-    }
-
-    @Override
-    public void visit(QueryVisitor visitor) {
-        if (visitor.acceptField(field)) {
-            visitor.visitLeaf(this);
-        }
-    }
-}

+ 288 - 0
x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/BinaryDvConfirmedQuery.java

@@ -0,0 +1,288 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.wildcard.mapper;
+
+import org.apache.lucene.index.BinaryDocValues;
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.QueryVisitor;
+import org.apache.lucene.search.ScoreMode;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.ScorerSupplier;
+import org.apache.lucene.search.TwoPhaseIterator;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.automaton.Automaton;
+import org.apache.lucene.util.automaton.ByteRunAutomaton;
+import org.elasticsearch.common.io.stream.ByteArrayStreamInput;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Query that provided an arbitrary match across all binary doc values (but only for docs that also
+ * match a provided approximation query which is key to getting good performance).
+ */
+
+abstract class BinaryDvConfirmedQuery extends Query {
+
+    protected final String field;
+    protected final Query approxQuery;
+
+    private BinaryDvConfirmedQuery(Query approximation, String field) {
+        this.approxQuery = approximation;
+        this.field = field;
+    }
+
+    /**
+     * Returns a query that runs the provided Automaton across all binary doc values (but only for docs that also
+     * match a provided approximation query which is key to getting good performance).
+     */
+    public static Query fromAutomaton(Query approximation, String field, String matchPattern, Automaton automaton) {
+        return new BinaryDvConfirmedAutomatonQuery(approximation, field, matchPattern, automaton);
+    }
+
+    /**
+     * Returns a query that checks for equality of at leat one of the provided terms across
+     * all binary doc values (but only for docs that also match a provided approximation query which
+     * is key to getting good performance).
+     */
+    public static Query fromTerms(Query approximation, String field, BytesRef... terms) {
+        Arrays.sort(terms, BytesRef::compareTo);
+        return new BinaryDvConfirmedTermsQuery(approximation, field, terms);
+    }
+
+    protected abstract boolean matchesBinaryDV(ByteArrayStreamInput bytes, BytesRef bytesRef, BytesRef scratch) throws IOException;
+
+    protected abstract Query rewrite(Query approxRewrite) throws IOException;
+
+    @Override
+    public Query rewrite(IndexSearcher searcher) throws IOException {
+        Query approxRewrite = approxQuery.rewrite(searcher);
+        if (approxQuery != approxRewrite) {
+            return rewrite(approxRewrite);
+        }
+        return this;
+    }
+
+    @Override
+    public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
+        final Weight approxWeight = approxQuery.createWeight(searcher, scoreMode, boost);
+
+        return new ConstantScoreWeight(this, boost) {
+
+            @Override
+            public Scorer scorer(LeafReaderContext context) throws IOException {
+                ScorerSupplier scorerSupplier = scorerSupplier(context);
+                if (scorerSupplier == null) {
+                    return null;
+                }
+                return scorerSupplier.get(Long.MAX_VALUE);
+            }
+
+            @Override
+            public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
+                ScorerSupplier approxScorerSupplier = approxWeight.scorerSupplier(context);
+                if (approxScorerSupplier == null) {
+                    // No matches to be had
+                    return null;
+                }
+                final ByteArrayStreamInput bytes = new ByteArrayStreamInput();
+                final BytesRef scratch = new BytesRef();
+                final BinaryDocValues values = DocValues.getBinary(context.reader(), field);
+                final Weight thisWeight = this;
+                return new ScorerSupplier() {
+                    @Override
+                    public Scorer get(long leadCost) throws IOException {
+                        final Scorer approxScorer = approxScorerSupplier.get(leadCost);
+                        final DocIdSetIterator approxDisi = approxScorer.iterator();
+                        final TwoPhaseIterator twoPhase = new TwoPhaseIterator(approxDisi) {
+                            @Override
+                            public boolean matches() throws IOException {
+                                if (values.advanceExact(approxDisi.docID()) == false) {
+                                    // Can happen when approxQuery resolves to some form of MatchAllDocs expression
+                                    return false;
+                                }
+                                final BytesRef bytesRef = values.binaryValue();
+                                bytes.reset(bytesRef.bytes, bytesRef.offset, bytesRef.length);
+                                return matchesBinaryDV(bytes, bytesRef, scratch);
+                            }
+
+                            @Override
+                            public float matchCost() {
+                                // TODO: how can we compute this?
+                                return 1000f;
+                            }
+                        };
+                        return new ConstantScoreScorer(thisWeight, score(), scoreMode, twoPhase);
+                    }
+
+                    @Override
+                    public long cost() {
+                        return approxScorerSupplier.cost();
+                    }
+                };
+            }
+
+            @Override
+            public boolean isCacheable(LeafReaderContext ctx) {
+                return true;
+            }
+        };
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (sameClassAs(obj) == false) {
+            return false;
+        }
+        BinaryDvConfirmedQuery other = (BinaryDvConfirmedQuery) obj;
+        return Objects.equals(field, other.field) && Objects.equals(approxQuery, other.approxQuery);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(classHash(), field, approxQuery);
+    }
+
+    Query getApproximationQuery() {
+        return approxQuery;
+    }
+
+    @Override
+    public void visit(QueryVisitor visitor) {
+        if (visitor.acceptField(field)) {
+            visitor.visitLeaf(this);
+        }
+    }
+
+    private static class BinaryDvConfirmedAutomatonQuery extends BinaryDvConfirmedQuery {
+
+        private final ByteRunAutomaton byteRunAutomaton;
+        private final String matchPattern;
+
+        private BinaryDvConfirmedAutomatonQuery(Query approximation, String field, String matchPattern, Automaton automaton) {
+            this(approximation, field, matchPattern, new ByteRunAutomaton(automaton));
+        }
+
+        private BinaryDvConfirmedAutomatonQuery(Query approximation, String field, String matchPattern, ByteRunAutomaton byteRunAutomaton) {
+            super(approximation, field);
+            this.matchPattern = matchPattern;
+            this.byteRunAutomaton = byteRunAutomaton;
+        }
+
+        @Override
+        protected boolean matchesBinaryDV(ByteArrayStreamInput bytes, BytesRef bytesRef, BytesRef scratch) throws IOException {
+            int size = bytes.readVInt();
+            for (int i = 0; i < size; i++) {
+                int valLength = bytes.readVInt();
+                if (byteRunAutomaton.run(bytesRef.bytes, bytes.getPosition(), valLength)) {
+                    return true;
+                }
+                bytes.skipBytes(valLength);
+            }
+            return false;
+        }
+
+        @Override
+        protected Query rewrite(Query approxRewrite) {
+            return new BinaryDvConfirmedAutomatonQuery(approxRewrite, field, matchPattern, byteRunAutomaton);
+        }
+
+        @Override
+        public String toString(String field) {
+            return field + ":" + matchPattern;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            if (super.equals(o) == false) return false;
+            BinaryDvConfirmedAutomatonQuery other = (BinaryDvConfirmedAutomatonQuery) o;
+            return Objects.equals(byteRunAutomaton, other.byteRunAutomaton) && Objects.equals(matchPattern, other.matchPattern);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(super.hashCode(), matchPattern, byteRunAutomaton);
+        }
+    }
+
+    private static class BinaryDvConfirmedTermsQuery extends BinaryDvConfirmedQuery {
+
+        private final BytesRef[] terms;
+
+        private BinaryDvConfirmedTermsQuery(Query approximation, String field, BytesRef[] terms) {
+            super(approximation, field);
+            // terms must already be sorted
+            this.terms = terms;
+        }
+
+        @Override
+        protected boolean matchesBinaryDV(ByteArrayStreamInput bytes, BytesRef bytesRef, BytesRef scratch) throws IOException {
+            scratch.bytes = bytesRef.bytes;
+            final int size = bytes.readVInt();
+            for (int i = 0; i < size; i++) {
+                final int valLength = bytes.readVInt();
+                scratch.offset = bytes.getPosition();
+                scratch.length = valLength;
+                if (terms.length == 1) {
+                    if (terms[0].bytesEquals(scratch)) {
+                        return true;
+                    }
+                } else {
+                    final int pos = Arrays.binarySearch(terms, scratch, BytesRef::compareTo);
+                    if (pos >= 0) {
+                        assert terms[pos].bytesEquals(scratch) : "Expected term at position " + pos + " to match scratch, but it did not.";
+                        return true;
+                    }
+                }
+                bytes.skipBytes(valLength);
+            }
+            assert bytes.available() == 0 : "Expected no bytes left to read, but found " + bytes.available();
+            return false;
+        }
+
+        @Override
+        protected Query rewrite(Query approxRewrite) {
+            return new BinaryDvConfirmedTermsQuery(approxRewrite, field, terms);
+        }
+
+        @Override
+        public String toString(String field) {
+            StringBuilder builder = new StringBuilder(field + ": [");
+            for (int i = 0; i < terms.length; i++) {
+                if (i > 0) {
+                    builder.append(", ");
+                }
+                builder.append(terms[i].utf8ToString());
+            }
+            builder.append("]");
+            return builder.toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            if (super.equals(o) == false) return false;
+            BinaryDvConfirmedTermsQuery that = (BinaryDvConfirmedTermsQuery) o;
+            return Arrays.equals(this.terms, that.terms);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(super.hashCode(), Arrays.hashCode(terms));
+        }
+    }
+}

+ 50 - 30
x-pack/plugin/wildcard/src/main/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapper.java

@@ -292,10 +292,45 @@ public class WildcardFieldMapper extends FieldMapper {
 
         @Override
         public Query wildcardQuery(String wildcardPattern, RewriteMethod method, boolean caseInsensitive, SearchExecutionContext context) {
+            BooleanQuery.Builder rewritten = new BooleanQuery.Builder();
+            Integer numClauses = getApproxWildCardQuery(wildcardPattern, rewritten);
+            if (numClauses == null) {
+                // We have no concrete characters and we're not a pure length query e.g. ???
+                return new FieldExistsQuery(name());
+            }
+            Automaton automaton = caseInsensitive
+                ? AutomatonQueries.toCaseInsensitiveWildcardAutomaton(new Term(name(), wildcardPattern))
+                : WildcardQuery.toAutomaton(new Term(name(), wildcardPattern));
+            if (numClauses > 0) {
+                // We can accelerate execution with the ngram query
+                BooleanQuery approxQuery = rewritten.build();
+                return BinaryDvConfirmedQuery.fromAutomaton(approxQuery, name(), wildcardPattern, automaton);
+            } else {
+                return BinaryDvConfirmedQuery.fromAutomaton(new MatchAllDocsQuery(), name(), wildcardPattern, automaton);
+            }
+        }
 
-            String ngramIndexPattern = addLineEndChars(wildcardPattern);
+        private Integer getApproxWildCardQuery(String wildcardPattern, BooleanQuery.Builder rewritten) {
             // Break search term into tokens
             Set<String> tokens = new LinkedHashSet<>();
+            boolean matchAll = breakIntoTokens(wildcardPattern, tokens);
+            if (matchAll) {
+                // We have no concrete characters and we're not a pure length query e.g. ???
+                return null;
+            }
+            int clauseCount = 0;
+            for (String string : tokens) {
+                if (clauseCount >= MAX_CLAUSES_IN_APPROXIMATION_QUERY) {
+                    break;
+                }
+                addClause(string, rewritten, Occur.MUST);
+                clauseCount++;
+            }
+            return clauseCount;
+        }
+
+        private boolean breakIntoTokens(String wildcardPattern, Set<String> tokens) {
+            String ngramIndexPattern = addLineEndChars(wildcardPattern);
             StringBuilder sequence = new StringBuilder();
             int numWildcardChars = 0;
             int numWildcardStrings = 0;
@@ -337,29 +372,7 @@ public class WildcardFieldMapper extends FieldMapper {
             if (sequence.length() > 0) {
                 getNgramTokens(tokens, sequence.toString());
             }
-
-            BooleanQuery.Builder rewritten = new BooleanQuery.Builder();
-            int clauseCount = 0;
-            for (String string : tokens) {
-                if (clauseCount >= MAX_CLAUSES_IN_APPROXIMATION_QUERY) {
-                    break;
-                }
-                addClause(string, rewritten, Occur.MUST);
-                clauseCount++;
-            }
-            Automaton automaton = caseInsensitive
-                ? AutomatonQueries.toCaseInsensitiveWildcardAutomaton(new Term(name(), wildcardPattern))
-                : WildcardQuery.toAutomaton(new Term(name(), wildcardPattern));
-            if (clauseCount > 0) {
-                // We can accelerate execution with the ngram query
-                BooleanQuery approxQuery = rewritten.build();
-                return new BinaryDvConfirmedAutomatonQuery(approxQuery, name(), wildcardPattern, automaton);
-            } else if (numWildcardChars == 0 || numWildcardStrings > 0) {
-                // We have no concrete characters and we're not a pure length query e.g. ???
-                return new FieldExistsQuery(name());
-            }
-            return new BinaryDvConfirmedAutomatonQuery(new MatchAllDocsQuery(), name(), wildcardPattern, automaton);
-
+            return tokens.isEmpty() && (numWildcardChars == 0 || numWildcardStrings > 0);
         }
 
         @Override
@@ -393,7 +406,7 @@ public class WildcardFieldMapper extends FieldMapper {
             Automaton automaton = regex.toAutomaton(maxDeterminizedStates);
 
             // We can accelerate execution with the ngram query
-            return new BinaryDvConfirmedAutomatonQuery(approxNgramQuery, name(), value, automaton);
+            return BinaryDvConfirmedQuery.fromAutomaton(approxNgramQuery, name(), value, automaton);
         }
 
         // Convert a regular expression to a simplified query consisting of BooleanQuery and TermQuery objects
@@ -699,9 +712,9 @@ public class WildcardFieldMapper extends FieldMapper {
             Automaton automaton = TermRangeQuery.toAutomaton(lower, upper, includeLower, includeUpper);
 
             if (accelerationQuery == null) {
-                return new BinaryDvConfirmedAutomatonQuery(new MatchAllDocsQuery(), name(), lower + "-" + upper, automaton);
+                return BinaryDvConfirmedQuery.fromAutomaton(new MatchAllDocsQuery(), name(), lower + "-" + upper, automaton);
             }
-            return new BinaryDvConfirmedAutomatonQuery(accelerationQuery, name(), lower + "-" + upper, automaton);
+            return BinaryDvConfirmedQuery.fromAutomaton(accelerationQuery, name(), lower + "-" + upper, automaton);
         }
 
         @Override
@@ -790,10 +803,10 @@ public class WildcardFieldMapper extends FieldMapper {
                         rewriteMethod
                     );
                 if (ngramQ.clauses().size() == 0) {
-                    return new BinaryDvConfirmedAutomatonQuery(new MatchAllDocsQuery(), name(), searchTerm, fq.getAutomata().automaton);
+                    return BinaryDvConfirmedQuery.fromAutomaton(new MatchAllDocsQuery(), name(), searchTerm, fq.getAutomata().automaton);
                 }
 
-                return new BinaryDvConfirmedAutomatonQuery(ngramQ, name(), searchTerm, fq.getAutomata().automaton);
+                return BinaryDvConfirmedQuery.fromAutomaton(ngramQ, name(), searchTerm, fq.getAutomata().automaton);
             } catch (IOException ioe) {
                 throw new ElasticsearchParseException("Error parsing wildcard field fuzzy string [" + searchTerm + "]");
             }
@@ -812,7 +825,14 @@ public class WildcardFieldMapper extends FieldMapper {
         @Override
         public Query termQuery(Object value, SearchExecutionContext context) {
             String searchTerm = BytesRefs.toString(value);
-            return wildcardQuery(escapeWildcardSyntax(searchTerm), MultiTermQuery.CONSTANT_SCORE_REWRITE, false, context);
+            BooleanQuery.Builder rewritten = new BooleanQuery.Builder();
+            Integer numClauses = getApproxWildCardQuery(escapeWildcardSyntax(searchTerm), rewritten);
+            if (numClauses != null && numClauses > 0) {
+                Query approxQuery = rewritten.build();
+                return BinaryDvConfirmedQuery.fromTerms(approxQuery, name(), new BytesRef(searchTerm));
+            } else {
+                return BinaryDvConfirmedQuery.fromTerms(new MatchAllDocsQuery(), name(), new BytesRef(searchTerm));
+            }
         }
 
         private static String escapeWildcardSyntax(String term) {

+ 5 - 13
x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/TermsQueryTests.java

@@ -7,14 +7,11 @@
 
 package org.elasticsearch.xpack.wildcard.mapper;
 
-import org.apache.lucene.search.ConstantScoreQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.index.mapper.MapperService;
-import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.TermsQueryBuilder;
-import org.elasticsearch.index.query.WildcardQueryBuilder;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.test.AbstractBuilderTestCase;
 import org.elasticsearch.xpack.wildcard.Wildcard;
@@ -52,9 +49,9 @@ public class TermsQueryTests extends AbstractBuilderTestCase {
         termsQueryBuilder = termsQueryBuilder.rewrite(createQueryRewriteContext());
         Query actual = termsQueryBuilder.toQuery(createSearchExecutionContext());
 
-        QueryBuilder queryBuilder = new BoolQueryBuilder().should(new WildcardQueryBuilder("mapped_wildcard", "duplicate"));
+        QueryBuilder queryBuilder = new TermsQueryBuilder("mapped_wildcard", "duplicate");
         queryBuilder = queryBuilder.rewrite(createQueryRewriteContext());
-        Query expected = new ConstantScoreQuery(queryBuilder.toQuery(createSearchExecutionContext()));
+        Query expected = queryBuilder.toQuery(createSearchExecutionContext());
 
         assertEquals(expected, actual);
     }
@@ -79,14 +76,9 @@ public class TermsQueryTests extends AbstractBuilderTestCase {
         Query actual = termsQueryBuilder.toQuery(createSearchExecutionContext());
 
         Set<String> ordered = new HashSet<>(randomTerms);
-        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
-        for (String randomTerm : ordered) {
-            QueryBuilder wildcardQueryBuilder = new WildcardQueryBuilder("mapped_wildcard", randomTerm);
-            wildcardQueryBuilder = wildcardQueryBuilder.rewrite(createQueryRewriteContext());
-            boolQueryBuilder.should(wildcardQueryBuilder);
-        }
-        QueryBuilder expectedQueryBuilder = boolQueryBuilder.rewrite(createQueryRewriteContext());
-        Query expected = new ConstantScoreQuery(expectedQueryBuilder.toQuery(createSearchExecutionContext()));
+        QueryBuilder queryBuilder = new TermsQueryBuilder("mapped_wildcard", ordered.toArray(new String[0]));
+        queryBuilder = queryBuilder.rewrite(createQueryRewriteContext());
+        Query expected = queryBuilder.toQuery(createSearchExecutionContext());
 
         assertEquals(expected, actual);
     }

+ 20 - 23
x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java

@@ -601,7 +601,7 @@ public class WildcardFieldMapperTests extends MapperTestCase {
             "\\D" };
         for (String regex : matchAllButVerifyTests) {
             Query wildcardFieldQuery = wildcardFieldType.fieldType().regexpQuery(regex, RegExp.ALL, 0, 20000, null, MOCK_CONTEXT);
-            BinaryDvConfirmedAutomatonQuery q = (BinaryDvConfirmedAutomatonQuery) wildcardFieldQuery;
+            BinaryDvConfirmedQuery q = (BinaryDvConfirmedQuery) wildcardFieldQuery;
             Query approximationQuery = unwrapAnyBoost(q.getApproximationQuery());
             approximationQuery = getSimplifiedApproximationQuery(q.getApproximationQuery());
             assertTrue(
@@ -657,7 +657,7 @@ public class WildcardFieldMapperTests extends MapperTestCase {
             String expectedAccelerationQueryString = test[1].replaceAll("_", "" + WildcardFieldMapper.TOKEN_START_OR_END_CHAR);
             Query wildcardFieldQuery = wildcardFieldType.fieldType().wildcardQuery(pattern, null, MOCK_CONTEXT);
             testExpectedAccelerationQuery(pattern, wildcardFieldQuery, expectedAccelerationQueryString);
-            assertTrue(unwrapAnyConstantScore(wildcardFieldQuery) instanceof BinaryDvConfirmedAutomatonQuery);
+            assertTrue(unwrapAnyConstantScore(wildcardFieldQuery) instanceof BinaryDvConfirmedQuery);
         }
 
         // TODO All these expressions have no acceleration at all and could be improved
@@ -665,7 +665,7 @@ public class WildcardFieldMapperTests extends MapperTestCase {
         for (String pattern : slowPatterns) {
             Query wildcardFieldQuery = wildcardFieldType.fieldType().wildcardQuery(pattern, null, MOCK_CONTEXT);
             wildcardFieldQuery = unwrapAnyConstantScore(wildcardFieldQuery);
-            BinaryDvConfirmedAutomatonQuery q = (BinaryDvConfirmedAutomatonQuery) wildcardFieldQuery;
+            BinaryDvConfirmedQuery q = (BinaryDvConfirmedQuery) wildcardFieldQuery;
             assertTrue(
                 pattern + " was not as slow as we assumed " + formatQuery(wildcardFieldQuery),
                 q.getApproximationQuery() instanceof MatchAllDocsQuery
@@ -674,34 +674,31 @@ public class WildcardFieldMapperTests extends MapperTestCase {
 
     }
 
-    public void testQueryCachingEquality() throws IOException, ParseException {
+    public void testQueryCachingEqualityFromAutomaton() {
         String pattern = "A*b*B?a";
         // Case sensitivity matters when it comes to caching
         Automaton caseSensitiveAutomaton = WildcardQuery.toAutomaton(new Term("field", pattern));
         Automaton caseInSensitiveAutomaton = AutomatonQueries.toCaseInsensitiveWildcardAutomaton(new Term("field", pattern));
-        BinaryDvConfirmedAutomatonQuery csQ = new BinaryDvConfirmedAutomatonQuery(
-            new MatchAllDocsQuery(),
-            "field",
-            pattern,
-            caseSensitiveAutomaton
-        );
-        BinaryDvConfirmedAutomatonQuery ciQ = new BinaryDvConfirmedAutomatonQuery(
-            new MatchAllDocsQuery(),
-            "field",
-            pattern,
-            caseInSensitiveAutomaton
-        );
+        Query csQ = BinaryDvConfirmedQuery.fromAutomaton(new MatchAllDocsQuery(), "field", pattern, caseSensitiveAutomaton);
+        Query ciQ = BinaryDvConfirmedQuery.fromAutomaton(new MatchAllDocsQuery(), "field", pattern, caseInSensitiveAutomaton);
         assertNotEquals(csQ, ciQ);
         assertNotEquals(csQ.hashCode(), ciQ.hashCode());
 
         // Same query should be equal
         Automaton caseSensitiveAutomaton2 = WildcardQuery.toAutomaton(new Term("field", pattern));
-        BinaryDvConfirmedAutomatonQuery csQ2 = new BinaryDvConfirmedAutomatonQuery(
-            new MatchAllDocsQuery(),
-            "field",
-            pattern,
-            caseSensitiveAutomaton2
-        );
+        Query csQ2 = BinaryDvConfirmedQuery.fromAutomaton(new MatchAllDocsQuery(), "field", pattern, caseSensitiveAutomaton2);
+        assertEquals(csQ, csQ2);
+        assertEquals(csQ.hashCode(), csQ2.hashCode());
+    }
+
+    public void testQueryCachingEqualityFromTerms() {
+        Query csQ = BinaryDvConfirmedQuery.fromTerms(new MatchAllDocsQuery(), "field", new BytesRef("termA"));
+        Query ciQ = BinaryDvConfirmedQuery.fromTerms(new MatchAllDocsQuery(), "field", new BytesRef("termB"));
+        assertNotEquals(csQ, ciQ);
+        assertNotEquals(csQ.hashCode(), ciQ.hashCode());
+
+        // Same query should be equal
+        Query csQ2 = BinaryDvConfirmedQuery.fromTerms(new MatchAllDocsQuery(), "field", new BytesRef("termA"));
         assertEquals(csQ, csQ2);
         assertEquals(csQ.hashCode(), csQ2.hashCode());
     }
@@ -879,7 +876,7 @@ public class WildcardFieldMapperTests extends MapperTestCase {
 
     void testExpectedAccelerationQuery(String regex, Query combinedQuery, Query expectedAccelerationQuery) throws ParseException,
         IOException {
-        BinaryDvConfirmedAutomatonQuery cq = (BinaryDvConfirmedAutomatonQuery) unwrapAnyConstantScore(combinedQuery);
+        BinaryDvConfirmedQuery cq = (BinaryDvConfirmedQuery) unwrapAnyConstantScore(combinedQuery);
         Query approximationQuery = cq.getApproximationQuery();
         approximationQuery = getSimplifiedApproximationQuery(approximationQuery);
         String message = "regex: "