SOLR-6011: ComplexPhraseQuery hashCode/equals fix for inOrder

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1589812 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yonik Seeley 2014-04-24 18:12:15 +00:00
parent 11ae32183f
commit 54cfa922ae
4 changed files with 102 additions and 68 deletions

View File

@ -319,6 +319,10 @@ Bug fixes
* LUCENE-5624: Ensure NativeFSLockFactory does not leak file handles if it is unable * LUCENE-5624: Ensure NativeFSLockFactory does not leak file handles if it is unable
to obtain the lock. (Uwe Schindler, Robert Muir) to obtain the lock. (Uwe Schindler, Robert Muir)
* SOLR-6011: ComplexPhraseQueryParser produced Query objects that did not correctly
implement hashCode and equals (inOrder was ignored), causing issues for any
system using Query objects as keys. (yonik)
Test Framework Test Framework
* LUCENE-5592: Incorrectly reported uncloseable files. (Dawid Weiss) * LUCENE-5592: Incorrectly reported uncloseable files. (Dawid Weiss)

View File

@ -208,11 +208,11 @@ public class ComplexPhraseQueryParser extends QueryParser {
*/ */
static class ComplexPhraseQuery extends Query { static class ComplexPhraseQuery extends Query {
String field; final String field;
String phrasedQueryStringContents; final String phrasedQueryStringContents;
int slopFactor; final int slopFactor;
private final boolean inOrder; private final boolean inOrder;
@ -394,6 +394,7 @@ public class ComplexPhraseQueryParser extends QueryParser {
+ ((phrasedQueryStringContents == null) ? 0 + ((phrasedQueryStringContents == null) ? 0
: phrasedQueryStringContents.hashCode()); : phrasedQueryStringContents.hashCode());
result = prime * result + slopFactor; result = prime * result + slopFactor;
result = prime * result + (inOrder ? 1 : 0);
return result; return result;
} }
@ -422,7 +423,7 @@ public class ComplexPhraseQueryParser extends QueryParser {
return false; return false;
if (slopFactor != other.slopFactor) if (slopFactor != other.slopFactor)
return false; return false;
return true; return inOrder == other.inOrder;
} }
} }
} }

View File

@ -140,6 +140,31 @@ public class TestComplexPhraseQuery extends LuceneTestCase {
checkMatches("name:\"john smith\"~2 AND role:designer AND id:3", "3"); checkMatches("name:\"john smith\"~2 AND role:designer AND id:3", "3");
} }
public void testHashcodeEquals() throws Exception {
ComplexPhraseQueryParser qp = new ComplexPhraseQueryParser(TEST_VERSION_CURRENT, defaultFieldName, analyzer);
qp.setInOrder(true);
qp.setFuzzyPrefixLength(1);
String qString = "\"aaa* bbb*\"";
Query q = qp.parse(qString);
Query q2 = qp.parse(qString);
assertEquals(q.hashCode(), q2.hashCode());
assertEquals(q, q2);
qp.setInOrder(false); // SOLR-6011
q2 = qp.parse(qString);
// although the general contract of hashCode can't guarantee different values, if we only change one thing
// about a single query, it normally should result in a different value (and will with the current
// implementation in ComplexPhraseQuery)
assertTrue(q.hashCode() != q2.hashCode());
assertTrue(!q.equals(q2));
assertTrue(!q2.equals(q));
}
@Override @Override
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();

View File

@ -30,7 +30,7 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
@BeforeClass @BeforeClass
public static void beforeClass() throws Exception { public static void beforeClass() throws Exception {
initCore("solrconfig-query-parser-init.xml","schema-complex-phrase.xml"); initCore("solrconfig.xml","schema15.xml");
} }
@Override @Override
@ -54,43 +54,43 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
assertQ(req("q", "{!complexphrase} \"john smith\"") assertQ(req("q", "{!complexphrase} \"john smith\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
); );
assertQ(req("q", "{!complexphrase} \"j* smyth~\"") assertQ(req("q", "{!complexphrase} \"j* smyth~\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
); );
assertQ(req("q", "{!complexphrase} \"(jo* -john) smith\"") assertQ(req("q", "{!complexphrase} \"(jo* -john) smith\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
); );
assertQ(req("q", "{!complexphrase} \"jo* smith\"~2") assertQ(req("q", "{!complexphrase} \"jo* smith\"~2")
, "//result[@numFound='3']" , "//result[@numFound='3']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
); );
assertQ(req("q", "{!complexphrase} \"jo* [sma TO smz]\"") assertQ(req("q", "{!complexphrase} \"jo* [sma TO smz]\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
); );
assertQ(req("q", "{!complexphrase} \"john\"") assertQ(req("q", "{!complexphrase} \"john\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
); );
assertQ(req("q", "{!complexphrase} \"(john johathon) smith\"") assertQ(req("q", "{!complexphrase} \"(john johathon) smith\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
); );
} }
@ -113,55 +113,55 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
assertQ("Simple multi-term still works", assertQ("Simple multi-term still works",
sumLRF.makeRequest("name:\"john smith\""), sumLRF.makeRequest("name:\"john smith\""),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//result[@numFound='1']" "//result[@numFound='1']"
); );
assertQ(req("q", "{!complexphrase} name:\"john smith\""), assertQ(req("q", "{!complexphrase} name:\"john smith\""),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//result[@numFound='1']" "//result[@numFound='1']"
); );
assertQ("wildcards and fuzzies are OK in phrases", assertQ("wildcards and fuzzies are OK in phrases",
sumLRF.makeRequest("name:\"j* smyth~\""), sumLRF.makeRequest("name:\"j* smyth~\""),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//doc[./int[@name='id']='2']", "//doc[./str[@name='id']='2']",
"//result[@numFound='2']" "//result[@numFound='2']"
); );
assertQ("boolean logic works", assertQ("boolean logic works",
sumLRF.makeRequest("name:\"(jo* -john) smith\""), sumLRF.makeRequest("name:\"(jo* -john) smith\""),
"//doc[./int[@name='id']='2']", "//doc[./str[@name='id']='2']",
"//result[@numFound='1']" "//result[@numFound='1']"
); );
assertQ("position logic works", assertQ("position logic works",
sumLRF.makeRequest("name:\"jo* smith\"~2"), sumLRF.makeRequest("name:\"jo* smith\"~2"),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//doc[./int[@name='id']='2']", "//doc[./str[@name='id']='2']",
"//doc[./int[@name='id']='3']", "//doc[./str[@name='id']='3']",
"//result[@numFound='3']" "//result[@numFound='3']"
); );
assertQ("range queries supported", assertQ("range queries supported",
sumLRF.makeRequest("name:\"jo* [sma TO smz]\""), sumLRF.makeRequest("name:\"jo* [sma TO smz]\""),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//doc[./int[@name='id']='2']", "//doc[./str[@name='id']='2']",
"//result[@numFound='2']" "//result[@numFound='2']"
); );
assertQ("Simple single-term still works", assertQ("Simple single-term still works",
sumLRF.makeRequest("name:\"john\""), sumLRF.makeRequest("name:\"john\""),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//doc[./int[@name='id']='3']", "//doc[./str[@name='id']='3']",
"//result[@numFound='2']" "//result[@numFound='2']"
); );
assertQ("OR inside phrase works", assertQ("OR inside phrase works",
sumLRF.makeRequest("name:\"(john johathon) smith\""), sumLRF.makeRequest("name:\"(john johathon) smith\""),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//doc[./int[@name='id']='2']", "//doc[./str[@name='id']='2']",
"//result[@numFound='2']" "//result[@numFound='2']"
); );
@ -192,9 +192,9 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
assertQ("range queries supported", assertQ("range queries supported",
sumLRF.makeRequest("name:[sma TO smz]"), sumLRF.makeRequest("name:[sma TO smz]"),
"//doc[./int[@name='id']='1']", "//doc[./str[@name='id']='1']",
"//doc[./int[@name='id']='2']", "//doc[./str[@name='id']='2']",
"//doc[./int[@name='id']='3']", "//doc[./str[@name='id']='3']",
"//result[@numFound='3']" "//result[@numFound='3']"
); );
@ -246,29 +246,29 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
assertQ(req("q", "{!complexphrase} name:\"protein digest\" AND text:\"dna rules\"") assertQ(req("q", "{!complexphrase} name:\"protein digest\" AND text:\"dna rules\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
); );
assertQ(req("q", "{!complexphrase} name:\"prot* dige*\" AND text:\"d* r*\"") assertQ(req("q", "{!complexphrase} name:\"prot* dige*\" AND text:\"d* r*\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
); );
assertQ(req("q", "{!complexphrase inOrder=\"false\"} name:\"dna* rule*\" AND text:\"prot* diges*\"") assertQ(req("q", "{!complexphrase inOrder=\"false\"} name:\"dna* rule*\" AND text:\"prot* diges*\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
); );
assertQ(req("q", "{!unorderedcomplexphrase} name:\"protein digest\" AND text:\"dna rules\"~2") assertQ(req("q", "{!complexphrase inOrder=false} name:\"protein digest\" AND text:\"dna rules\"~2")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
, "//doc[./int[@name='id']='4']" , "//doc[./str[@name='id']='4']"
); );
assertQ(req("q", "{!unorderedcomplexphrase inOrder=\"true\"} name:\"protein digest\" AND text:\"dna rules\"") assertQ(req("q", "{!complexphrase inOrder=\"true\"} name:\"protein digest\" AND text:\"dna rules\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
); );
} }
@ -290,49 +290,49 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
*/ */
assertQ(req("q", "{!complexphrase} \"protein digest\"") assertQ(req("q", "{!complexphrase} \"protein digest\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
); );
assertQ(req("q", "{!complexphrase} \"pro* di*\"") assertQ(req("q", "{!complexphrase} \"pro* di*\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
); );
assertQ(req("q", "{!complexphrase} name:\"protein digest\"") assertQ(req("q", "{!complexphrase} name:\"protein digest\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
); );
assertQ(req("q", "{!complexphrase} name:\"pro* di*\"") assertQ(req("q", "{!complexphrase} name:\"pro* di*\"")
, "//result[@numFound='1']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
); );
/** /**
* unordered phrase query returns two documents. * unordered phrase query returns two documents.
*/ */
assertQ(req("q", "{!unorderedcomplexphrase} \"digest protein\"") assertQ(req("q", "{!complexphrase inOrder=false} \"digest protein\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
); );
assertQ(req("q", "{!unorderedcomplexphrase} \"di* pro*\"") assertQ(req("q", "{!complexphrase inOrder=false} \"di* pro*\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
); );
assertQ(req("q", "{!unorderedcomplexphrase} name:\"digest protein\"") assertQ(req("q", "{!complexphrase inOrder=false} name:\"digest protein\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
, "//doc[./int[@name='id']='4']" , "//doc[./str[@name='id']='4']"
); );
assertQ(req("q", "{!unorderedcomplexphrase} name:\"di* pro*\"") assertQ(req("q", "{!complexphrase inOrder=false} name:\"di* pro*\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
, "//doc[./int[@name='id']='4']" , "//doc[./str[@name='id']='4']"
); );
/** /**
@ -340,8 +340,13 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
*/ */
assertQ(req("q", "{!complexphrase inOrder=false} \"di* pro*\"") assertQ(req("q", "{!complexphrase inOrder=false} \"di* pro*\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
);
assertQ(req("q", "{!complexphrase inOrder=true} \"di* pro*\"")
, "//result[@numFound='1']"
); );
/** /**
@ -349,8 +354,8 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
*/ */
assertQ(req("q", "{!complexphrase inOrder=false df=name} \"di* pro*\"") assertQ(req("q", "{!complexphrase inOrder=false df=name} \"di* pro*\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='3']" , "//doc[./str[@name='id']='3']"
, "//doc[./int[@name='id']='4']" , "//doc[./str[@name='id']='4']"
); );
} }
/** /**
@ -369,14 +374,13 @@ public class TestComplexPhraseQParserPlugin extends AbstractSolrTestCase {
assertQ(req("q", "{!complexphrase} \"sulfur-reducing bacteria\"") assertQ(req("q", "{!complexphrase} \"sulfur-reducing bacteria\"")
, "//result[@numFound='2']" , "//result[@numFound='2']"
, "//doc[./int[@name='id']='1']" , "//doc[./str[@name='id']='1']"
, "//doc[./int[@name='id']='2']" , "//doc[./str[@name='id']='2']"
); );
// the analysis for "name" currently does not break on "-" (only whitespace) and thus only matches one doc
assertQ(req("q", "{!complexphrase} name:\"sulfur-reducing bacteria\"") assertQ(req("q", "{!complexphrase} name:\"sulfur-reducing bacteria\"")
, "//result[@numFound='2']" , "//result[@numFound='1']"
, "//doc[./int[@name='id']='3']"
, "//doc[./int[@name='id']='4']"
); );
} }
} }