[hibernate-commits] Hibernate SVN: r11533 - in trunk/HibernateExt/search/src: java/org/hibernate/search/bridge/builtin and 3 other directories.
hibernate-commits at lists.jboss.org
hibernate-commits at lists.jboss.org
Thu May 17 15:19:25 EDT 2007
Author: epbernard
Date: 2007-05-17 15:19:25 -0400 (Thu, 17 May 2007)
New Revision: 11533
Added:
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
Modified:
trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java
trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java
Log:
HSEARCH-53 support for projection (ie load data from the index only)
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -41,9 +41,22 @@
* Defines the Database Query used to load the Lucene results.
* Useful to load a given object graph by refining the fetch modes
*
- * No projection allowed, the root entity must be the only returned type
+ * No projection (criteria.setProjection() ) allowed, the root entity must be the only returned type
* No where restriction can be defined either.
*
*/
FullTextQuery setCriteriaQuery(Criteria criteria);
+
+ /**
+ * Defines the Lucene field names projected and returned in a query result
+ * Each field is converted back to it's object representation, an Object[] being returned for each "row"
+ * (similar to an HQL or a Criteria API projection).
+ *
+ * A projectable field must be stored in the Lucene index and use a {@link org.hibernate.search.bridge.TwoWayFieldBridge}
+ * Unless notified in their JavaDoc, all built-in bridges are two-way. All @DocumentId fields are projectable by design.
+ *
+ * If the projected field is not a projectable field, null is returned in the object[]
+ *
+ */
+ FullTextQuery setProjection(String... fields);
}
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -2,6 +2,7 @@
package org.hibernate.search.bridge.builtin;
import org.hibernate.search.bridge.TwoWayStringBridge;
+import org.hibernate.annotations.common.util.StringHelper;
/**
@@ -12,6 +13,7 @@
public class BooleanBridge implements TwoWayStringBridge {
public Boolean stringToObject(String stringValue) {
+ if ( StringHelper.isEmpty(stringValue) ) return null;
return Boolean.valueOf( stringValue );
}
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -2,6 +2,7 @@
package org.hibernate.search.bridge.builtin;
import org.hibernate.search.bridge.TwoWayStringBridge;
+import org.hibernate.annotations.common.util.StringHelper;
/**
@@ -21,6 +22,7 @@
}
public Enum<? extends Enum> stringToObject(String stringValue) {
+ if ( StringHelper.isEmpty( stringValue ) ) return null;
return Enum.valueOf( clazz, stringValue );
}
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -10,6 +10,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -533,6 +534,70 @@
return (Serializable) builder.getIdBridge().get( builder.getIdKeywordName(), document );
}
+ public static Object[] getDocumentFields(SearchFactory searchFactory, Class clazz, Document document, String[] fields) {
+ DocumentBuilder builder = searchFactory.getDocumentBuilders().get( clazz );
+ if ( builder == null ) throw new SearchException( "No Lucene configuration set up for: " + clazz.getName() );
+ final int fieldNbr = fields.length;
+ Object[] result = new Object[fieldNbr];
+
+ if ( builder.idKeywordName != null ) {
+ populateResult(builder.idKeywordName, builder.idBridge, Field.Store.YES, fields, result, document);
+ }
+
+ final PropertiesMetadata metadata = builder.rootPropertiesMetadata;
+ processFieldsForProjection( metadata, fields, result, document );
+ return result;
+ }
+
+ private static void processFieldsForProjection(PropertiesMetadata metadata, String[] fields, Object[] result, Document document) {
+ final int nbrFoEntityFields = metadata.fieldNames.size();
+ for (int index = 0 ; index < nbrFoEntityFields; index++ ) {
+ populateResult( metadata.fieldNames.get(index),
+ metadata.fieldBridges.get(index),
+ metadata.fieldStore.get(index),
+ fields,
+ result,
+ document
+ );
+ }
+ final int nbrOfEmbeddedObjects = metadata.embeddedPropertiesMetadata.size();
+ for (int index = 0 ; index < nbrOfEmbeddedObjects ; index++) {
+ //there is nothing we can do for collections
+ if ( metadata.embeddedContainers.get(index) == PropertiesMetadata.Container.OBJECT) {
+ processFieldsForProjection( metadata.embeddedPropertiesMetadata.get(index), fields, result, document );
+ }
+ }
+ }
+
+ private static void populateResult(String fieldName, FieldBridge fieldBridge, Field.Store store,
+ String[] fields, Object[] result, Document document) {
+ int matchingPosition = getFieldPosition( fields, fieldName );
+ if (matchingPosition != -1) {
+ //TODO make use of an isTwoWay() method
+ if ( store != Field.Store.NO && TwoWayFieldBridge.class.isAssignableFrom( fieldBridge.getClass() ) ) {
+ result[matchingPosition] = ( (TwoWayFieldBridge) fieldBridge ).get( fieldName, document );
+ if ( log.isTraceEnabled() ) {
+ log.trace( "Field " + fieldName + " projected as " + result[matchingPosition] );
+ }
+ }
+ else {
+ //FIXME else exception?
+ if ( log.isTraceEnabled() ) {
+ log.trace( "Field " + fieldName + " not projected: Store = " + store + " and isTwoWay = "
+ + TwoWayFieldBridge.class.isAssignableFrom( fieldBridge.getClass() ) );
+ }
+ }
+ }
+ }
+
+ private static int getFieldPosition(String[] fields, String fieldName) {
+ int fieldNbr = fields.length;
+ for (int index = 0 ; index < fieldNbr ; index++) {
+ if ( fieldName.equals( fields[index] ) ) return index;
+ }
+ return -1;
+ }
+
public void postInitialize(Set<Class> indexedClasses) {
//this method does not requires synchronization
Class plainClass = reflectionManager.toClass( beanClass );
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -10,14 +10,20 @@
*/
public class DocumentExtractor {
private SearchFactory searchFactory;
- public DocumentExtractor(SearchFactory searchFactory) {
+ private String[] projection;
+
+ public DocumentExtractor(SearchFactory searchFactory, String... projection) {
this.searchFactory = searchFactory;
+ this.projection = projection;
}
- public EntityInfo extract(Document document, String... fields) {
+ public EntityInfo extract(Document document) {
EntityInfo entityInfo = new EntityInfo();
entityInfo.clazz = DocumentBuilder.getDocumentClass( document );
entityInfo.id = DocumentBuilder.getDocumentId( searchFactory, entityInfo.clazz, document );
+ if (projection != null && projection.length > 0) {
+ entityInfo.projection = DocumentBuilder.getDocumentFields( searchFactory, entityInfo.clazz, document, projection );
+ }
//TODO read fields
return entityInfo;
}
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -45,6 +45,7 @@
import org.hibernate.search.engine.ObjectLoader;
import org.hibernate.search.engine.QueryLoader;
import org.hibernate.search.engine.EntityInfo;
+import org.hibernate.search.engine.ProjectionLoader;
import org.hibernate.search.util.ContextHelper;
/**
@@ -64,6 +65,7 @@
private Integer resultSize;
private Sort sort;
private Criteria criteria;
+ private String[] projection;
/**
* classes must be immutable
@@ -105,7 +107,7 @@
Session sess = (Session) this.session;
List<EntityInfo> entityInfos = new ArrayList<EntityInfo>( max - first + 1 );
- DocumentExtractor extractor = new DocumentExtractor( searchFactory );
+ DocumentExtractor extractor = new DocumentExtractor( searchFactory, projection );
for ( int index = first; index <= max; index++ ) {
Document document = hits.doc( index );
entityInfos.add( extractor.extract( document ) );
@@ -127,6 +129,11 @@
}
private Loader getLoader(Session session, SearchFactory searchFactory) {
+ if (projection != null) {
+ ProjectionLoader loader = new ProjectionLoader();
+ loader.init( session, searchFactory );
+ return loader;
+ }
if (criteria != null) {
if (classes.length > 1) throw new SearchException("Cannot mix criteria and multiple entity types");
if ( criteria instanceof CriteriaImpl ) {
@@ -175,7 +182,7 @@
hits = getHits( searcher );
int first = first();
int max = max( first, hits );
- DocumentExtractor extractor = new DocumentExtractor( searchFactory );
+ DocumentExtractor extractor = new DocumentExtractor( searchFactory, projection );
Loader loader = getLoader( (Session) this.session, searchFactory );
return new ScrollableResultsImpl( searcher, hits, first, max, extractor, loader);
}
@@ -208,7 +215,7 @@
int max = max( first, hits );
Session sess = (Session) this.session;
List<EntityInfo> infos = new ArrayList<EntityInfo>( max - first + 1 );
- DocumentExtractor extractor = new DocumentExtractor( searchFactory );
+ DocumentExtractor extractor = new DocumentExtractor( searchFactory, projection );
for ( int index = first; index <= max; index++ ) {
Document document = hits.doc( index );
infos.add( extractor.extract( document ) );
@@ -383,6 +390,16 @@
return this;
}
+ public FullTextQuery setProjection(String... fields) {
+ if ( fields == null || fields.length == 0) {
+ this.projection = null;
+ }
+ else {
+ this.projection = fields;
+ }
+ return this;
+ }
+
public FullTextQuery setFirstResult(int firstResult) {
this.firstResult = firstResult;
return this;
Modified: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -5,6 +5,10 @@
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.Index;
+
/**
* @author Emmanuel Bernard
*/
@@ -21,6 +25,7 @@
this.id = id;
}
+ @Field(index = Index.TOKENIZED, store = Store.YES)
public String getName() {
return name;
}
Modified: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java 2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -6,11 +6,16 @@
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Text;
import org.hibernate.search.annotations.Keyword;
import org.hibernate.search.annotations.Unstored;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.IndexedEmbedded;
/**
* @author Emmanuel Bernard
@@ -23,7 +28,18 @@
private String body;
private String summary;
private Set<Author> authors = new HashSet<Author>();
+ private Author mainAuthor;
+ @IndexedEmbedded
+ @ManyToOne
+ public Author getMainAuthor() {
+ return mainAuthor;
+ }
+
+ public void setMainAuthor(Author mainAuthor) {
+ this.mainAuthor = mainAuthor;
+ }
+
@ManyToMany
public Set<Author> getAuthors() {
return authors;
@@ -60,7 +76,7 @@
this.id = id;
}
- @Text
+ @Field(index = Index.TOKENIZED, store = Store.YES)
public String getSummary() {
return summary;
}
Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java (rev 0)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java 2007-05-17 19:19:25 UTC (rev 11533)
@@ -0,0 +1,74 @@
+//$Id: $
+package org.hibernate.search.test.query;
+
+import java.util.List;
+
+import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.Query;
+import org.hibernate.Transaction;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.test.SearchTestCase;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class ProjectionQueryTest extends SearchTestCase {
+
+ public void testProjection() throws Exception {
+ FullTextSession s = Search.createFullTextSession( openSession() );
+ Transaction tx = s.beginTransaction();
+ 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 );
+ Author emmanuel = new Author();
+ emmanuel.setName( "Emmanuel" );
+ s.save( emmanuel );
+ book.setMainAuthor(emmanuel);
+ tx.commit();
+ s.clear();
+ tx = s.beginTransaction();
+ QueryParser parser = new QueryParser( "title", new StopAnalyzer() );
+
+ Query query = parser.parse( "summary:Festina" );
+ org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query, Book.class );
+ hibQuery.setProjection( "id", "summary", "body", "mainAuthor.name");
+
+ List result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( "Query with no explicit criteria", 1, result.size() );
+ Object[] projection = (Object[]) result.get( 0 );
+ assertEquals( "id", 1, projection[0] );
+ assertEquals( "summary", "La chute de la petite reine a travers les yeux de Festina", projection[1] );
+ assertEquals( "body should be null (non projectable)", null, projection[2] );
+ assertEquals( "mainAuthor.name (embedded objects)", "Emmanuel", projection[3] );
+
+ hibQuery = s.createFullTextQuery( query, Book.class );
+ hibQuery.setProjection();
+ result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( 1, result.size() );
+ assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+
+ hibQuery = s.createFullTextQuery( query, Book.class );
+ hibQuery.setProjection(null);
+ result = hibQuery.list();
+ assertNotNull( result );
+ assertEquals( 1, result.size() );
+ assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+
+ //cleanup
+ for (Object element : s.createQuery( "from " + Book.class.getName() ).list()) s.delete( element );
+ for (Object element : s.createQuery( "from " + Author.class.getName() ).list()) s.delete( element );
+ tx.commit();
+ s.close();
+ }
+
+
+ protected Class[] getMappings() {
+ return new Class[] {
+ Book.class,
+ Author.class
+ };
+ }
+}
More information about the hibernate-commits
mailing list