Author: epbernard
Date: 2006-11-01 16:32:11 -0500 (Wed, 01 Nov 2006)
New Revision: 10678
Modified:
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/IteratorImpl.java
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/LuceneQueryImpl.java
branches/Lucene_Integration/HibernateExt/metadata/src/test/org/hibernate/lucene/test/query/LuceneQueryTest.java
Log:
ANN-460 filter by classes on queries
Modified:
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java
===================================================================
---
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java 2006-11-01
19:07:24 UTC (rev 10677)
+++
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/DocumentBuilder.java 2006-11-01
21:32:11 UTC (rev 10678)
@@ -219,7 +219,7 @@
}
{
Field classField =
- new Field( CLASS_FIELDNAME, instanceClass.getName(), Field.Store.YES,
Field.Index.NO );
+ new Field( CLASS_FIELDNAME, instanceClass.getName(), Field.Store.YES,
Field.Index.UN_TOKENIZED );
doc.add( classField );
idBridge.set( idKeywordName, id, doc, Field.Store.YES,
Field.Index.UN_TOKENIZED, idBoost );
}
Modified:
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/IteratorImpl.java
===================================================================
---
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/IteratorImpl.java 2006-11-01
19:07:24 UTC (rev 10677)
+++
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/IteratorImpl.java 2006-11-01
21:32:11 UTC (rev 10678)
@@ -2,6 +2,7 @@
package org.hibernate.lucene.query;
import java.util.Iterator;
+import java.util.List;
import org.hibernate.Session;
@@ -10,15 +11,15 @@
*/
public class IteratorImpl implements Iterator {
- private final EntityInfo[] entityInfos;
+ private final List<EntityInfo> entityInfos;
private final Session session;
private int index = 0;
private final int size;
- public IteratorImpl(EntityInfo[] entityInfos, Session session) {
+ public IteratorImpl(List<EntityInfo> entityInfos, Session session) {
this.entityInfos = entityInfos;
this.session = session;
- this.size = entityInfos.length;
+ this.size = entityInfos.size();
}
public boolean hasNext() {
@@ -26,13 +27,13 @@
}
public Object next() {
- Object object = session.get( entityInfos[index].clazz, entityInfos[index].id );
+ Object object = session.get( entityInfos.get(index).clazz, entityInfos.get(index).id
);
index++;
return object;
}
public void remove() {
//TODO this is theorically doable
- throw new UnsupportedOperationException( "Cannot remove from a lucene query
interator" );
+ throw new UnsupportedOperationException( "Cannot remove from a lucene query
iterator" );
}
}
Modified:
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/LuceneQueryImpl.java
===================================================================
---
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/LuceneQueryImpl.java 2006-11-01
19:07:24 UTC (rev 10677)
+++
branches/Lucene_Integration/HibernateExt/metadata/src/java/org/hibernate/lucene/query/LuceneQueryImpl.java 2006-11-01
21:32:11 UTC (rev 10678)
@@ -18,7 +18,11 @@
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiSearcher;
import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.store.Directory;
+import org.apache.lucene.index.Term;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
@@ -41,6 +45,7 @@
private static final Log log = LogFactory.getLog( LuceneQueryImpl.class );
private org.apache.lucene.search.Query luceneQuery;
private Class[] classes;
+ private Set<Class> classesAndSubclasses;
private Integer firstResult;
private Integer maxResults;
private int resultSize;
@@ -71,18 +76,18 @@
Searcher searcher = buildSearcher( listener );
Hits hits;
try {
- hits = searcher.search( luceneQuery );
+ org.apache.lucene.search.Query query = filterQueryByClasses(luceneQuery);
+ hits = searcher.search( query );
setResultSize( hits );
int first = first();
int max = max( first, hits );
- EntityInfo[] entityInfos = new EntityInfo[max - first + 1];
+ List<EntityInfo> entityInfos = new ArrayList<EntityInfo>(max - first +
1);
for ( int index = first; index <= max; index++ ) {
Document document = hits.doc( index );
EntityInfo entityInfo = new EntityInfo();
entityInfo.clazz = DocumentBuilder.getDocumentClass( document );
- //FIXME should check that clazz match classes but this complexify a lot the
firstResult/maxResult
entityInfo.id = DocumentBuilder.getDocumentId( listener, entityInfo.clazz, document
);
- entityInfos[index - first] = entityInfo;
+ entityInfos.add(entityInfo);
}
return new IteratorImpl( entityInfos, (Session) this.session );
}
@@ -106,7 +111,8 @@
Searcher searcher = buildSearcher( listener );
Hits hits;
try {
- hits = searcher.search( luceneQuery );
+ org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
+ hits = searcher.search( query );
setResultSize( hits );
int first = first();
int max = max( first, hits );
@@ -134,7 +140,9 @@
Searcher searcher = buildSearcher( listener );
Hits hits;
try {
- hits = searcher.search( luceneQuery );
+ org.apache.lucene.search.Query query = filterQueryByClasses(luceneQuery);
+ log.warn(query.toString());
+ hits = searcher.search( query );
setResultSize( hits );
int first = first();
int max = max( first, hits );
@@ -143,10 +151,10 @@
for ( int index = first; index <= max; index++ ) {
Document document = hits.doc( index );
Class clazz = DocumentBuilder.getDocumentClass( document );
- //FIXME should check that clazz match classes but this complexify a lot the
firstResult/maxResult
Serializable id = DocumentBuilder.getDocumentId( listener, clazz, document );
result.add( sess.load( clazz, id ) );
- //use load to benefit from the batch-size (but facing some proxy casting issues...
+ //use load to benefit from the batch-size
+ //we don't face proxy casting issues since the exact class is extracted from the
index
}
//then initialize the objects
for ( Object element : result ) {
@@ -167,11 +175,33 @@
}
}
+ private org.apache.lucene.search.Query
filterQueryByClasses(org.apache.lucene.search.Query luceneQuery) {
+ //A query filter is more practical than a manual class filtering post query (esp on
scrollable resultsets)
+ //it also probably minimise the memory footprint
+ if (classesAndSubclasses == null) {
+ return luceneQuery;
+ }
+ else {
+ BooleanQuery classFilter = new BooleanQuery();
+ //annihilate the scoring impact of DocumentBuilder.CLASS_FIELDNAME
+ classFilter.setBoost( 0 );
+ for (Class clazz : classesAndSubclasses) {
+ Term t = new Term( DocumentBuilder.CLASS_FIELDNAME, clazz.getName() );
+ TermQuery termQuery = new TermQuery( t );
+ classFilter.add( termQuery, BooleanClause.Occur.SHOULD );
+ }
+ BooleanQuery filteredQuery = new BooleanQuery();
+ filteredQuery.add( luceneQuery, BooleanClause.Occur.MUST );
+ filteredQuery.add( classFilter, BooleanClause.Occur.MUST );
+ return filteredQuery;
+ }
+ }
+
private int max(int first, Hits hits) {
return maxResults == null ?
- first + hits.length() - 1 :
+ hits.length() - 1 :
maxResults + first < hits.length() ?
- first + maxResults :
+ first + maxResults - 1 :
hits.length() - 1;
}
@@ -181,6 +211,7 @@
0;
}
+ //TODO change classesAndSubclasses by side effect, which is a mismatch with the Searcher
return, fix that.
private Searcher buildSearcher(LuceneEventListener listener) {
Map<Class, DocumentBuilder<Object>> builders =
listener.getDocumentBuilders();
Set<Directory> directories = new HashSet<Directory>();
@@ -189,6 +220,7 @@
for ( DocumentBuilder builder : builders.values() ) {
directories.add( builder.getDirectoryProvider().getDirectory() );
}
+ classesAndSubclasses = null;
}
else {
Set<Class> involvedClasses = new HashSet<Class>( classes.length );
@@ -203,6 +235,7 @@
if ( builder == null ) throw new HibernateException( "Not a mapped entity:
" + clazz );
directories.add( builder.getDirectoryProvider().getDirectory() );
}
+ classesAndSubclasses = involvedClasses;
}
//set up the searcher
Modified:
branches/Lucene_Integration/HibernateExt/metadata/src/test/org/hibernate/lucene/test/query/LuceneQueryTest.java
===================================================================
---
branches/Lucene_Integration/HibernateExt/metadata/src/test/org/hibernate/lucene/test/query/LuceneQueryTest.java 2006-11-01
19:07:24 UTC (rev 10677)
+++
branches/Lucene_Integration/HibernateExt/metadata/src/test/org/hibernate/lucene/test/query/LuceneQueryTest.java 2006-11-01
21:32:11 UTC (rev 10678)
@@ -56,11 +56,57 @@
assertTrue( Hibernate.isInitialized( element ) );
s.delete( element );
}
- for (Object element : s.createQuery( "from Book" ).list() ) s.delete( element
);
+ for (Object element : s.createQuery( "from java.lang.Object" ).list() )
s.delete( element );
tx.commit();
s.close();
}
+ public void testFirstMax() throws Exception {
+ LuceneSession s = new LuceneSession( openSession() );
+ Transaction tx = s.beginTransaction();
+ Clock clock = new Clock(1, "Seiko");
+ s.save( clock );
+ clock = new Clock( 2, "Festina");
+ s.save( clock );
+ Book book = new Book(1, "La chute de la petite reine a travers les yeux de
Festina", "La chute de la petite reine a travers les yeux de Festina,
blahblah");
+ s.save(book);
+ book = new Book(2, "La gloire de mon p�re", "Les deboires de mon p�re en
v�lo");
+ s.save(book);
+ tx.commit();
+ s.clear();
+ tx = s.beginTransaction();
+ QueryParser parser = new QueryParser("title", new StopAnalyzer() );
+
+ Query query = parser.parse( "summary:Festina Or brand:Seiko" );
+ org.hibernate.Query hibQuery = s.createLuceneQuery( query, Clock.class, Book.class );
+ hibQuery.setFirstResult( 1 );
+ List result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( "first result no max result", 1, result.size() );
+
+ hibQuery.setFirstResult( 0 );
+ hibQuery.setMaxResults( 1 );
+ result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( "max result set", 1, result.size() );
+
+ hibQuery.setFirstResult( 0 );
+ hibQuery.setMaxResults( 3 );
+ result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( "max result out of limit", 2, result.size() );
+
+ hibQuery.setFirstResult( 2 );
+ hibQuery.setMaxResults( 3 );
+ result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( "first result out of limit", 0, result.size() );
+
+ for (Object element : s.createQuery( "from java.lang.Object" ).list() )
s.delete( element );
+ tx.commit();
+ s.close();
+ }
+
public void testIterator() throws Exception {
LuceneSession s = new LuceneSession( openSession() );
Transaction tx = s.beginTransaction();
@@ -93,7 +139,7 @@
s.delete( result.next() );
}
assertEquals( 2, index );
- for (Object element : s.createQuery( "from Book" ).list() ) s.delete( element
);
+ for (Object element : s.createQuery( "from java.lang.Object" ).list() )
s.delete( element );
tx.commit();
s.close();
}
@@ -136,7 +182,7 @@
while ( result.next() ) {
s.delete( result.get()[0] );
}
- for (Object element : s.createQuery( "from Book" ).list() ) s.delete( element
);
+ for (Object element : s.createQuery( "from java.lang.Object" ).list() )
s.delete( element );
tx.commit();
s.close();
}
@@ -144,6 +190,8 @@
public void testMultipleEntityPerIndex() throws Exception {
LuceneSession s = new LuceneSession( openSession() );
Transaction tx = s.beginTransaction();
+ Clock clock = new Clock(1, "Seiko");
+ s.save( clock );
Book book = new Book(1, "La chute de la petite reine a travers les yeux de
Festina", "La chute de la petite reine a travers les yeux de Festina,
blahblah");
s.save(book);
AlternateBook alternateBook = new AlternateBook(1, "La chute de la petite reine a
travers les yeux de Festina");
@@ -158,8 +206,30 @@
List result = hibQuery.list();
assertNotNull( result );
assertEquals( "Query with explicit class filter", 1, result.size() );
+
+ query = parser.parse( "summary:Festina" );
+ hibQuery = s.createLuceneQuery( query, Clock.class, Book.class );
+ Iterator it = hibQuery.iterate();
+ assertTrue( it.hasNext() );
+ assertNotNull( it.next() );
+ assertFalse( it.hasNext() );
- query = parser.parse( "summary:Festina" );
+ query = parser.parse( "summary:Festina" );
+ hibQuery = s.createLuceneQuery( query, Clock.class, Book.class );
+ ScrollableResults sr = hibQuery.scroll();
+ assertTrue( sr.first() );
+ assertNotNull( sr.get() );
+ assertFalse( sr.next() );
+ sr.close();
+
+ query = parser.parse( "summary:Festina OR brand:seiko" );
+ hibQuery = s.createLuceneQuery( query, Clock.class, Book.class );
+ hibQuery.setMaxResults( 2 );
+ result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( "Query with explicit class filter and limit", 2, result.size()
);
+
+ query = parser.parse( "summary:Festina" );
hibQuery = s.createLuceneQuery( query );
result = hibQuery.list();
assertNotNull( result );