You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ji...@apache.org on 2017/02/22 13:26:27 UTC

lucene-solr:branch_6x: LUCENE-7699: Query parsers now use span queries to produce more efficient phrase queries for multi-token synonyms.

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x d8e493c50 -> 43317a33f


LUCENE-7699: Query parsers now use span queries to produce more efficient phrase queries for multi-token synonyms.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/43317a33
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/43317a33
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/43317a33

Branch: refs/heads/branch_6x
Commit: 43317a33f5d782d982d562df23ffbcdfb8820204
Parents: d8e493c
Author: Jim Ferenczi <ji...@elastic.co>
Authored: Wed Feb 22 14:05:21 2017 +0100
Committer: Jim Ferenczi <ji...@elastic.co>
Committed: Wed Feb 22 14:26:08 2017 +0100

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   3 +
 .../org/apache/lucene/util/QueryBuilder.java    |  97 +++++++++++--
 .../apache/lucene/util/TestQueryBuilder.java    |  16 ++-
 .../queryparser/classic/TestQueryParser.java    | 137 ++++++++++---------
 4 files changed, 173 insertions(+), 80 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43317a33/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index f5bc70f..49e3086 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -136,6 +136,9 @@ Optimizations
   points (or cut vertices) in order to create more efficient queries for
   multi-token synonyms. (Jim Ferenczi)
 
+* LUCENE-7699: Query parsers now use span queries to produce more efficient
+  phrase queries for multi-token synonyms. (Matt Webber via Jim Ferenczi)
+
 Build
 
 * LUCENE-7653: Update randomizedtesting to version 2.5.0. (Dawid Weiss)

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43317a33/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java b/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
index 5945c16..8bb4ef4 100644
--- a/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
+++ b/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
@@ -37,6 +37,10 @@ import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SynonymQuery;
 import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.spans.SpanNearQuery;
+import org.apache.lucene.search.spans.SpanOrQuery;
+import org.apache.lucene.search.spans.SpanQuery;
+import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.lucene.util.graph.GraphTokenStreamFiniteStrings;
 
 /**
@@ -314,7 +318,7 @@ public class QueryBuilder {
       } else if (isGraph) {
         // graph
         if (quoted) {
-          return analyzeGraphPhrase(stream, operator, field, phraseSlop);
+          return analyzeGraphPhrase(stream, field, phraseSlop);
         } else {
           return analyzeGraphBoolean(field, stream, operator);
         }
@@ -341,7 +345,31 @@ public class QueryBuilder {
       throw new RuntimeException("Error analyzing query text", e);
     }
   }
-  
+
+  /**
+   * Creates a span query from the tokenstream.  In the case of a single token, a simple <code>SpanTermQuery</code> is
+   * returned.  When multiple tokens, an ordered <code>SpanNearQuery</code> with slop of 0 is returned.
+   */
+  protected final SpanQuery createSpanQuery(TokenStream in, String field) throws IOException {
+    TermToBytesRefAttribute termAtt = in.getAttribute(TermToBytesRefAttribute.class);
+    if (termAtt == null) {
+      return null;
+    }
+
+    List<SpanTermQuery> terms = new ArrayList<>();
+    while (in.incrementToken()) {
+      terms.add(new SpanTermQuery(new Term(field, termAtt.getBytesRef())));
+    }
+
+    if (terms.isEmpty()) {
+      return null;
+    } else if (terms.size() == 1) {
+      return terms.get(0);
+    } else {
+      return new SpanNearQuery(terms.toArray(new SpanTermQuery[0]), 0, true);
+    }
+  }
+
   /** 
    * Creates simple term query from the cached tokenstream contents 
    */
@@ -521,21 +549,66 @@ public class QueryBuilder {
   }
 
   /**
-   * Creates a query from a graph token stream by extracting all the finite strings from the graph and using them to create the query.
+   * Creates a span near (phrase) query from a graph token stream. The articulation points of the graph are visited in
+   * order and the queries created at each point are merged in the returned near query.
    */
-  protected Query analyzeGraphPhrase(TokenStream source, BooleanClause.Occur operator, String field, int phraseSlop)
+  protected SpanQuery analyzeGraphPhrase(TokenStream source, String field, int phraseSlop)
       throws IOException {
     source.reset();
-    GraphTokenStreamFiniteStrings visitor = new GraphTokenStreamFiniteStrings(source);
-    Iterator<TokenStream> it = visitor.getFiniteStrings();
-    List<Query> queries = new ArrayList<>();
-    while (it.hasNext()) {
-      Query query = createFieldQuery(it.next(), operator, field, true, phraseSlop);
-      if (query != null) {
-        queries.add(query);
+    GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(source);
+    List<SpanQuery> clauses = new ArrayList<>();
+    int[] articulationPoints = graph.articulationPoints();
+    int lastState = 0;
+    for (int i = 0; i <= articulationPoints.length; i++) {
+      int start = lastState;
+      int end = -1;
+      if (i < articulationPoints.length) {
+        end = articulationPoints[i];
+      }
+      lastState = end;
+      final SpanQuery queryPos;
+      if (graph.hasSidePath(start)) {
+        List<SpanQuery> queries = new ArrayList<>();
+        Iterator<TokenStream> it = graph.getFiniteStrings(start, end);
+        while (it.hasNext()) {
+          TokenStream ts = it.next();
+          SpanQuery q = createSpanQuery(ts, field);
+          if (q != null) {
+            queries.add(q);
+          }
+        }
+        if (queries.size() > 0) {
+          queryPos = new SpanOrQuery(queries.toArray(new SpanQuery[0]));
+        } else {
+          queryPos = null;
+        }
+      } else {
+        Term[] terms = graph.getTerms(field, start);
+        assert terms.length > 0;
+        if (terms.length == 1) {
+          queryPos = new SpanTermQuery(terms[0]);
+        } else {
+          SpanTermQuery[] orClauses = new SpanTermQuery[terms.length];
+          for (int idx = 0; idx < terms.length; idx++) {
+            orClauses[idx] = new SpanTermQuery(terms[idx]);
+          }
+
+          queryPos = new SpanOrQuery(orClauses);
+        }
       }
+
+      if (queryPos != null) {
+        clauses.add(queryPos);
+      }
+    }
+
+    if (clauses.isEmpty()) {
+      return null;
+    } else if (clauses.size() == 1) {
+      return clauses.get(0);
+    } else {
+      return new SpanNearQuery(clauses.toArray(new SpanQuery[0]), phraseSlop, true);
     }
-    return new GraphQuery(queries.toArray(new Query[0]));
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43317a33/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java b/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
index e349e98..a408a61 100644
--- a/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
+++ b/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
@@ -37,6 +37,10 @@ import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SynonymQuery;
 import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.spans.SpanNearQuery;
+import org.apache.lucene.search.spans.SpanOrQuery;
+import org.apache.lucene.search.spans.SpanQuery;
+import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.lucene.util.automaton.CharacterRunAutomaton;
 import org.apache.lucene.util.automaton.RegExp;
 
@@ -153,14 +157,16 @@ public class TestQueryBuilder extends LuceneTestCase {
 
   /** forms graph query */
   public void testMultiWordSynonymsPhrase() throws Exception {
-    PhraseQuery.Builder expectedPhrase = new PhraseQuery.Builder();
-    expectedPhrase.add(new Term("field", "guinea"));
-    expectedPhrase.add(new Term("field", "pig"));
+    SpanNearQuery expectedNear = SpanNearQuery.newOrderedNearQuery("field")
+        .addClause(new SpanTermQuery(new Term("field", "guinea")))
+        .addClause(new SpanTermQuery(new Term("field", "pig")))
+        .setSlop(0)
+        .build();
 
-    TermQuery expectedTerm = new TermQuery(new Term("field", "cavy"));
+    SpanTermQuery expectedTerm = new SpanTermQuery(new Term("field", "cavy"));
 
     QueryBuilder queryBuilder = new QueryBuilder(new MockSynonymAnalyzer());
-    assertEquals(new GraphQuery(expectedPhrase.build(), expectedTerm),
+    assertEquals(new SpanOrQuery(new SpanQuery[]{expectedNear, expectedTerm}),
         queryBuilder.createPhraseQuery("field", "guinea pig"));
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/43317a33/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
----------------------------------------------------------------------
diff --git a/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java b/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
index 1c829c5..ba4cf77 100644
--- a/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
+++ b/lucene/queryparser/src/test/org/apache/lucene/queryparser/classic/TestQueryParser.java
@@ -41,6 +41,11 @@ import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SynonymQuery;
 import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.spans.SpanNearQuery;
+import org.apache.lucene.search.spans.SpanOrQuery;
+import org.apache.lucene.search.spans.SpanQuery;
+import org.apache.lucene.search.spans.SpanTermQuery;
+import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.automaton.TooComplexToDeterminizeException;
 
 /**
@@ -54,20 +59,20 @@ public class TestQueryParser extends QueryParserTestBase {
     public QPTestParser(String f, Analyzer a) {
       super(f, a);
     }
-    
+
     @Override
     protected Query getFuzzyQuery(String field, String termStr,
         float minSimilarity) throws ParseException {
       throw new ParseException("Fuzzy queries not allowed");
     }
-    
+
     @Override
     protected Query getWildcardQuery(String field, String termStr)
         throws ParseException {
       throw new ParseException("Wildcard queries not allowed");
     }
   }
-  
+
   public QueryParser getParser(Analyzer a) throws Exception {
     if (a == null) a = new MockAnalyzer(random(), MockTokenizer.SIMPLE, true);
     QueryParser qp = new QueryParser(getDefaultField(), a);
@@ -75,13 +80,13 @@ public class TestQueryParser extends QueryParserTestBase {
     qp.setSplitOnWhitespace(splitOnWhitespace);
     return qp;
   }
-  
+
   @Override
   public CommonQueryParserConfiguration getParserConfig(Analyzer a)
       throws Exception {
     return getParser(a);
   }
-  
+
   @Override
   public Query getQuery(String query, CommonQueryParserConfiguration cqpC)
       throws Exception {
@@ -90,31 +95,31 @@ public class TestQueryParser extends QueryParserTestBase {
     QueryParser qp = (QueryParser) cqpC;
     return qp.parse(query);
   }
-  
+
   @Override
   public Query getQuery(String query, Analyzer a) throws Exception {
     return getParser(a).parse(query);
   }
-  
+
   @Override
   public boolean isQueryParserException(Exception exception) {
     return exception instanceof ParseException;
   }
-  
+
   @Override
   public void setDefaultOperatorOR(CommonQueryParserConfiguration cqpC) {
     assert (cqpC instanceof QueryParser);
     QueryParser qp = (QueryParser) cqpC;
     qp.setDefaultOperator(Operator.OR);
   }
-  
+
   @Override
   public void setDefaultOperatorAND(CommonQueryParserConfiguration cqpC) {
     assert (cqpC instanceof QueryParser);
     QueryParser qp = (QueryParser) cqpC;
     qp.setDefaultOperator(Operator.AND);
   }
-  
+
   @Override
   public void setAnalyzeRangeTerms(CommonQueryParserConfiguration cqpC,
       boolean value) {
@@ -122,7 +127,7 @@ public class TestQueryParser extends QueryParserTestBase {
     QueryParser qp = (QueryParser) cqpC;
     qp.setAnalyzeRangeTerms(value);
   }
-  
+
   @Override
   public void setAutoGeneratePhraseQueries(CommonQueryParserConfiguration cqpC,
       boolean value) {
@@ -130,7 +135,7 @@ public class TestQueryParser extends QueryParserTestBase {
     QueryParser qp = (QueryParser) cqpC;
     qp.setAutoGeneratePhraseQueries(value);
   }
-  
+
   @Override
   public void setDateResolution(CommonQueryParserConfiguration cqpC,
       CharSequence field, Resolution value) {
@@ -138,7 +143,7 @@ public class TestQueryParser extends QueryParserTestBase {
     QueryParser qp = (QueryParser) cqpC;
     qp.setDateResolution(field.toString(), value);
   }
-  
+
   @Override
   public void testDefaultOperator() throws Exception {
     QueryParser qp = getParser(new MockAnalyzer(random()));
@@ -149,7 +154,7 @@ public class TestQueryParser extends QueryParserTestBase {
     setDefaultOperatorOR(qp);
     assertEquals(QueryParserBase.OR_OPERATOR, qp.getDefaultOperator());
   }
-  
+
   // LUCENE-2002: when we run javacc to regen QueryParser,
   // we also run a replaceregexp step to fix 2 of the public
   // ctors (change them to protected):
@@ -175,14 +180,14 @@ public class TestQueryParser extends QueryParserTestBase {
       // expected
     }
   }
-  
+
   public void testFuzzySlopeExtendability() throws ParseException {
     QueryParser qp = new QueryParser("a",  new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false)) {
 
       @Override
       Query handleBareFuzzy(String qfield, Token fuzzySlop, String termImage)
           throws ParseException {
-        
+
         if(fuzzySlop.image.endsWith("\u20ac")) {
           float fms = fuzzyMinSim;
           try {
@@ -193,11 +198,11 @@ public class TestQueryParser extends QueryParserTestBase {
         }
         return super.handleBareFuzzy(qfield, fuzzySlop, termImage);
       }
-      
+
     };
     assertEquals(qp.parse("a:[11.95 TO 12.95]"), qp.parse("12.45~1\u20ac"));
   }
-  
+
   @Override
   public void testStarParsing() throws Exception {
     final int[] type = new int[1];
@@ -209,14 +214,14 @@ public class TestQueryParser extends QueryParserTestBase {
         type[0] = 1;
         return new TermQuery(new Term(field, termStr));
       }
-      
+
       @Override
       protected Query getPrefixQuery(String field, String termStr) {
         // override error checking of superclass
         type[0] = 2;
         return new TermQuery(new Term(field, termStr));
       }
-      
+
       @Override
       protected Query getFieldQuery(String field, String queryText,
           boolean quoted) throws ParseException {
@@ -224,9 +229,9 @@ public class TestQueryParser extends QueryParserTestBase {
         return super.getFieldQuery(field, queryText, quoted);
       }
     };
-    
+
     TermQuery tq;
-    
+
     tq = (TermQuery) qp.parse("foo:zoo*");
     assertEquals("zoo", tq.getTerm().text());
     assertEquals(2, type[0]);
@@ -236,35 +241,35 @@ public class TestQueryParser extends QueryParserTestBase {
     assertEquals("zoo", tq.getTerm().text());
     assertEquals(2, type[0]);
     assertEquals(bq.getBoost(), 2, 0);
-    
+
     tq = (TermQuery) qp.parse("foo:*");
     assertEquals("*", tq.getTerm().text());
     assertEquals(1, type[0]); // could be a valid prefix query in the future too
-    
+
     bq = (BoostQuery) qp.parse("foo:*^2");
     tq = (TermQuery) bq.getQuery();
     assertEquals("*", tq.getTerm().text());
     assertEquals(1, type[0]);
     assertEquals(bq.getBoost(), 2, 0);
-    
+
     tq = (TermQuery) qp.parse("*:foo");
     assertEquals("*", tq.getTerm().field());
     assertEquals("foo", tq.getTerm().text());
     assertEquals(3, type[0]);
-    
+
     tq = (TermQuery) qp.parse("*:*");
     assertEquals("*", tq.getTerm().field());
     assertEquals("*", tq.getTerm().text());
     assertEquals(1, type[0]); // could be handled as a prefix query in the
                               // future
-    
+
     tq = (TermQuery) qp.parse("(*:*)");
     assertEquals("*", tq.getTerm().field());
     assertEquals("*", tq.getTerm().text());
     assertEquals(1, type[0]);
-    
+
   }
-  
+
   // Wildcard queries should not be allowed
   public void testCustomQueryParserWildcard() {
     expectThrows(ParseException.class, () -> {
@@ -272,7 +277,7 @@ public class TestQueryParser extends QueryParserTestBase {
           MockTokenizer.WHITESPACE, false)).parse("a?t");
     });
   }
-  
+
   // Fuzzy queries should not be allowed
   public void testCustomQueryParserFuzzy() throws Exception {
     expectThrows(ParseException.class, () -> {
@@ -280,15 +285,15 @@ public class TestQueryParser extends QueryParserTestBase {
           MockTokenizer.WHITESPACE, false)).parse("xunit~");
     });
   }
-  
+
   /** query parser that doesn't expand synonyms when users use double quotes */
   private class SmartQueryParser extends QueryParser {
     Analyzer morePrecise = new Analyzer2();
-    
+
     public SmartQueryParser() {
       super("field", new Analyzer1());
     }
-    
+
     @Override
     protected Query getFieldQuery(String field, String queryText, boolean quoted)
         throws ParseException {
@@ -296,7 +301,7 @@ public class TestQueryParser extends QueryParserTestBase {
       else return super.getFieldQuery(field, queryText, quoted);
     }
   }
-  
+
   @Override
   public void testNewFieldQuery() throws Exception {
     /** ordinary behavior, synonyms form uncoordinated boolean query */
@@ -306,13 +311,13 @@ public class TestQueryParser extends QueryParserTestBase {
     assertEquals(expanded, dumb.parse("\"dogs\""));
     /** even with the phrase operator the behavior is the same */
     assertEquals(expanded, dumb.parse("dogs"));
-    
+
     /**
      * custom behavior, the synonyms are expanded, unless you use quote operator
      */
     QueryParser smart = new SmartQueryParser();
     assertEquals(expanded, smart.parse("dogs"));
-    
+
     Query unexpanded = new TermQuery(new Term("field", "dogs"));
     assertEquals(unexpanded, smart.parse("\"dogs\""));
   }
@@ -330,7 +335,7 @@ public class TestQueryParser extends QueryParserTestBase {
     assertEquals(expected, qp.parse("dogs^2"));
     assertEquals(expected, qp.parse("\"dogs\"^2"));
   }
-  
+
   /** forms multiphrase query */
   public void testSynonymsPhrase() throws Exception {
     MultiPhraseQuery.Builder expectedQBuilder = new MultiPhraseQuery.Builder();
@@ -346,7 +351,7 @@ public class TestQueryParser extends QueryParserTestBase {
     expected = new BoostQuery(expectedQBuilder.build(), 2f);
     assertEquals(expected, qp.parse("\"old dogs\"~3^2"));
   }
-  
+
   /**
    * adds synonym of "\u570b" for "\u56fd".
    */
@@ -354,7 +359,7 @@ public class TestQueryParser extends QueryParserTestBase {
     CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
     PositionIncrementAttribute posIncAtt = addAttribute(PositionIncrementAttribute.class);
     boolean addSynonym = false;
-    
+
     public MockCJKSynonymFilter(TokenStream input) {
       super(input);
     }
@@ -368,16 +373,16 @@ public class TestQueryParser extends QueryParserTestBase {
         addSynonym = false;
         return true;
       }
-      
+
       if (input.incrementToken()) {
         addSynonym = termAtt.toString().equals("\u56fd");
         return true;
       } else {
         return false;
       }
-    } 
+    }
   }
-  
+
   static class MockCJKSynonymAnalyzer extends Analyzer {
     @Override
     protected TokenStreamComponents createComponents(String fieldName) {
@@ -385,7 +390,7 @@ public class TestQueryParser extends QueryParserTestBase {
       return new TokenStreamComponents(tokenizer, new MockCJKSynonymFilter(tokenizer));
     }
   }
-  
+
   /** simple CJK synonym test */
   public void testCJKSynonym() throws Exception {
     Query expected = new SynonymQuery(new Term("field", "\u56fd"), new Term("field", "\u570b"));
@@ -396,7 +401,7 @@ public class TestQueryParser extends QueryParserTestBase {
     expected = new BoostQuery(expected, 2f);
     assertEquals(expected, qp.parse("\u56fd^2"));
   }
-  
+
   /** synonyms with default OR operator */
   public void testCJKSynonymsOR() throws Exception {
     BooleanQuery.Builder expectedB = new BooleanQuery.Builder();
@@ -409,7 +414,7 @@ public class TestQueryParser extends QueryParserTestBase {
     expected = new BoostQuery(expected, 2f);
     assertEquals(expected, qp.parse("\u4e2d\u56fd^2"));
   }
-  
+
   /** more complex synonyms with default OR operator */
   public void testCJKSynonymsOR2() throws Exception {
     BooleanQuery.Builder expectedB = new BooleanQuery.Builder();
@@ -424,7 +429,7 @@ public class TestQueryParser extends QueryParserTestBase {
     expected = new BoostQuery(expected, 2f);
     assertEquals(expected, qp.parse("\u4e2d\u56fd\u56fd^2"));
   }
-  
+
   /** synonyms with default AND operator */
   public void testCJKSynonymsAND() throws Exception {
     BooleanQuery.Builder expectedB = new BooleanQuery.Builder();
@@ -438,7 +443,7 @@ public class TestQueryParser extends QueryParserTestBase {
     expected = new BoostQuery(expected, 2f);
     assertEquals(expected, qp.parse("\u4e2d\u56fd^2"));
   }
-  
+
   /** more complex synonyms with default AND operator */
   public void testCJKSynonymsAND2() throws Exception {
     BooleanQuery.Builder expectedB = new BooleanQuery.Builder();
@@ -454,7 +459,7 @@ public class TestQueryParser extends QueryParserTestBase {
     expected = new BoostQuery(expected, 2f);
     assertEquals(expected, qp.parse("\u4e2d\u56fd\u56fd^2"));
   }
-  
+
   /** forms multiphrase query */
   public void testCJKSynonymsPhrase() throws Exception {
     MultiPhraseQuery.Builder expectedQBuilder = new MultiPhraseQuery.Builder();
@@ -509,31 +514,37 @@ public class TestQueryParser extends QueryParserTestBase {
     synonym.add(pig, BooleanClause.Occur.MUST);
     BooleanQuery guineaPig = synonym.build();
 
+    PhraseQuery phraseGuineaPig = new PhraseQuery.Builder()
+        .add(new Term("field", "guinea"))
+        .add(new Term("field", "pig"))
+        .build();
+
     GraphQuery graphQuery = new GraphQuery(guineaPig, cavy);
     assertEquals(graphQuery, dumb.parse("guinea pig"));
 
-    // With the phrase operator, a multi-word synonym source will form a graph query with inner phrase queries.
-    PhraseQuery.Builder phraseSynonym = new PhraseQuery.Builder();
-    phraseSynonym.add(new Term("field", "guinea"));
-    phraseSynonym.add(new Term("field", "pig"));
-    PhraseQuery guineaPigPhrase = phraseSynonym.build();
-
-    graphQuery = new GraphQuery(guineaPigPhrase, cavy);
-    assertEquals(graphQuery, dumb.parse("\"guinea pig\""));
+    // With the phrase operator, a multi-word synonym source will form span near queries.
+    SpanNearQuery spanGuineaPig = SpanNearQuery.newOrderedNearQuery("field")
+        .addClause(new SpanTermQuery(new Term("field", "guinea")))
+        .addClause(new SpanTermQuery(new Term("field", "pig")))
+        .setSlop(0)
+        .build();
+    SpanTermQuery spanCavy = new SpanTermQuery(new Term("field", "cavy"));
+    SpanOrQuery spanPhrase = new SpanOrQuery(new SpanQuery[]{spanGuineaPig, spanCavy});
+    assertEquals(spanPhrase, dumb.parse("\"guinea pig\""));
 
     // custom behavior, the synonyms are expanded, unless you use quote operator
     QueryParser smart = new SmartQueryParser();
     smart.setSplitOnWhitespace(false);
     graphQuery = new GraphQuery(guineaPig, cavy);
     assertEquals(graphQuery, smart.parse("guinea pig"));
-    assertEquals(guineaPigPhrase, smart.parse("\"guinea pig\""));
+    assertEquals(phraseGuineaPig, smart.parse("\"guinea pig\""));
   }
 
   public void testEnableGraphQueries() throws Exception {
     QueryParser dumb = new QueryParser("field", new Analyzer1());
     dumb.setSplitOnWhitespace(false);
     dumb.setEnableGraphQueries(false);
-    
+
     TermQuery guinea = new TermQuery(new Term("field", "guinea"));
     TermQuery pig = new TermQuery(new Term("field", "pig"));
     TermQuery cavy = new TermQuery(new Term("field", "cavy"));
@@ -622,9 +633,9 @@ public class TestQueryParser extends QueryParserTestBase {
     assertQueryEquals("guinea pig running?", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running?");
     assertQueryEquals("guinea pig \"running\"", a, "Graph(+field:guinea +field:pig, field:cavy, hasBoolean=true, hasPhrase=false) running");
 
-    assertQueryEquals("\"guinea pig\"~2", a, "Graph(field:\"guinea pig\"~2, field:cavy, hasBoolean=false, hasPhrase=true)");
+    assertQueryEquals("\"guinea pig\"~2", a, "spanOr([spanNear([guinea, pig], 0, true), cavy])");
 
-    assertQueryEquals("field:\"guinea pig\"", a, "Graph(field:\"guinea pig\", field:cavy, hasBoolean=false, hasPhrase=true)");
+    assertQueryEquals("field:\"guinea pig\"", a, "spanOr([spanNear([guinea, pig], 0, true), cavy])");
 
     splitOnWhitespace = oldSplitOnWhitespace;
   }
@@ -701,9 +712,9 @@ public class TestQueryParser extends QueryParserTestBase {
     assertQueryEquals("guinea pig running?", a, "guinea pig running?");
     assertQueryEquals("guinea pig \"running\"", a, "guinea pig running");
 
-    assertQueryEquals("\"guinea pig\"~2", a, "Graph(field:\"guinea pig\"~2, field:cavy, hasBoolean=false, hasPhrase=true)");
+    assertQueryEquals("\"guinea pig\"~2", a, "spanOr([spanNear([guinea, pig], 0, true), cavy])");
 
-    assertQueryEquals("field:\"guinea pig\"", a, "Graph(field:\"guinea pig\", field:cavy, hasBoolean=false, hasPhrase=true)");
+    assertQueryEquals("field:\"guinea pig\"", a, "spanOr([spanNear([guinea, pig], 0, true), cavy])");
 
     splitOnWhitespace = oldSplitOnWhitespace;
   }
@@ -737,4 +748,4 @@ public class TestQueryParser extends QueryParserTestBase {
       qp2.setSplitOnWhitespace(false);
     });
   }
-}
\ No newline at end of file
+}