[hibernate-commits] Hibernate SVN: r19022 - in search/trunk/hibernate-search/src: main/java/org/hibernate/search/query/dsl/v2/impl and 1 other directories.
hibernate-commits at lists.jboss.org
hibernate-commits at lists.jboss.org
Thu Mar 18 14:58:28 EDT 2010
Author: epbernard
Date: 2010-03-18 14:58:27 -0400 (Thu, 18 Mar 2010)
New Revision: 19022
Added:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/BooleanJunction.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/MustJunction.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryCustomization.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/BooleanQueryBuilder.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/QueryCustomizer.java
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryBuilder.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermCustomization.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedQueryBuilder.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermContext.java
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/Month.java
Log:
HSEARCH-414 add boolean query framework (needs test) and common query methods (boostedTo etc)
Added: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/BooleanJunction.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/BooleanJunction.java (rev 0)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/BooleanJunction.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -0,0 +1,26 @@
+package org.hibernate.search.query.dsl.v2;
+
+import org.apache.lucene.search.Query;
+
+/**
+ * Represents a boolean query that can contains one or more elements to join
+ *
+ * @author Emmanuel Bernard
+ */
+public interface BooleanJunction<T extends BooleanJunction> extends QueryCustomization<T> {
+ /**
+ * The boolean query results should match the subquery
+ */
+ BooleanJunction should(Query query);
+
+ /**
+ * The boolean query results must (or must not) match the subquery
+ * Call the .not() method to ensure results of the boolean query do NOT match the subquery.
+ */
+ MustJunction must(Query query);
+
+ /**
+ * Return the lucene query representing the boolean operation
+ */
+ Query createQuery();
+}
Added: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/MustJunction.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/MustJunction.java (rev 0)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/MustJunction.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -0,0 +1,14 @@
+package org.hibernate.search.query.dsl.v2;
+
+/**
+ * Represents the context in which a must clause is described.
+ *
+ * @author Emmanuel Bernard
+ */
+public interface MustJunction extends BooleanJunction<MustJunction> {
+ /**
+ * Negate the must clause.
+ * Results of the boolean query do NOT match the subquery.
+ */
+ BooleanJunction not();
+}
Modified: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryBuilder.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryBuilder.java 2010-03-18 18:17:05 UTC (rev 19021)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryBuilder.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -8,4 +8,10 @@
* build a term query
*/
TermContext term();
+
+
+ /**
+ * Boolean query
+ */
+ BooleanJunction<BooleanJunction> bool();
}
Added: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryCustomization.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryCustomization.java (rev 0)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/QueryCustomization.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -0,0 +1,40 @@
+package org.hibernate.search.query.dsl.v2;
+
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.Query;
+
+/**
+ * Operations common to all types of queries
+ *
+ * @author Emmanuel Bernard
+ */
+public interface QueryCustomization<T> {
+
+ /**
+ * Boost the query to a given value
+ * Most of the time positive float:
+ * - lower than 1 to diminish the weight
+ * - higher than 1 to increase the weight
+ *
+ * Could be negative but not unless you understand what is going on (advanced)
+ */
+ T boostedTo(float boost);
+
+ /**
+ * All results matching the query have a constant score equals to the boost
+ * FIXME is that true?
+ */
+ T constantScore();
+
+ /**
+ * Filter the query results with the Filter instance
+ */
+ T filter(Filter filter);
+
+ //TODO filter(String) + parameters
+
+ /**
+ * Create a Lucene query
+ */
+ Query createQuery();
+}
Modified: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermCustomization.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermCustomization.java 2010-03-18 18:17:05 UTC (rev 19021)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/TermCustomization.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -5,7 +5,7 @@
/**
* @author Emmanuel Bernard
*/
-public interface TermCustomization {
+public interface TermCustomization extends QueryCustomization<TermCustomization> {
/**
* Advanced
* Do not execute the analyzer on the text.
@@ -28,9 +28,4 @@
//TODO make it mutually exclusive with fuzzy use (but that's much more complex)
TermCustomization wildcard();
- /**
- * Create a Lucene query
- */
- Query createQuery();
-
}
Added: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/BooleanQueryBuilder.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/BooleanQueryBuilder.java (rev 0)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/BooleanQueryBuilder.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -0,0 +1,87 @@
+package org.hibernate.search.query.dsl.v2.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+
+import org.hibernate.annotations.common.AssertionFailure;
+import org.hibernate.search.query.dsl.v2.BooleanJunction;
+import org.hibernate.search.query.dsl.v2.MustJunction;
+
+/**
+ * @author Emmanuel Bernard
+ */
+class BooleanQueryBuilder implements MustJunction {
+ private final List<BooleanClause> clauses;
+ private final QueryCustomizer queryCustomizer;
+
+ BooleanQueryBuilder() {
+ clauses = new ArrayList<BooleanClause>(5);
+ queryCustomizer = new QueryCustomizer();
+ }
+
+ public BooleanJunction not() {
+ final int lastIndex = clauses.size() -1;
+ final BooleanClause last = clauses.get(lastIndex);
+ if ( ! last.getOccur().equals( BooleanClause.Occur.MUST ) ) {
+ throw new AssertionFailure( "Cannot negate class: " + last.getOccur() );
+ }
+ clauses.add( lastIndex, new BooleanClause( last.getQuery(), BooleanClause.Occur.MUST_NOT ) );
+ return this;
+ }
+
+ public BooleanJunction should(Query query) {
+ clauses.add( new BooleanClause( query, BooleanClause.Occur.SHOULD ) );
+ return this;
+ }
+
+ public MustJunction must(Query query) {
+ clauses.add( new BooleanClause( query, BooleanClause.Occur.SHOULD ) );
+ return this;
+ }
+
+ public MustJunction boostedTo(float boost) {
+ queryCustomizer.boostedTo( boost );
+ return this;
+ }
+
+ public MustJunction constantScore() {
+ queryCustomizer.constantScore();
+ return this;
+ }
+
+ public MustJunction filter(Filter filter) {
+ queryCustomizer.filter(filter);
+ return this;
+ }
+
+ public Query createQuery() {
+ final int nbrOfClauses = clauses.size();
+ if ( nbrOfClauses == 0) {
+ throw new AssertionFailure( "Cannot create an empty boolean query" );
+ }
+ else if ( nbrOfClauses == 1 ) {
+ final BooleanClause uniqueClause = clauses.get( 0 );
+ if ( uniqueClause.getOccur().equals( BooleanClause.Occur.MUST_NOT ) ) {
+ //FIXME We have two choices here, raise an exception or combine with an All query. #2 is done atm.
+ //TODO which normfield to use and how to pass it?
+ should( new MatchAllDocsQuery() );
+ }
+ else {
+ //optimize
+ return queryCustomizer.setWrappedQuery( uniqueClause.getQuery() ).createQuery();
+ }
+ }
+
+ BooleanQuery query = new BooleanQuery( );
+ for (BooleanClause clause : clauses) {
+ query.add( clause );
+ }
+ return queryCustomizer.setWrappedQuery( query ).createQuery();
+ }
+}
Modified: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedQueryBuilder.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedQueryBuilder.java 2010-03-18 18:17:05 UTC (rev 19021)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedQueryBuilder.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -3,6 +3,7 @@
import org.apache.lucene.analysis.Analyzer;
import org.hibernate.search.SearchFactory;
+import org.hibernate.search.query.dsl.v2.BooleanJunction;
import org.hibernate.search.query.dsl.v2.QueryBuilder;
import org.hibernate.search.query.dsl.v2.TermContext;
@@ -23,4 +24,8 @@
public TermContext term() {
return new ConnectedTermContext( queryAnalyzer, factory);
}
+
+ public BooleanJunction bool() {
+ return new BooleanQueryBuilder();
+ }
}
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-03-18 18:17:05 UTC (rev 19021)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/ConnectedTermContext.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -12,6 +12,7 @@
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
@@ -28,7 +29,7 @@
/**
* @author Emmanuel Bernard
*/
-public class ConnectedTermContext implements TermContext {
+class ConnectedTermContext implements TermContext {
private final SearchFactory factory;
private final Analyzer queryAnalyzer;
@@ -62,6 +63,7 @@
private final String field;
private final String text;
private final Analyzer queryAnalyzer;
+ private final QueryCustomizer queryCustomizer;
private boolean ignoreAnalyzer;
private Approximation approximation = Approximation.EXACT;
@@ -73,6 +75,7 @@
this.field = field;
this.text = text;
this.queryAnalyzer = queryAnalyzer;
+ this.queryCustomizer = new QueryCustomizer();
}
public TermCustomization ignoreAnalyzer() {
@@ -96,9 +99,25 @@
return this;
}
+ public TermCustomization boostedTo(float boost) {
+ queryCustomizer.boostedTo( boost );
+ return this;
+ }
+
+ public TermCustomization constantScore() {
+ queryCustomizer.constantScore();
+ return this;
+ }
+
+ public TermCustomization filter(Filter filter) {
+ queryCustomizer.filter( filter );
+ return this;
+ }
+
public Query createQuery() {
+ final Query result;
if ( ignoreAnalyzer ) {
- return createTermQuery( text );
+ result = createTermQuery( text );
}
else {
List<String> terms;
@@ -112,18 +131,19 @@
throw new SearchException("try to search with an empty string: " + field);
}
else if (terms.size() == 1 ) {
- return createTermQuery( terms.get( 0 ) );
+ result = createTermQuery( terms.get( 0 ) );
}
else {
- BooleanQuery finalQuery = new BooleanQuery();
+ BooleanQuery booleanQuery = new BooleanQuery();
for (String term : terms) {
Query termQuery = createTermQuery(term);
//termQuery.setBoost( boost );
- finalQuery.add( termQuery, BooleanClause.Occur.SHOULD );
+ booleanQuery.add( termQuery, BooleanClause.Occur.SHOULD );
}
- return finalQuery;
+ result = booleanQuery;
}
}
+ return queryCustomizer.setWrappedQuery( result ).createQuery();
}
private Query createTermQuery(String term) {
@@ -146,19 +166,26 @@
}
private List<String> getAllTermsFromText(String fieldName, String text, Analyzer analyzer) throws IOException {
- Reader reader = new StringReader(text);
- TokenStream stream = analyzer.reusableTokenStream( fieldName, reader);
- TermAttribute attribute = (TermAttribute) stream.addAttribute( TermAttribute.class );
- stream.reset();
+ //it's better not to apply the analyzer with windcards as * and ? can be mistakenly removed
List<String> terms = new ArrayList<String>();
- while ( stream.incrementToken() ) {
- if ( attribute.termLength() > 0 ) {
- String term = attribute.term();
- terms.add( term );
+ if ( approximation == 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();
}
- stream.end();
- stream.close();
return terms;
}
Added: search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/QueryCustomizer.java
===================================================================
--- search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/QueryCustomizer.java (rev 0)
+++ search/trunk/hibernate-search/src/main/java/org/hibernate/search/query/dsl/v2/impl/QueryCustomizer.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -0,0 +1,56 @@
+package org.hibernate.search.query.dsl.v2.impl;
+
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.FilteredQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.QueryFilter;
+import org.apache.lucene.search.QueryWrapperFilter;
+
+import org.hibernate.annotations.common.AssertionFailure;
+import org.hibernate.search.query.dsl.v2.QueryCustomization;
+
+/**
+ * @author Emmanuel Bernard
+ */
+class QueryCustomizer implements QueryCustomization<QueryCustomizer> {
+ private float boost = 1f;
+ private boolean constantScore;
+ private Query wrappedQuery;
+ private Filter filter;
+
+ public QueryCustomizer boostedTo(float boost) {
+ this.boost = boost;
+ return this;
+ }
+
+ public QueryCustomizer constantScore() {
+ constantScore = true;
+ return this;
+ }
+
+ public QueryCustomizer filter(Filter filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ public QueryCustomizer setWrappedQuery(Query wrappedQuery) {
+ this.wrappedQuery = wrappedQuery;
+ return this;
+ }
+
+ public Query createQuery() {
+ Query finalQuery = wrappedQuery;
+ if (wrappedQuery == null) {
+ throw new AssertionFailure( "wrapped query not set" );
+ }
+ finalQuery.setBoost( boost );
+ if (filter != null) {
+ finalQuery = new FilteredQuery(finalQuery, filter);
+ }
+ if ( constantScore ) {
+ finalQuery = new ConstantScoreQuery( new QueryWrapperFilter( finalQuery ) );
+ }
+ return finalQuery;
+ }
+}
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-03-18 18:17:05 UTC (rev 19021)
+++ search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -14,6 +14,7 @@
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;
@@ -27,16 +28,9 @@
public class DSLTest extends SearchTestCase {
public void testTermQueryOnAnalyzer() throws Exception {
- Session session = openSession();
- FullTextSession fts = Search.getFullTextSession( session );
+ FullTextSession fts = initData();
+
Transaction transaction = fts.beginTransaction();
- fts.persist( new Month("January", "Month of colder and whitening") );
- fts.persist( new Month("February", "Month of snowboarding") );
- transaction.commit();
-
- fts.clear();
-
- transaction = fts.beginTransaction();
final QueryBuilder monthQb = fts.getSearchFactory()
.buildQueryBuilder().forEntity( Month.class ).get();
Query
@@ -67,27 +61,15 @@
query = monthQb.term().on( "mythology" ).matches( "Month" ).createQuery();
- final List<Month> results = fts.createFullTextQuery( query, Month.class ).list();
- assertEquals( 2, results.size() );
+ transaction.commit();
- for (Month entity : results) {
- fts.delete( entity );
- }
- transaction.commit();
- fts.close();
+ cleanData( fts );
}
- public void tesFuzzyAndWildcardQuery() throws Exception {
- Session session = openSession();
- FullTextSession fts = Search.getFullTextSession( session );
+ public void testFuzzyAndWildcardQuery() throws Exception {
+ FullTextSession fts = initData();
+
Transaction transaction = fts.beginTransaction();
- fts.persist( new Month("January", "Month of colder and whitening") );
- fts.persist( new Month("February", "Month of snowboarding") );
- transaction.commit();
-
- fts.clear();
-
- transaction = fts.beginTransaction();
final QueryBuilder monthQb = fts.getSearchFactory()
.buildQueryBuilder().forEntity( Month.class ).get();
Query
@@ -106,19 +88,72 @@
.term().on( "mythology" ).matches( "mon*" )
.wildcard()
.createQuery();
-
+ System.out.println(query.toString( ));
assertEquals( 2, fts.createFullTextQuery( query, Month.class ).getResultSize() );
- final List<Month> results = fts.createFullTextQuery( query, Month.class ).list();
+ transaction.commit();
+
+ cleanData( fts );
+ }
+
+ public void testQueryCustomization() 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
+ .bool()
+ .should( monthQb.term().on( "mythology" ).matches( "whitening" ).createQuery() )
+ .should( monthQb.term().on( "history" ).matches( "whitening" ).createQuery() )
+ .createQuery();
+
+ List<Month> 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
+ .bool()
+ .should( monthQb.term().on( "mythology" ).matches( "whitening" ).createQuery() )
+ .should( monthQb.term().on( "history" ).matches( "whitening" ).boostedTo( 30 ).createQuery() )
+ .createQuery();
+
+ results = fts.createFullTextQuery( query, Month.class ).list();
+ assertEquals( 2, results.size() );
+ assertEquals( "February", results.get( 0 ).getName() );
+
+ transaction.commit();
+
+ cleanData( fts );
+ }
+
+ private void cleanData(FullTextSession fts) {
+ Transaction tx = fts.beginTransaction();
+ final List<Month> results = fts.createQuery( "from " + Month.class.getName() ).list();
+ assertEquals( 2, results.size() );
+
for (Month entity : results) {
fts.delete( entity );
}
- transaction.commit();
+ tx.commit();
fts.close();
}
+ private FullTextSession initData() {
+ Session session = openSession();
+ FullTextSession fts = Search.getFullTextSession( session );
+ Transaction tx = fts.beginTransaction();
+ fts.persist( new Month("January", "Month of colder and whitening", "Historically colder than any other month in the northern hemisphere") );
+ fts.persist( new Month("February", "Month of snowboarding", "Historically, the month where we make babies while watching the whitening landscape") );
+ tx.commit();
+ fts.clear();
+ return fts;
+ }
@Override
protected Class<?>[] getMappings() {
Modified: search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/Month.java
===================================================================
--- search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/Month.java 2010-03-18 18:17:05 UTC (rev 19021)
+++ search/trunk/hibernate-search/src/test/java/org/hibernate/search/test/query/dsl/Month.java 2010-03-18 18:58:27 UTC (rev 19022)
@@ -17,9 +17,10 @@
public class Month {
public Month() {}
- public Month(String name, String mythology) {
+ public Month(String name, String mythology, String history) {
this.name = name;
this.mythology = mythology;
+ this.history = history;
}
@Id @GeneratedValue
@@ -40,5 +41,11 @@
public String getMythology() { return mythology; }
public void setMythology(String mythology) { this.mythology = mythology; }
private String mythology;
+
+ @Field
+ public String getHistory() { return history; }
+ public void setHistory(String history) { this.history = history; }
+ private String history;
+
}
More information about the hibernate-commits
mailing list