LUCENE-2945: Fix equals/hashCode for surround query parser generated queries

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1166156 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Erik Hatcher 2011-09-07 13:04:29 +00:00
parent ab3883291e
commit 35e7439ca4
11 changed files with 328 additions and 80 deletions

View File

@ -530,6 +530,9 @@ Bug fixes
rarely cause deletions to be incorrectly applied. (Yonik Seeley, rarely cause deletions to be incorrectly applied. (Yonik Seeley,
Simon Willnauer, Mike McCandless) Simon Willnauer, Mike McCandless)
* LUCENE-2945: Fix hashCode/equals for surround query parser generated queries.
(Paul Elschot, Simon Rosenthal, gsingers via ehatcher)
======================= Lucene 3.x (not yet released) ================ ======================= Lucene 3.x (not yet released) ================
Bug fixes Bug fixes

View File

@ -45,8 +45,19 @@ public class BasicQueryFactory {
public int getNrQueriesMade() {return queriesMade;} public int getNrQueriesMade() {return queriesMade;}
public int getMaxBasicQueries() {return maxBasicQueries;} public int getMaxBasicQueries() {return maxBasicQueries;}
private synchronized void checkMax() throws TooManyBasicQueries { public String toString() {
if (queriesMade >= maxBasicQueries) return getClass().getName()
+ "(maxBasicQueries: " + maxBasicQueries
+ ", queriesMade: " + queriesMade
+ ")";
}
private boolean atMax() {
return queriesMade >= maxBasicQueries;
}
protected synchronized void checkMax() throws TooManyBasicQueries {
if (atMax())
throw new TooManyBasicQueries(getMaxBasicQueries()); throw new TooManyBasicQueries(getMaxBasicQueries());
queriesMade++; queriesMade++;
} }
@ -60,6 +71,22 @@ public class BasicQueryFactory {
checkMax(); checkMax();
return new SpanTermQuery(term); return new SpanTermQuery(term);
} }
@Override
public int hashCode() {
return getClass().hashCode() ^ (atMax() ? 7 : 31*32);
}
/** Two BasicQueryFactory's are equal when they generate
* the same types of basic queries, or both cannot generate queries anymore.
*/
@Override
public boolean equals(Object obj) {
if (! (obj instanceof BasicQueryFactory))
return false;
BasicQueryFactory other = (BasicQueryFactory) obj;
return atMax() == other.atMax();
}
} }

View File

@ -24,36 +24,36 @@ import org.apache.lucene.search.Query;
public abstract class ComposedQuery extends SrndQuery { public abstract class ComposedQuery extends SrndQuery {
public ComposedQuery(List qs, boolean operatorInfix, String opName) { public ComposedQuery(List<SrndQuery> qs, boolean operatorInfix, String opName) {
recompose(qs); recompose(qs);
this.operatorInfix = operatorInfix; this.operatorInfix = operatorInfix;
this.opName = opName; this.opName = opName;
} }
protected void recompose(List queries) { protected void recompose(List<SrndQuery> queries) {
if (queries.size() < 2) throw new AssertionError("Too few subqueries"); if (queries.size() < 2) throw new AssertionError("Too few subqueries");
this.queries = queries; this.queries = queries;
} }
private String opName; protected String opName;
public String getOperatorName() {return opName;} public String getOperatorName() {return opName;}
private List queries; protected List<SrndQuery> queries;
public Iterator getSubQueriesIterator() {return queries.listIterator();} public Iterator<SrndQuery> getSubQueriesIterator() {return queries.listIterator();}
public int getNrSubQueries() {return queries.size();} public int getNrSubQueries() {return queries.size();}
public SrndQuery getSubQuery(int qn) {return (SrndQuery) queries.get(qn);} public SrndQuery getSubQuery(int qn) {return queries.get(qn);}
private boolean operatorInfix; private boolean operatorInfix;
public boolean isOperatorInfix() { return operatorInfix; } /* else prefix operator */ public boolean isOperatorInfix() { return operatorInfix; } /* else prefix operator */
public List<Query> makeLuceneSubQueriesField(String fn, BasicQueryFactory qf) { public List<Query> makeLuceneSubQueriesField(String fn, BasicQueryFactory qf) {
List<Query> luceneSubQueries = new ArrayList<Query>(); List<Query> luceneSubQueries = new ArrayList<Query>();
Iterator sqi = getSubQueriesIterator(); Iterator<SrndQuery> sqi = getSubQueriesIterator();
while (sqi.hasNext()) { while (sqi.hasNext()) {
luceneSubQueries.add( ((SrndQuery) sqi.next()).makeLuceneQueryField(fn, qf)); luceneSubQueries.add( (sqi.next()).makeLuceneQueryField(fn, qf));
} }
return luceneSubQueries; return luceneSubQueries;
} }
@ -77,7 +77,7 @@ public abstract class ComposedQuery extends SrndQuery {
protected void infixToString(StringBuilder r) { protected void infixToString(StringBuilder r) {
/* Brackets are possibly redundant in the result. */ /* Brackets are possibly redundant in the result. */
Iterator sqi = getSubQueriesIterator(); Iterator<SrndQuery> sqi = getSubQueriesIterator();
r.append(getBracketOpen()); r.append(getBracketOpen());
if (sqi.hasNext()) { if (sqi.hasNext()) {
r.append(sqi.next().toString()); r.append(sqi.next().toString());
@ -92,7 +92,7 @@ public abstract class ComposedQuery extends SrndQuery {
} }
protected void prefixToString(StringBuilder r) { protected void prefixToString(StringBuilder r) {
Iterator sqi = getSubQueriesIterator(); Iterator<SrndQuery> sqi = getSubQueriesIterator();
r.append(getOperatorName()); /* prefix operator */ r.append(getOperatorName()); /* prefix operator */
r.append(getBracketOpen()); r.append(getBracketOpen());
if (sqi.hasNext()) { if (sqi.hasNext()) {
@ -109,9 +109,9 @@ public abstract class ComposedQuery extends SrndQuery {
@Override @Override
public boolean isFieldsSubQueryAcceptable() { public boolean isFieldsSubQueryAcceptable() {
/* at least one subquery should be acceptable */ /* at least one subquery should be acceptable */
Iterator sqi = getSubQueriesIterator(); Iterator<SrndQuery> sqi = getSubQueriesIterator();
while (sqi.hasNext()) { while (sqi.hasNext()) {
if (((SrndQuery) sqi.next()).isFieldsSubQueryAcceptable()) { if ((sqi.next()).isFieldsSubQueryAcceptable()) {
return true; return true;
} }
} }

View File

@ -16,7 +16,6 @@ package org.apache.lucene.queryparser.surround.query;
* limitations under the License. * limitations under the License.
*/ */
import java.util.List; import java.util.List;
import java.util.Iterator; import java.util.Iterator;
@ -39,12 +38,14 @@ public class DistanceQuery extends ComposedQuery implements DistanceSubQuery {
this.ordered = ordered; this.ordered = ordered;
} }
private int opDistance; private int opDistance;
public int getOpDistance() {return opDistance;} public int getOpDistance() {return opDistance;}
private boolean ordered; private boolean ordered;
public boolean subQueriesOrdered() {return ordered;} public boolean subQueriesOrdered() {return ordered;}
@Override
public String distanceSubQueryNotAllowed() { public String distanceSubQueryNotAllowed() {
Iterator<?> sqi = getSubQueriesIterator(); Iterator<?> sqi = getSubQueriesIterator();
while (sqi.hasNext()) { while (sqi.hasNext()) {
@ -62,30 +63,13 @@ public class DistanceQuery extends ComposedQuery implements DistanceSubQuery {
return null; /* subqueries acceptable */ return null; /* subqueries acceptable */
} }
@Override
public void addSpanQueries(SpanNearClauseFactory sncf) throws IOException { public void addSpanQueries(SpanNearClauseFactory sncf) throws IOException {
Query snq = getSpanNearQuery(sncf.getIndexReader(), Query snq = getSpanNearQuery(sncf.getIndexReader(),
sncf.getFieldName(), sncf.getFieldName(),
getWeight(), getWeight(),
sncf.getBasicQueryFactory()); sncf.getBasicQueryFactory());
sncf.addSpanNearQuery(snq); sncf.addSpanQuery(snq);
}
@Override
public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
return new Query () {
@Override
public String toString(String fn) {
return getClass().toString() + " " + fieldName + " (" + fn + "?)";
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
return getSpanNearQuery(reader, fieldName, getBoost(), qf);
}
};
} }
public Query getSpanNearQuery( public Query getSpanNearQuery(
@ -93,7 +77,7 @@ public class DistanceQuery extends ComposedQuery implements DistanceSubQuery {
String fieldName, String fieldName,
float boost, float boost,
BasicQueryFactory qf) throws IOException { BasicQueryFactory qf) throws IOException {
SpanQuery[] spanNearClauses = new SpanQuery[getNrSubQueries()]; SpanQuery[] spanClauses = new SpanQuery[getNrSubQueries()];
Iterator<?> sqi = getSubQueriesIterator(); Iterator<?> sqi = getSubQueriesIterator();
int qi = 0; int qi = 0;
while (sqi.hasNext()) { while (sqi.hasNext()) {
@ -108,14 +92,18 @@ public class DistanceQuery extends ComposedQuery implements DistanceSubQuery {
return SrndQuery.theEmptyLcnQuery; return SrndQuery.theEmptyLcnQuery;
} }
spanNearClauses[qi] = sncf.makeSpanNearClause(); spanClauses[qi] = sncf.makeSpanClause();
qi++; qi++;
} }
SpanNearQuery r = new SpanNearQuery(spanNearClauses, getOpDistance() - 1, subQueriesOrdered()); SpanNearQuery r = new SpanNearQuery(spanClauses, getOpDistance() - 1, subQueriesOrdered());
r.setBoost(boost); r.setBoost(boost);
return r; return r;
} }
@Override
public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
return new DistanceRewriteQuery(this, fieldName, qf);
}
} }

View File

@ -0,0 +1,38 @@
package org.apache.lucene.queryparser.surround.query;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Query;
class DistanceRewriteQuery extends RewriteQuery<DistanceQuery> {
DistanceRewriteQuery(
DistanceQuery srndQuery,
String fieldName,
BasicQueryFactory qf) {
super(srndQuery, fieldName, qf);
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
return srndQuery.getSpanNearQuery(reader, fieldName, getBoost(), qf);
}
}

View File

@ -0,0 +1,81 @@
package org.apache.lucene.queryparser.surround.query;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Query;
abstract class RewriteQuery<SQ extends SrndQuery> extends Query {
protected final SQ srndQuery;
protected final String fieldName;
protected final BasicQueryFactory qf;
RewriteQuery(
SQ srndQuery,
String fieldName,
BasicQueryFactory qf) {
this.srndQuery = srndQuery;
this.fieldName = fieldName;
this.qf = qf;
}
@Override
abstract public Query rewrite(IndexReader reader) throws IOException;
@Override
public String toString() {
return toString(null);
}
@Override
public String toString(String field) {
return getClass().getName()
+ (field == null ? "" : "(unused: " + field + ")")
+ "(" + fieldName
+ ", " + srndQuery.toString()
+ ", " + qf.toString()
+ ")";
}
@Override
public int hashCode() {
return getClass().hashCode()
^ fieldName.hashCode()
^ qf.hashCode()
^ srndQuery.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (! getClass().equals(obj.getClass()))
return false;
RewriteQuery other = (RewriteQuery)obj;
return fieldName.equals(other.fieldName)
&& qf.equals(other.qf)
&& srndQuery.equals(other.srndQuery);
}
/** @throws UnsupportedOperationException */
@Override
public Object clone() {
throw new UnsupportedOperationException();
}
}

View File

@ -17,12 +17,9 @@ package org.apache.lucene.queryparser.surround.query;
*/ */
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
public abstract class SimpleTerm public abstract class SimpleTerm
@ -39,6 +36,10 @@ public abstract class SimpleTerm
public abstract String toStringUnquoted(); public abstract String toStringUnquoted();
/** @deprecated (March 2011) Not normally used, to be removed from Lucene 4.0.
* This class implementing Comparable is to be removed at the same time.
*/
@Deprecated
public int compareTo(SimpleTerm ost) { public int compareTo(SimpleTerm ost) {
/* for ordering terms and prefixes before using an index, not used */ /* for ordering terms and prefixes before using an index, not used */
return this.toStringUnquoted().compareTo( ost.toStringUnquoted()); return this.toStringUnquoted().compareTo( ost.toStringUnquoted());
@ -70,35 +71,10 @@ public abstract class SimpleTerm
void visitMatchingTerm(Term t)throws IOException; void visitMatchingTerm(Term t)throws IOException;
} }
@Override
public String distanceSubQueryNotAllowed() {return null;} public String distanceSubQueryNotAllowed() {return null;}
@Override @Override
public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
return new Query() {
@Override
public String toString(String fn) {
return getClass().toString() + " " + fieldName + " (" + fn + "?)";
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
final List<Query> luceneSubQueries = new ArrayList<Query>();
visitMatchingTerms( reader, fieldName,
new MatchingTermVisitor() {
public void visitMatchingTerm(Term term) throws IOException {
luceneSubQueries.add(qf.newTermQuery(term));
}
});
return (luceneSubQueries.size() == 0) ? SrndQuery.theEmptyLcnQuery
: (luceneSubQueries.size() == 1) ? luceneSubQueries.get(0)
: SrndBooleanQuery.makeBooleanQuery(
/* luceneSubQueries all have default weight */
luceneSubQueries, BooleanClause.Occur.SHOULD); /* OR the subquery terms */
}
};
}
public void addSpanQueries(final SpanNearClauseFactory sncf) throws IOException { public void addSpanQueries(final SpanNearClauseFactory sncf) throws IOException {
visitMatchingTerms( visitMatchingTerms(
sncf.getIndexReader(), sncf.getIndexReader(),
@ -109,6 +85,11 @@ public abstract class SimpleTerm
} }
}); });
} }
@Override
public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
return new SimpleTermRewriteQuery(this, fieldName, qf);
}
} }

View File

@ -0,0 +1,52 @@
package org.apache.lucene.queryparser.surround.query;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.index.Term;
class SimpleTermRewriteQuery extends RewriteQuery<SimpleTerm> {
SimpleTermRewriteQuery(
SimpleTerm srndQuery,
String fieldName,
BasicQueryFactory qf) {
super(srndQuery, fieldName, qf);
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
final List<Query> luceneSubQueries = new ArrayList<Query>();
srndQuery.visitMatchingTerms(reader, fieldName,
new SimpleTerm.MatchingTermVisitor() {
public void visitMatchingTerm(Term term) throws IOException {
luceneSubQueries.add(qf.newTermQuery(term));
}
});
return (luceneSubQueries.size() == 0) ? SrndQuery.theEmptyLcnQuery
: (luceneSubQueries.size() == 1) ? luceneSubQueries.get(0)
: SrndBooleanQuery.makeBooleanQuery(
/* luceneSubQueries all have default weight */
luceneSubQueries, BooleanClause.Occur.SHOULD); /* OR the subquery terms */
}
}

View File

@ -64,7 +64,7 @@ import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery; import org.apache.lucene.search.spans.SpanTermQuery;
public class SpanNearClauseFactory { public class SpanNearClauseFactory { // FIXME: rename to SpanClauseFactory
public SpanNearClauseFactory(IndexReader reader, String fieldName, BasicQueryFactory qf) { public SpanNearClauseFactory(IndexReader reader, String fieldName, BasicQueryFactory qf) {
this.reader = reader; this.reader = reader;
this.fieldName = fieldName; this.fieldName = fieldName;
@ -101,16 +101,15 @@ public class SpanNearClauseFactory {
addSpanQueryWeighted(stq, weight); addSpanQueryWeighted(stq, weight);
} }
public void addSpanNearQuery(Query q) { public void addSpanQuery(Query q) {
if (q == SrndQuery.theEmptyLcnQuery) if (q == SrndQuery.theEmptyLcnQuery)
return; return;
if (! (q instanceof SpanNearQuery)) if (! (q instanceof SpanQuery))
throw new AssertionError("Expected SpanNearQuery: " + q.toString(getFieldName())); throw new AssertionError("Expected SpanQuery: " + q.toString(getFieldName()));
/* CHECKME: wrap in Hashable...? */ addSpanQueryWeighted((SpanQuery)q, q.getBoost());
addSpanQueryWeighted((SpanNearQuery)q, q.getBoost());
} }
public SpanQuery makeSpanNearClause() { public SpanQuery makeSpanClause() {
SpanQuery [] spanQueries = new SpanQuery[size()]; SpanQuery [] spanQueries = new SpanQuery[size()];
Iterator<SpanQuery> sqi = weightBySpanQuery.keySet().iterator(); Iterator<SpanQuery> sqi = weightBySpanQuery.keySet().iterator();
int i = 0; int i = 0;

View File

@ -53,6 +53,9 @@ public abstract class SrndQuery implements Cloneable {
public abstract Query makeLuceneQueryFieldNoBoost(String fieldName, BasicQueryFactory qf); public abstract Query makeLuceneQueryFieldNoBoost(String fieldName, BasicQueryFactory qf);
/** This method is used by {@link #hashCode()} and {@link #equals(Object)},
* see LUCENE-2945.
*/
@Override @Override
public abstract String toString(); public abstract String toString();
@ -67,7 +70,31 @@ public abstract class SrndQuery implements Cloneable {
} }
} }
/* An empty Lucene query */ /** For subclasses of {@link SrndQuery} within the package
* {@link org.apache.lucene.queryparser.surround.query}
* it is not necessary to override this method,
* @see #toString().
*/
@Override
public int hashCode() {
return getClass().hashCode() ^ toString().hashCode();
}
/** For subclasses of {@link SrndQuery} within the package
* {@link org.apache.lucene.queryparser.surround.query}
* it is not necessary to override this method,
* @see #toString().
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (! getClass().equals(obj.getClass()))
return false;
return toString().equals(obj.toString());
}
/** An empty Lucene query */
public final static Query theEmptyLcnQuery = new BooleanQuery() { /* no changes allowed */ public final static Query theEmptyLcnQuery = new BooleanQuery() { /* no changes allowed */
@Override @Override
public void setBoost(float boost) { public void setBoost(float boost) {

View File

@ -0,0 +1,52 @@
package org.apache.lucene.queryparser.surround.query;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import junit.framework.Assert;
import org.apache.lucene.queryparser.surround.parser.QueryParser;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryUtils;
import org.apache.lucene.util.LuceneTestCase;
import org.junit.Test;
/**
*
*
**/
public class SrndQueryTest extends LuceneTestCase {
void checkEqualParsings(String s1, String s2) throws Exception {
String fieldName = "foo";
BasicQueryFactory qf = new BasicQueryFactory(16);
Query lq1, lq2;
lq1 = QueryParser.parse(s1).makeLuceneQueryField(fieldName, qf);
lq2 = QueryParser.parse(s2).makeLuceneQueryField(fieldName, qf);
QueryUtils.checkEqual(lq1, lq2);
}
@Test
public void testHashEquals() throws Exception {
//grab some sample queries from Test02Boolean and Test03Distance and
//check there hashes and equals
checkEqualParsings("word1 w word2", " word1 w word2 ");
checkEqualParsings("2N(w1,w2,w3)", " 2N(w1, w2 , w3)");
checkEqualParsings("abc?", " abc? ");
checkEqualParsings("w*rd?", " w*rd?");
}
}