Author: epbernard
Date: 2010-04-21 13:17:21 -0400 (Wed, 21 Apr 2010)
New Revision: 19270
Added:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedMultiFieldsTermQueryBuilder.java
Removed:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedSingleTermQueryBuilder.java
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermContext.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermMatchingContext.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermContext.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermMatchingContext.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/FieldContext.java
search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java
Log:
HSEARCH-508 Support query on multiple fields
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermContext.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermContext.java 2010-04-21
15:42:33 UTC (rev 19269)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermContext.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -11,4 +11,6 @@
*/
TermMatchingContext onField(String field);
+ TermMatchingContext onFields(String... field);
+
}
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermMatchingContext.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermMatchingContext.java 2010-04-21
15:42:33 UTC (rev 19269)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermMatchingContext.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -9,8 +9,8 @@
*/
TermTermination matches(String text);
-// /**
-// * field / property the term query is executed on
-// */
-// TermMatchingContext onField(String field);
+ /**
+ * field / property the term query is executed on
+ */
+ TermMatchingContext andField(String field);
}
Copied:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedMultiFieldsTermQueryBuilder.java
(from rev 19264,
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedSingleTermQueryBuilder.java)
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedMultiFieldsTermQueryBuilder.java
(rev 0)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedMultiFieldsTermQueryBuilder.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -0,0 +1,138 @@
+package org.hibernate.search.query.dsl.v2.impl;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.tokenattributes.TermAttribute;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FuzzyQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.WildcardQuery;
+
+import org.hibernate.annotations.common.AssertionFailure;
+import org.hibernate.search.SearchException;
+import org.hibernate.search.SearchFactory;
+import org.hibernate.search.query.dsl.v2.TermTermination;
+
+/**
+* @author Emmanuel Bernard
+*/
+public class ConnectedMultiFieldsTermQueryBuilder implements TermTermination {
+ private final String text;
+ private final Analyzer queryAnalyzer;
+ private final QueryCustomizer queryCustomizer;
+ private final QueryContext queryContext;
+ private final List<FieldContext> fieldContexts;
+
+ public ConnectedMultiFieldsTermQueryBuilder(QueryContext queryContext,
+ String text,
+ List<FieldContext> fieldContexts,
+ QueryCustomizer queryCustomizer,
+ Analyzer queryAnalyzer,
+ SearchFactory factory) {
+ this.queryContext = queryContext;
+ this.text = text;
+ this.queryAnalyzer = queryAnalyzer;
+ this.queryCustomizer = queryCustomizer;
+ this.fieldContexts = fieldContexts;
+ }
+
+ public Query createQuery() {
+ final int size = fieldContexts.size();
+ if ( size == 1 ) {
+ return queryCustomizer.setWrappedQuery( createQuery( fieldContexts.get( 0 ) )
).createQuery();
+ }
+ else {
+ BooleanQuery aggregatedFieldsQuery = new BooleanQuery( );
+ for ( FieldContext fieldContext : fieldContexts ) {
+ aggregatedFieldsQuery.add( createQuery( fieldContext ), BooleanClause.Occur.SHOULD
);
+ }
+ return queryCustomizer.setWrappedQuery( aggregatedFieldsQuery ).createQuery();
+ }
+ }
+
+ public Query createQuery(FieldContext fieldContext) {
+ final Query perFieldQuery;
+ if ( fieldContext.isIgnoreAnalyzer() ) {
+ perFieldQuery = createTermQuery( fieldContext, text );
+ }
+ else {
+ List<String> terms;
+ try {
+ terms = getAllTermsFromText( fieldContext.getField(), text, queryAnalyzer );
+ }
+ catch ( IOException e ) {
+ throw new AssertionFailure("IO exception while reading String stream??",
e);
+ }
+ if ( terms.size() == 0 ) {
+ throw new SearchException( "try to search with an empty string: " +
fieldContext.getField() );
+ }
+ else if (terms.size() == 1 ) {
+ perFieldQuery = createTermQuery( fieldContext, terms.get( 0 ) );
+ }
+ else {
+ BooleanQuery booleanQuery = new BooleanQuery();
+ for (String term : terms) {
+ Query termQuery = createTermQuery(fieldContext, term);
+ booleanQuery.add( termQuery, BooleanClause.Occur.SHOULD );
+ }
+ perFieldQuery = booleanQuery;
+ }
+ }
+ return fieldContext.getFieldCustomizer().setWrappedQuery( perFieldQuery
).createQuery();
+ }
+
+ private Query createTermQuery(FieldContext fieldContext, String term) {
+ Query query;
+ switch ( queryContext.getApproximation() ) {
+ case EXACT:
+ query = new TermQuery( new Term( fieldContext.getField(), term ) );
+ break;
+ case WILDCARD:
+ query = new WildcardQuery( new Term( fieldContext.getField(), term ) );
+ break;
+ case FUZZY:
+ query = new FuzzyQuery(
+ new Term( fieldContext.getField(), term ),
+ queryContext.getThreshold(),
+ queryContext.getPrefixLength() );
+ break;
+ default:
+ throw new AssertionFailure( "Unknown approximation: " +
queryContext.getApproximation());
+ }
+ return query;
+ }
+
+ private List<String> getAllTermsFromText(String fieldName, String text, Analyzer
analyzer) throws IOException {
+ //it's better not to apply the analyzer with windcards as * and ? can be mistakenly
removed
+ List<String> terms = new ArrayList<String>();
+ if ( queryContext.getApproximation() == QueryContext.Approximation.WILDCARD ) {
+ terms.add( text );
+ }
+ else {
+ Reader reader = new StringReader(text);
+ TokenStream stream = analyzer.reusableTokenStream( fieldName, reader);
+ TermAttribute attribute = (TermAttribute) stream.addAttribute( TermAttribute.class );
+ stream.reset();
+
+ while ( stream.incrementToken() ) {
+ if ( attribute.termLength() > 0 ) {
+ String term = attribute.term();
+ terms.add( term );
+ }
+ }
+ stream.end();
+ stream.close();
+ }
+ return terms;
+ }
+
+}
Deleted:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedSingleTermQueryBuilder.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedSingleTermQueryBuilder.java 2010-04-21
15:42:33 UTC (rev 19269)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedSingleTermQueryBuilder.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -1,127 +0,0 @@
-package org.hibernate.search.query.dsl.v2.impl;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.tokenattributes.TermAttribute;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.FuzzyQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.WildcardQuery;
-
-import org.hibernate.annotations.common.AssertionFailure;
-import org.hibernate.search.SearchException;
-import org.hibernate.search.SearchFactory;
-import org.hibernate.search.query.dsl.v2.TermTermination;
-
-/**
-* @author Emmanuel Bernard
-*/
-public class ConnectedSingleTermQueryBuilder implements TermTermination {
- private final SearchFactory factory;
- private final String field;
- private final String text;
- private final Analyzer queryAnalyzer;
- private final QueryCustomizer queryCustomizer;
- private boolean ignoreAnalyzer;
- private final QueryContext context;
-
- public ConnectedSingleTermQueryBuilder(
- QueryContext context,
- boolean ignoreAnalyzer,
- String text,
- String field,
- QueryCustomizer queryCustomizer,
- Analyzer queryAnalyzer,
- SearchFactory factory) {
- this.context = context;
- this.factory = factory;
- this.field = field;
- this.text = text;
- this.queryAnalyzer = queryAnalyzer;
- this.queryCustomizer = queryCustomizer;
- this.ignoreAnalyzer = ignoreAnalyzer;
- }
-
- public Query createQuery() {
- final Query result;
- if ( ignoreAnalyzer ) {
- result = createTermQuery( text );
- }
- else {
- List<String> terms;
- try {
- terms = getAllTermsFromText( field, text, queryAnalyzer );
- }
- catch ( IOException e ) {
- throw new AssertionFailure("IO exception while reading String stream??",
e);
- }
- if ( terms.size() == 0 ) {
- throw new SearchException("try to search with an empty string: " + field);
- }
- else if (terms.size() == 1 ) {
- result = createTermQuery( terms.get( 0 ) );
- }
- else {
- BooleanQuery booleanQuery = new BooleanQuery();
- for (String term : terms) {
- Query termQuery = createTermQuery(term);
- booleanQuery.add( termQuery, BooleanClause.Occur.SHOULD );
- }
- result = booleanQuery;
- }
- }
- return queryCustomizer.setWrappedQuery( result ).createQuery();
- }
-
- private Query createTermQuery(String term) {
- Query query;
- switch ( context.getApproximation() ) {
- case EXACT:
- query = new TermQuery( new Term(field, term) );
- break;
- case WILDCARD:
- query = new WildcardQuery( new Term(field, term) );
- break;
- case FUZZY:
- query = new FuzzyQuery( new Term(field, term), context.getThreshold(),
context.getPrefixLength() );
- break;
- default:
- throw new AssertionFailure( "Unknown approximation: " +
context.getApproximation());
- }
- return query;
- }
-
- private List<String> getAllTermsFromText(String fieldName, String text, Analyzer
analyzer) throws IOException {
- //it's better not to apply the analyzer with windcards as * and ? can be mistakenly
removed
- List<String> terms = new ArrayList<String>();
- if ( context.getApproximation() == QueryContext.Approximation.WILDCARD ) {
- terms.add( text );
- }
- else {
- Reader reader = new StringReader(text);
- TokenStream stream = analyzer.reusableTokenStream( fieldName, reader);
- TermAttribute attribute = (TermAttribute) stream.addAttribute( TermAttribute.class );
- stream.reset();
-
- while ( stream.incrementToken() ) {
- if ( attribute.termLength() > 0 ) {
- String term = attribute.term();
- terms.add( term );
- }
- }
- stream.end();
- stream.close();
- }
- return terms;
- }
-
-}
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermContext.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermContext.java 2010-04-21
15:42:33 UTC (rev 19269)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermContext.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -27,6 +27,10 @@
return new ConnectedTermMatchingContext(context, field, queryCustomizer, queryAnalyzer,
factory);
}
+ public TermMatchingContext onFields(String... fields) {
+ return new ConnectedTermMatchingContext(context, fields, queryCustomizer,
queryAnalyzer, factory);
+ }
+
public TermContext boostedTo(float boost) {
queryCustomizer.boostedTo( boost );
return this;
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermMatchingContext.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermMatchingContext.java 2010-04-21
15:42:33 UTC (rev 19269)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermMatchingContext.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -14,36 +14,61 @@
*/
public class ConnectedTermMatchingContext implements TermMatchingContext {
private final SearchFactory factory;
- private final String field;
private final Analyzer queryAnalyzer;
private final QueryCustomizer queryCustomizer;
- private boolean ignoreAnalyzer;
private final QueryContext queryContext;
- private final FieldContext fieldContext;
- private final List<ConnectedTermMatchingContext> otherFields;
+ private final List<FieldContext> fieldContexts;
+ //when a varargs of fields are passed, apply the same customization for all.
+ //keep the index of the first context in this queue
+ private int firstOfContext = 0;
public ConnectedTermMatchingContext(QueryContext queryContext,
String field, QueryCustomizer queryCustomizer, Analyzer queryAnalyzer, SearchFactory
factory) {
this.factory = factory;
- this.field = field;
this.queryAnalyzer = queryAnalyzer;
this.queryCustomizer = queryCustomizer;
this.queryContext = queryContext;
- this.otherFields = new ArrayList<ConnectedTermMatchingContext>();
- this.fieldContext = null;
+ this.fieldContexts = new ArrayList<FieldContext>(4);
+ this.fieldContexts.add( new FieldContext( field ) );
}
+ public ConnectedTermMatchingContext(QueryContext queryContext,
+ String[] fields, QueryCustomizer queryCustomizer, Analyzer queryAnalyzer,
SearchFactory factory) {
+ this.factory = factory;
+ this.queryAnalyzer = queryAnalyzer;
+ this.queryCustomizer = queryCustomizer;
+ this.queryContext = queryContext;
+ this.fieldContexts = new ArrayList<FieldContext>(fields.length);
+ for (String field : fields) {
+ this.fieldContexts.add( new FieldContext( field ) );
+ }
+ }
+
public TermTermination matches(String text) {
- return new ConnectedSingleTermQueryBuilder( queryContext, ignoreAnalyzer, text, field,
queryCustomizer, queryAnalyzer, factory);
+ return new ConnectedMultiFieldsTermQueryBuilder( queryContext, text, fieldContexts,
queryCustomizer, queryAnalyzer, factory);
}
+ public TermMatchingContext andField(String field) {
+ this.fieldContexts.add( new FieldContext( field ) );
+ this.firstOfContext = fieldContexts.size() - 1;
+ return this;
+ }
+
public TermMatchingContext boostedTo(float boost) {
- queryCustomizer.boostedTo( boost );
+ for ( FieldContext fieldContext : getCurrentFieldContexts() ) {
+ fieldContext.getFieldCustomizer().boostedTo( boost );
+ }
return this;
}
+ private List<FieldContext> getCurrentFieldContexts() {
+ return fieldContexts.subList( firstOfContext, fieldContexts.size() );
+ }
+
public TermMatchingContext ignoreAnalyzer() {
- this.ignoreAnalyzer = true;
+ for ( FieldContext fieldContext : getCurrentFieldContexts() ) {
+ fieldContext.setIgnoreAnalyzer( true );
+ }
return this;
}
}
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/FieldContext.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/FieldContext.java 2010-04-21
15:42:33 UTC (rev 19269)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/FieldContext.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -7,28 +7,18 @@
*/
public class FieldContext {
private final String field;
- private final Analyzer queryAnalyzer;
- private final QueryCustomizer queryCustomizer;
private boolean ignoreAnalyzer;
+ private final QueryCustomizer fieldCustomizer;
- public FieldContext(String field, Analyzer queryAnalyzer, QueryCustomizer
queryCustomizer) {
+ public FieldContext(String field) {
this.field = field;
- this.queryAnalyzer = queryAnalyzer;
- this.queryCustomizer = queryCustomizer;
+ this.fieldCustomizer = new QueryCustomizer();
}
public String getField() {
return field;
}
- public Analyzer getQueryAnalyzer() {
- return queryAnalyzer;
- }
-
- public QueryCustomizer getQueryCustomizer() {
- return queryCustomizer;
- }
-
public boolean isIgnoreAnalyzer() {
return ignoreAnalyzer;
}
@@ -36,4 +26,8 @@
public void setIgnoreAnalyzer(boolean ignoreAnalyzer) {
this.ignoreAnalyzer = ignoreAnalyzer;
}
+
+ public QueryCustomizer getFieldCustomizer() {
+ return fieldCustomizer;
+ }
}
Modified:
search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java
===================================================================
---
search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java 2010-04-21
15:42:33 UTC (rev 19269)
+++
search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java 2010-04-21
17:17:21 UTC (rev 19270)
@@ -14,7 +14,6 @@
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.search.Environment;
-import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.annotations.Factory;
@@ -137,6 +136,54 @@
cleanData( fts );
}
+ public void testMultipleFields() throws Exception {
+ FullTextSession fts = initData();
+
+ Transaction transaction = fts.beginTransaction();
+ final QueryBuilder monthQb = fts.getSearchFactory()
+ .buildQueryBuilder().forEntity( Month.class ).get();
+ Query
+
+ //combined query, January and february both contain whitening but February in a longer
text
+ query = monthQb.exact()
+ .onField( "mythology" )
+ .andField( "history" )
+ .matches( "whitening" ).createQuery();
+
+ List<Month> results = fts.createFullTextQuery( query, Month.class ).list();
+ assertEquals( 2, results.size() );
+ assertEquals( "January", results.get( 0 ).getName() );
+
+ //combined query, January and february both contain whitening but February in a longer
text
+ query = monthQb.exact()
+ .onFields( "mythology", "history" )
+ .boostedTo( 30 )
+ .matches( "whitening" ).createQuery();
+
+ results = fts.createFullTextQuery( query, Month.class ).list();
+ assertEquals( 2, results.size() );
+ assertEquals( "January", results.get( 0 ).getName() );
+
+ //boosted query, January and february both contain whitening but February in a longer
text
+ //since history is boosted, February should come first though
+ query = monthQb.exact()
+ .onField( "mythology" )
+ .andField( "history" )
+ .boostedTo( 30 )
+ .matches( "whitening" )
+ .createQuery();
+
+ results = fts.createFullTextQuery( query, Month.class ).list();
+ assertEquals( 2, results.size() );
+ assertEquals( "February", results.get( 0 ).getName() );
+
+ //FIXME add other method tests besides boostedTo
+
+ transaction.commit();
+
+ cleanData( fts );
+ }
+
public void testBoolean() throws Exception {
FullTextSession fts = initData();