Browse Source

QueryString and SimpleQueryString Graph Support (#22541)

Add support for graph token streams to "query_String" and
"simple_query_string" queries.
Matt Weber 8 years ago
parent
commit
609d2aab15

+ 35 - 13
core/src/main/java/org/apache/lucene/queryparser/classic/MapperQueryParser.java

@@ -19,6 +19,9 @@
 
 package org.apache.lucene.queryparser.classic;
 
+import static java.util.Collections.unmodifiableMap;
+import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded;
+
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
@@ -30,6 +33,7 @@ import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.DisjunctionMaxQuery;
 import org.apache.lucene.search.FuzzyQuery;
+import org.apache.lucene.search.GraphQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.MultiPhraseQuery;
 import org.apache.lucene.search.PhraseQuery;
@@ -55,9 +59,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
-import static java.util.Collections.unmodifiableMap;
-import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded;
-
 /**
  * A query parser that uses the {@link MapperService} in order to build smarter
  * queries based on the mapping information.
@@ -739,27 +740,48 @@ public class MapperQueryParser extends AnalyzingQueryParser {
 
     private Query applySlop(Query q, int slop) {
         if (q instanceof PhraseQuery) {
-            PhraseQuery pq = (PhraseQuery) q;
-            PhraseQuery.Builder builder = new PhraseQuery.Builder();
-            builder.setSlop(slop);
-            final Term[] terms = pq.getTerms();
-            final int[] positions = pq.getPositions();
-            for (int i = 0; i < terms.length; ++i) {
-                builder.add(terms[i], positions[i]);
-            }
-            pq = builder.build();
             //make sure that the boost hasn't been set beforehand, otherwise we'd lose it
             assert q instanceof BoostQuery == false;
-            return pq;
+            return addSlopToPhrase((PhraseQuery) q, slop);
         } else if (q instanceof MultiPhraseQuery) {
             MultiPhraseQuery.Builder builder = new MultiPhraseQuery.Builder((MultiPhraseQuery) q);
             builder.setSlop(slop);
             return builder.build();
+        } else if (q instanceof GraphQuery && ((GraphQuery) q).hasPhrase()) {
+            // we have a graph query that has at least one phrase sub-query
+            // re-build and set slop on all phrase queries
+            List<Query> oldQueries = ((GraphQuery) q).getQueries();
+            Query[] queries = new Query[oldQueries.size()];
+            for (int i = 0; i < queries.length; i++) {
+                Query oldQuery = oldQueries.get(i);
+                if (oldQuery instanceof PhraseQuery) {
+                    queries[i] = addSlopToPhrase((PhraseQuery) oldQuery, slop);
+                } else {
+                    queries[i] = oldQuery;
+                }
+            }
+
+            return new GraphQuery(queries);
         } else {
             return q;
         }
     }
 
+    /**
+     * Rebuild a phrase query with a slop value
+     */
+    private PhraseQuery addSlopToPhrase(PhraseQuery query, int slop) {
+        PhraseQuery.Builder builder = new PhraseQuery.Builder();
+        builder.setSlop(slop);
+        final Term[] terms = query.getTerms();
+        final int[] positions = query.getPositions();
+        for (int i = 0; i < terms.length; ++i) {
+            builder.add(terms[i], positions[i]);
+        }
+
+        return builder.build();
+    }
+
     private Collection<String> extractMultiFields(String field) {
         Collection<String> fields;
         if (field != null) {

+ 29 - 0
core/src/main/java/org/elasticsearch/common/lucene/search/Queries.java

@@ -20,10 +20,12 @@
 package org.elasticsearch.common.lucene.search;
 
 import org.apache.lucene.index.Term;
+import org.apache.lucene.queries.ExtendedCommonTermsQuery;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.GraphQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.PrefixQuery;
@@ -135,6 +137,33 @@ public class Queries {
         }
     }
 
+    /**
+     * Potentially apply minimum should match value if we have a query that it can be applied to,
+     * otherwise return the original query.
+     */
+    public static Query maybeApplyMinimumShouldMatch(Query query, @Nullable String minimumShouldMatch) {
+        // If the coordination factor is disabled on a boolean query we don't apply the minimum should match.
+        // This is done to make sure that the minimum_should_match doesn't get applied when there is only one word
+        // and multiple variations of the same word in the query (synonyms for instance).
+        if (query instanceof BooleanQuery && !((BooleanQuery) query).isCoordDisabled()) {
+            return applyMinimumShouldMatch((BooleanQuery) query, minimumShouldMatch);
+        } else if (query instanceof ExtendedCommonTermsQuery) {
+            ((ExtendedCommonTermsQuery)query).setLowFreqMinimumNumberShouldMatch(minimumShouldMatch);
+        } else if (query instanceof GraphQuery && ((GraphQuery) query).hasBoolean()) {
+            // we have a graph query that has at least one boolean sub-query
+            // re-build and set minimum should match value on all boolean queries
+            List<Query> oldQueries = ((GraphQuery) query).getQueries();
+            Query[] queries = new Query[oldQueries.size()];
+            for (int i = 0; i < queries.length; i++) {
+                queries[i] = maybeApplyMinimumShouldMatch(oldQueries.get(i), minimumShouldMatch);
+            }
+
+            return new GraphQuery(queries);
+        }
+
+        return query;
+    }
+
     private static Pattern spaceAroundLessThanPattern = Pattern.compile("(\\s+<\\s*)|(\\s*<\\s+)");
     private static Pattern spacePattern = Pattern.compile(" ");
     private static Pattern lessThanPattern = Pattern.compile("<");

+ 1 - 29
core/src/main/java/org/elasticsearch/index/query/MatchQueryBuilder.java

@@ -461,35 +461,7 @@ public class MatchQueryBuilder extends AbstractQueryBuilder<MatchQueryBuilder> {
         matchQuery.setZeroTermsQuery(zeroTermsQuery);
 
         Query query = matchQuery.parse(type, fieldName, value);
-        if (query == null) {
-            return null;
-        }
-
-        // If the coordination factor is disabled on a boolean query we don't apply the minimum should match.
-        // This is done to make sure that the minimum_should_match doesn't get applied when there is only one word
-        // and multiple variations of the same word in the query (synonyms for instance).
-        if (query instanceof BooleanQuery && !((BooleanQuery) query).isCoordDisabled()) {
-            query = Queries.applyMinimumShouldMatch((BooleanQuery) query, minimumShouldMatch);
-        } else if (query instanceof GraphQuery && ((GraphQuery) query).hasBoolean()) {
-            // we have a graph query that has at least one boolean sub-query
-            // re-build and set minimum should match value on all boolean queries
-            List<Query> oldQueries = ((GraphQuery) query).getQueries();
-            Query[] queries = new Query[oldQueries.size()];
-            for (int i = 0; i < queries.length; i++) {
-                Query oldQuery = oldQueries.get(i);
-                if (oldQuery instanceof BooleanQuery) {
-                    queries[i] = Queries.applyMinimumShouldMatch((BooleanQuery) oldQuery, minimumShouldMatch);
-                } else {
-                    queries[i] = oldQuery;
-                }
-            }
-
-            query = new GraphQuery(queries);
-        } else if (query instanceof ExtendedCommonTermsQuery) {
-            ((ExtendedCommonTermsQuery)query).setLowFreqMinimumNumberShouldMatch(minimumShouldMatch);
-        }
-
-        return query;
+        return Queries.maybeApplyMinimumShouldMatch(query, minimumShouldMatch);
     }
 
     @Override

+ 1 - 6
core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java

@@ -1042,12 +1042,7 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
         }
 
         query = Queries.fixNegativeQueryIfNeeded(query);
-        // If the coordination factor is disabled on a boolean query we don't apply the minimum should match.
-        // This is done to make sure that the minimum_should_match doesn't get applied when there is only one word
-        // and multiple variations of the same word in the query (synonyms for instance).
-        if (query instanceof BooleanQuery && !((BooleanQuery) query).isCoordDisabled()) {
-            query = Queries.applyMinimumShouldMatch((BooleanQuery) query, this.minimumShouldMatch());
-        }
+        query = Queries.maybeApplyMinimumShouldMatch(query, this.minimumShouldMatch);
 
         //restore the previous BoostQuery wrapping
         for (int i = boosts.size() - 1; i >= 0; i--) {

+ 3 - 8
core/src/main/java/org/elasticsearch/index/query/SimpleQueryStringBuilder.java

@@ -21,6 +21,7 @@ package org.elasticsearch.index.query;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.GraphQuery;
 import org.apache.lucene.search.Query;
 import org.elasticsearch.Version;
 import org.elasticsearch.common.ParseField;
@@ -37,6 +38,7 @@ import org.elasticsearch.index.query.SimpleQueryParser.Settings;
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
@@ -412,15 +414,8 @@ public class SimpleQueryStringBuilder extends AbstractQueryBuilder<SimpleQuerySt
 
         SimpleQueryParser sqp = new SimpleQueryParser(luceneAnalyzer, resolvedFieldsAndWeights, flags, newSettings, context);
         sqp.setDefaultOperator(defaultOperator.toBooleanClauseOccur());
-
         Query query = sqp.parse(queryText);
-        // If the coordination factor is disabled on a boolean query we don't apply the minimum should match.
-        // This is done to make sure that the minimum_should_match doesn't get applied when there is only one word
-        // and multiple variations of the same word in the query (synonyms for instance).
-        if (minimumShouldMatch != null && query instanceof BooleanQuery && !((BooleanQuery) query).isCoordDisabled()) {
-            query = Queries.applyMinimumShouldMatch((BooleanQuery) query, minimumShouldMatch);
-        }
-        return query;
+        return Queries.maybeApplyMinimumShouldMatch(query, minimumShouldMatch);
     }
 
     private static String resolveIndexName(String fieldName, QueryShardContext context) {

+ 1 - 6
core/src/main/java/org/elasticsearch/index/search/MultiMatchQuery.java

@@ -61,12 +61,7 @@ public class MultiMatchQuery extends MatchQuery {
 
     private Query parseAndApply(Type type, String fieldName, Object value, String minimumShouldMatch, Float boostValue) throws IOException {
         Query query = parse(type, fieldName, value);
-        // If the coordination factor is disabled on a boolean query we don't apply the minimum should match.
-        // This is done to make sure that the minimum_should_match doesn't get applied when there is only one word
-        // and multiple variations of the same word in the query (synonyms for instance).
-        if (query instanceof BooleanQuery && !((BooleanQuery) query).isCoordDisabled()) {
-            query = Queries.applyMinimumShouldMatch((BooleanQuery) query, minimumShouldMatch);
-        }
+        query = Queries.maybeApplyMinimumShouldMatch(query, minimumShouldMatch);
         if (query != null && boostValue != null && boostValue != AbstractQueryBuilder.DEFAULT_BOOST) {
             query = new BoostQuery(query, boostValue);
         }

+ 125 - 7
core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java

@@ -19,6 +19,14 @@
 
 package org.elasticsearch.index.query;
 
+import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery;
+import static org.hamcrest.CoreMatchers.either;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
+
+import org.apache.lucene.analysis.MockSynonymAnalyzer;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.queryparser.classic.MapperQueryParser;
 import org.apache.lucene.queryparser.classic.QueryParserSettings;
@@ -27,6 +35,7 @@ import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.BoostQuery;
 import org.apache.lucene.search.DisjunctionMaxQuery;
 import org.apache.lucene.search.FuzzyQuery;
+import org.apache.lucene.search.GraphQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.MultiTermQuery;
@@ -43,6 +52,7 @@ import org.apache.lucene.util.automaton.TooComplexToDeterminizeException;
 import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.lucene.all.AllTermQuery;
 import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.search.internal.SearchContext;
 import org.elasticsearch.test.AbstractQueryTestCase;
 import org.hamcrest.Matchers;
@@ -53,13 +63,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery;
-import static org.hamcrest.CoreMatchers.either;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.instanceOf;
-
 public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStringQueryBuilder> {
 
     @Override
@@ -376,6 +379,121 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         }
     }
 
+    public void testToQueryWithGraph() throws Exception {
+        assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
+        for (Operator op : Operator.values()) {
+            BooleanClause.Occur defaultOp = op.toBooleanClauseOccur();
+            MapperQueryParser queryParser = new MapperQueryParser(createShardContext());
+            QueryParserSettings settings = new QueryParserSettings("");
+            settings.defaultField(STRING_FIELD_NAME);
+            settings.fieldsAndWeights(Collections.emptyMap());
+            settings.fuzziness(Fuzziness.AUTO);
+            settings.analyzeWildcard(true);
+            settings.rewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
+            settings.defaultOperator(op.toQueryParserOperator());
+            settings.forceAnalyzer(new MockSynonymAnalyzer());
+            settings.forceQuoteAnalyzer(new MockSynonymAnalyzer());
+            queryParser.reset(settings);
+
+            // simple multi-term
+            Query query = queryParser.parse("guinea pig");
+            Query expectedQuery = new GraphQuery(
+                new BooleanQuery.Builder()
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), defaultOp))
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), defaultOp))
+                    .build(),
+                new TermQuery(new Term(STRING_FIELD_NAME, "cavy"))
+            );
+            assertThat(query, Matchers.equalTo(expectedQuery));
+
+            // simple with additional tokens
+            query = queryParser.parse("that guinea pig smells");
+            expectedQuery = new GraphQuery(
+                new BooleanQuery.Builder()
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "that")), defaultOp))
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), defaultOp))
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), defaultOp))
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "smells")), defaultOp))
+                    .build(),
+                new BooleanQuery.Builder()
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "that")), defaultOp))
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "cavy")), defaultOp))
+                    .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "smells")), defaultOp))
+                    .build()
+            );
+            assertThat(query, Matchers.equalTo(expectedQuery));
+
+            // complex
+            query = queryParser.parse("+that -(guinea pig) +smells");
+            expectedQuery = new BooleanQuery.Builder()
+                .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "that")), BooleanClause.Occur.MUST))
+                .add(new BooleanClause(new GraphQuery(
+                    new BooleanQuery.Builder()
+                        .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), defaultOp))
+                        .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), defaultOp))
+                        .build(),
+                    new TermQuery(new Term(STRING_FIELD_NAME, "cavy"))
+                ), BooleanClause.Occur.MUST_NOT))
+                .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "smells")), BooleanClause.Occur.MUST))
+                .build();
+
+            assertThat(query, Matchers.equalTo(expectedQuery));
+
+            // no paren should cause guinea and pig to be treated as separate tokens
+            query = queryParser.parse("+that -guinea pig +smells");
+            expectedQuery = new BooleanQuery.Builder()
+                .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "that")), BooleanClause.Occur.MUST))
+                .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "guinea")), BooleanClause.Occur.MUST_NOT))
+                .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "pig")), defaultOp))
+                .add(new BooleanClause(new TermQuery(new Term(STRING_FIELD_NAME, "smells")), BooleanClause.Occur.MUST))
+                .build();
+
+            assertThat(query, Matchers.equalTo(expectedQuery));
+
+            // phrase
+            query = queryParser.parse("\"that guinea pig smells\"");
+            expectedQuery = new BooleanQuery.Builder()
+                .setDisableCoord(true)
+                .add(new BooleanClause(new GraphQuery(
+                    new PhraseQuery.Builder()
+                        .add(new Term(STRING_FIELD_NAME, "that"))
+                        .add(new Term(STRING_FIELD_NAME, "guinea"))
+                        .add(new Term(STRING_FIELD_NAME, "pig"))
+                        .add(new Term(STRING_FIELD_NAME, "smells"))
+                        .build(),
+                    new PhraseQuery.Builder()
+                        .add(new Term(STRING_FIELD_NAME, "that"))
+                        .add(new Term(STRING_FIELD_NAME, "cavy"))
+                        .add(new Term(STRING_FIELD_NAME, "smells"))
+                        .build()
+                ), BooleanClause.Occur.SHOULD)).build();
+
+            assertThat(query, Matchers.equalTo(expectedQuery));
+
+            // phrase with slop
+            query = queryParser.parse("\"that guinea pig smells\"~2");
+            expectedQuery = new BooleanQuery.Builder()
+                .setDisableCoord(true)
+                .add(new BooleanClause(new GraphQuery(
+                    new PhraseQuery.Builder()
+                        .add(new Term(STRING_FIELD_NAME, "that"))
+                        .add(new Term(STRING_FIELD_NAME, "guinea"))
+                        .add(new Term(STRING_FIELD_NAME, "pig"))
+                        .add(new Term(STRING_FIELD_NAME, "smells"))
+                        .setSlop(2)
+                        .build(),
+                    new PhraseQuery.Builder()
+                        .add(new Term(STRING_FIELD_NAME, "that"))
+                        .add(new Term(STRING_FIELD_NAME, "cavy"))
+                        .add(new Term(STRING_FIELD_NAME, "smells"))
+                        .setSlop(2)
+                        .build()
+                ), BooleanClause.Occur.SHOULD)).build();
+
+            assertThat(query, Matchers.equalTo(expectedQuery));
+        }
+    }
+
     public void testToQueryRegExpQuery() throws Exception {
         assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
         Query query = queryStringQuery("/foo*bar/").defaultField(STRING_FIELD_NAME)

+ 50 - 0
core/src/test/java/org/elasticsearch/index/query/SimpleQueryParserTests.java

@@ -20,10 +20,13 @@
 package org.elasticsearch.index.query;
 
 import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.MockSynonymAnalyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.GraphQuery;
+import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SynonymQuery;
@@ -113,6 +116,53 @@ public class SimpleQueryParserTests extends ESTestCase {
         }
     }
 
+    public void testAnalyzerWithGraph() {
+        SimpleQueryParser.Settings settings = new SimpleQueryParser.Settings();
+        settings.analyzeWildcard(true);
+        Map<String, Float> weights = new HashMap<>();
+        weights.put("field1", 1.0f);
+        SimpleQueryParser parser = new MockSimpleQueryParser(new MockSynonymAnalyzer(), weights, -1, settings);
+
+        for (Operator op : Operator.values()) {
+            BooleanClause.Occur defaultOp = op.toBooleanClauseOccur();
+            parser.setDefaultOperator(defaultOp);
+
+            // non-phrase won't detect multi-word synonym because of whitespace splitting
+            Query query = parser.parse("guinea pig");
+
+            Query expectedQuery = new BooleanQuery.Builder()
+                .add(new BooleanClause(new TermQuery(new Term("field1", "guinea")), defaultOp))
+                .add(new BooleanClause(new TermQuery(new Term("field1", "pig")), defaultOp))
+                .build();
+            assertThat(query, equalTo(expectedQuery));
+
+            // phrase will pick it up
+            query = parser.parse("\"guinea pig\"");
+
+            expectedQuery = new GraphQuery(
+                new PhraseQuery("field1", "guinea", "pig"),
+                new TermQuery(new Term("field1", "cavy")));
+
+            assertThat(query, equalTo(expectedQuery));
+
+            // phrase with slop
+            query = parser.parse("big \"guinea pig\"~2");
+
+            expectedQuery = new BooleanQuery.Builder()
+                .add(new BooleanClause(new TermQuery(new Term("field1", "big")), defaultOp))
+                .add(new BooleanClause(new GraphQuery(
+                    new PhraseQuery.Builder()
+                        .add(new Term("field1", "guinea"))
+                        .add(new Term("field1", "pig"))
+                        .setSlop(2)
+                        .build(),
+                    new TermQuery(new Term("field1", "cavy"))), defaultOp))
+                .build();
+
+            assertThat(query, equalTo(expectedQuery));
+        }
+    }
+
     public void testQuoteFieldSuffix() {
         SimpleQueryParser.Settings sqpSettings = new SimpleQueryParser.Settings();
         sqpSettings.quoteFieldSuffix(".quote");

+ 97 - 17
core/src/test/java/org/elasticsearch/search/query/QueryStringIT.java

@@ -19,16 +19,26 @@
 
 package org.elasticsearch.search.query;
 
+import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
+import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoSearchHits;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+
 import org.apache.lucene.util.LuceneTestCase;
 import org.elasticsearch.ExceptionsHelper;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.index.query.Operator;
+import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.index.query.QueryStringQueryBuilder;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHits;
@@ -41,22 +51,6 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
-import static org.elasticsearch.test.StreamsUtils.copyToStringFromClasspath;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFirstHit;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSecondHit;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasId;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.lessThan;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-
 public class QueryStringIT extends ESIntegTestCase {
 
     @Before
@@ -263,6 +257,92 @@ public class QueryStringIT extends ESIntegTestCase {
                 containsString("unit [D] not supported for date math [-2D]"));
     }
 
+    private void setupIndexWithGraph(String index) throws Exception {
+        CreateIndexRequestBuilder builder = prepareCreate(index).setSettings(
+            Settings.builder()
+                .put(indexSettings())
+                .put("index.analysis.filter.graphsyns.type", "synonym_graph")
+                .putArray("index.analysis.filter.graphsyns.synonyms", "wtf, what the fudge", "foo, bar baz")
+                .put("index.analysis.analyzer.lower_graphsyns.type", "custom")
+                .put("index.analysis.analyzer.lower_graphsyns.tokenizer", "standard")
+                .putArray("index.analysis.analyzer.lower_graphsyns.filter", "lowercase", "graphsyns")
+        );
+
+        XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(index).startObject("properties")
+            .startObject("field").field("type", "text").endObject().endObject().endObject().endObject();
+
+        assertAcked(builder.addMapping(index, mapping));
+        ensureGreen();
+
+        List<IndexRequestBuilder> builders = new ArrayList<>();
+        builders.add(client().prepareIndex(index, index, "1").setSource("field", "say wtf happened foo"));
+        builders.add(client().prepareIndex(index, index, "2").setSource("field", "bar baz what the fudge man"));
+        builders.add(client().prepareIndex(index, index, "3").setSource("field", "wtf"));
+        builders.add(client().prepareIndex(index, index, "4").setSource("field", "what is the name for fudge"));
+        builders.add(client().prepareIndex(index, index, "5").setSource("field", "bar two three"));
+        builders.add(client().prepareIndex(index, index, "6").setSource("field", "bar baz two three"));
+
+        indexRandom(true, false, builders);
+    }
+
+    public void testGraphQueries() throws Exception {
+        String index = "graph_test_index";
+        setupIndexWithGraph(index);
+
+        // phrase
+        SearchResponse searchResponse = client().prepareSearch(index).setQuery(
+            QueryBuilders.queryStringQuery("\"foo two three\"")
+                .defaultField("field")
+                .analyzer("lower_graphsyns")).get();
+
+        assertHitCount(searchResponse, 1L);
+        assertSearchHits(searchResponse, "6");
+
+        // and
+        searchResponse = client().prepareSearch(index).setQuery(
+            QueryBuilders.queryStringQuery("say what the fudge")
+                .defaultField("field")
+                .splitOnWhitespace(false)
+                .defaultOperator(Operator.AND)
+                .analyzer("lower_graphsyns")).get();
+
+        assertHitCount(searchResponse, 1L);
+        assertSearchHits(searchResponse, "1");
+
+        // and, split on whitespace means we should not recognize the multi-word synonym
+        searchResponse = client().prepareSearch(index).setQuery(
+            QueryBuilders.queryStringQuery("say what the fudge")
+                .defaultField("field")
+                .splitOnWhitespace(true)
+                .defaultOperator(Operator.AND)
+                .analyzer("lower_graphsyns")).get();
+
+        assertNoSearchHits(searchResponse);
+
+        // or
+        searchResponse = client().prepareSearch(index).setQuery(
+            QueryBuilders.queryStringQuery("three what the fudge foo")
+                .defaultField("field")
+                .splitOnWhitespace(false)
+                .defaultOperator(Operator.OR)
+                .analyzer("lower_graphsyns")).get();
+
+        assertHitCount(searchResponse, 6L);
+        assertSearchHits(searchResponse, "1", "2", "3", "4", "5", "6");
+
+        // min should match
+        searchResponse = client().prepareSearch(index).setQuery(
+            QueryBuilders.queryStringQuery("three what the fudge foo")
+                .defaultField("field")
+                .splitOnWhitespace(false)
+                .defaultOperator(Operator.OR)
+                .analyzer("lower_graphsyns")
+                .minimumShouldMatch("80%")).get();
+
+        assertHitCount(searchResponse, 3L);
+        assertSearchHits(searchResponse, "1", "2", "6");
+    }
+
     private void assertHits(SearchHits hits, String... ids) {
         assertThat(hits.totalHits(), equalTo((long) ids.length));
         Set<String> hitIds = new HashSet<>();

+ 0 - 9
docs/reference/analysis/tokenfilters/synonym-graph-tokenfilter.asciidoc

@@ -19,15 +19,6 @@ only.  If you want to apply synonyms during indexing please use the
 standard <<analysis-synonym-tokenfilter,synonym token filter>>.
 ===============================
 
-["NOTE",id="synonym-graph-query-note"]
-===============================
-The graph token stream created by this token filter requires special
-query handling. Currently only the <<query-dsl-match-query, Match>> and 
-<<query-dsl-multi-match-query, Multi Match>> queries can do this.  Using 
-it with any other type of analyzed query will potentially result in 
-incorrect search results.
-===============================
-
 Synonyms are configured using a configuration file.
 Here is an example: