[hibernate-commits] Hibernate SVN: r12792 - in trunk/HibernateExt/search/src: java/org/hibernate/search/engine and 3 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Fri Jul 20 20:16:16 EDT 2007


Author: epbernard
Date: 2007-07-20 20:16:16 -0400 (Fri, 20 Jul 2007)
New Revision: 12792

Added:
   trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java
Modified:
   trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
Log:
HSEARCH-74 Provide access to SCORE, BOOST, THIS, ID, DOCUMENT through projection (John Griffin)

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -12,10 +12,7 @@
  * @author Emmanuel Bernard
  */
 //TODO return FullTextQuery rather than Query in useful chain methods
-public interface FullTextQuery extends Query {
-	//todo Determine what other lucene specific features to expose via this
-	// interface. Maybe we should give access to the underlying lucene hit
-	// objects?
+public interface FullTextQuery extends Query, ProjectionConstants {
 
 	/**
 	 * Allows to let lucene sort the results. This is useful when you have

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -2,7 +2,6 @@
 package org.hibernate.search;
 
 import org.hibernate.classic.Session;
-import org.hibernate.search.FullTextQuery;
 
 /**
  * Extends the Hibernate {@link Session} with Full text search and indexing capabilities

Added: trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -0,0 +1,34 @@
+//$Id$
+package org.hibernate.search;
+
+/**
+ * Define Projection constants
+ *
+ * @author Emmanuel Bernard
+ */
+public interface ProjectionConstants {
+	/**
+	 * Represtnts the Hibernate Entity returned in a search.
+	 */
+	public String THIS = "__HSearch_This";
+	/**
+	 * The Lucene document returned by a search.
+	 */
+	public String DOCUMENT = "__HSearch_Document";
+	/**
+	 * The legacy document's score from a search.
+	 */
+	public String SCORE = "__HSearch_Score";
+	/**
+	 * The boost value of the legacy document.
+	 */
+	public String BOOST = "__HSearch_Boost";
+	/**
+	 * Object id property
+	 */
+	public String ID = "__HSearch_id";
+	/**
+	 * Object class
+	 */
+	//TODO OBJECT CLASS
+}

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -1,11 +1,19 @@
 //$Id$
 package org.hibernate.search.engine;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+
 import org.apache.lucene.document.Document;
+import org.apache.lucene.search.Hits;
 import org.hibernate.search.engine.EntityInfo;
+import org.hibernate.search.FullTextQuery;
+import org.hibernate.search.ProjectionConstants;
 
 /**
  * @author Emmanuel Bernard
+ * @author John Griffin
  */
 public class DocumentExtractor {
 	private SearchFactoryImplementor searchFactoryImplementor;
@@ -16,14 +24,46 @@
 		this.projection = projection;
 	}
 
-	public EntityInfo extract(Document document) {
+	private EntityInfo extract(Document document) {
 		EntityInfo entityInfo = new EntityInfo();
 		entityInfo.clazz = DocumentBuilder.getDocumentClass( document );
 		entityInfo.id = DocumentBuilder.getDocumentId( searchFactoryImplementor, entityInfo.clazz, document );
-		if (projection != null && projection.length > 0) {
+		if ( projection != null && projection.length > 0 ) {
 			entityInfo.projection = DocumentBuilder.getDocumentFields( searchFactoryImplementor, entityInfo.clazz, document, projection );
 		}
-		//TODO read fields
 		return entityInfo;
 	}
+
+	public EntityInfo extract(Hits hits, int index) throws IOException {
+		Document doc = hits.doc( index );
+		//TODO if we are lonly looking for score (unlikely), avoid accessing doc (lazy load)
+		EntityInfo entityInfo = extract( doc );
+		Object[] eip = entityInfo.projection;
+
+		if ( eip != null && eip.length > 0 ) {
+			for (int x = 0; x < projection.length; x++) {
+				if ( ProjectionConstants.SCORE.equals( projection[x] ) ) {
+					eip[x] = hits.score( index );
+				}
+				else if ( ProjectionConstants.ID.equals( projection[x] ) ) {
+					eip[x] = entityInfo.id;
+				}
+				else if ( ProjectionConstants.DOCUMENT.equals( projection[x] ) ) {
+					eip[x] = doc;
+				}
+				else if ( ProjectionConstants.BOOST.equals( projection[x] ) ) {
+					eip[x] = doc.getBoost();
+				}
+				else if ( ProjectionConstants.THIS.equals( projection[x] ) ) {
+					//THIS could be projected more than once
+					//THIS loading delayed to the Loader phase
+					if (entityInfo.indexesOfThis == null) {
+						entityInfo.indexesOfThis = new ArrayList<Integer>(1);
+					}
+					entityInfo.indexesOfThis.add(x);
+				}
+			}
+		}
+		return entityInfo;
+	}
 }

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -2,14 +2,17 @@
 package org.hibernate.search.engine;
 
 import java.io.Serializable;
+import java.util.List;
+import java.util.ArrayList;
 
 /**
  *
  * @author Emmanuel Bernard
  */
-//Move to egine?
+//TODO Move to egine?
 public class EntityInfo {
 	public Class clazz;
 	public Serializable id;
 	public Object[] projection;
+	public List<Integer> indexesOfThis;
 }

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -5,24 +5,60 @@
 import java.util.List;
 
 import org.hibernate.Session;
-import org.hibernate.search.engine.EntityInfo;
 
 /**
  * @author Emmanuel Bernard
  */
 public class ProjectionLoader implements Loader {
+	private SearchFactoryImplementor searchFactoryImplementor;
+	private Session session;
+	private ObjectLoader objectLoader;
+	private Boolean projectThis;
+
 	public void init(Session session, SearchFactoryImplementor searchFactoryImplementor) {
+		this.session = session;
+		this.searchFactoryImplementor = searchFactoryImplementor;
 	}
 
 	public Object load(EntityInfo entityInfo) {
+		initThisProjectionFlag( entityInfo );
+		if ( projectThis ) {
+			for (int index : entityInfo.indexesOfThis) {
+				entityInfo.projection[index] = objectLoader.load( entityInfo );
+			}
+		}
 		return entityInfo.projection;
 	}
 
+	private void initThisProjectionFlag(EntityInfo entityInfo) {
+		if ( projectThis == null ) {
+			projectThis = entityInfo.indexesOfThis != null;
+			if ( projectThis ) {
+				//TODO use QueryLoader when possible
+				objectLoader = new ObjectLoader();
+				objectLoader.init( session, searchFactoryImplementor );
+			}
+		}
+	}
+
 	public List load(EntityInfo... entityInfos) {
-		List results = new ArrayList(entityInfos.length);
+		List results = new ArrayList( entityInfos.length );
+		if ( entityInfos.length == 0 ) return results;
+
+		initThisProjectionFlag( entityInfos[0] );
+		if ( projectThis ) {
+			objectLoader.load( entityInfos ); //load by batch
+			for (EntityInfo entityInfo : entityInfos) {
+				for (int index : entityInfo.indexesOfThis) {
+					//set one by one to avoid loosing null objects (skipped in the objectLoader.load( EntityInfo[] ))
+					entityInfo.projection[index] = objectLoader.load( entityInfo );
+				}
+			}
+		}
 		for (EntityInfo entityInfo : entityInfos) {
 			results.add( entityInfo.projection );
 		}
+
 		return results;
 	}
 }

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -5,6 +5,7 @@
 
 import org.apache.lucene.search.Sort;
 import org.hibernate.Criteria;
+import org.hibernate.search.ProjectionConstants;
 
 /**
  * The base interface for lucene powered searches.
@@ -14,10 +15,7 @@
  * @author Emmanuel Bernard
  */
 //TODO return FullTextQuery rather than Query in useful chain methods
-public interface FullTextQuery extends Query {
-	//todo Determine what other lucene specific features to expose via this
-	// interface. Maybe we should give access to the underlying lucene hit
-	// objects?
+public interface FullTextQuery extends Query, ProjectionConstants {
 
 	/**
 	 * Allows to let lucene sort the results. This is useful when you have

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -12,7 +12,6 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.lucene.document.Document;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
@@ -35,7 +34,6 @@
 import org.hibernate.impl.CriteriaImpl;
 import org.hibernate.search.FullTextQuery;
 import org.hibernate.search.SearchException;
-import org.hibernate.search.impl.SearchFactoryImpl;
 import org.hibernate.search.engine.DocumentBuilder;
 import org.hibernate.search.engine.DocumentExtractor;
 import org.hibernate.search.engine.EntityInfo;
@@ -97,7 +95,7 @@
 		//find the directories
 		IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
 		if ( searcher == null ) {
-		   	return new IteratorImpl( new ArrayList<EntityInfo>(0), noLoader );
+			return new IteratorImpl( new ArrayList<EntityInfo>( 0 ), noLoader );
 		}
 		try {
 			Hits hits = getHits( searcher );
@@ -107,12 +105,11 @@
 
 			List<EntityInfo> entityInfos = new ArrayList<EntityInfo>( max - first + 1 );
 			DocumentExtractor extractor = new DocumentExtractor( searchFactoryImplementor, indexProjection );
-			for ( int index = first; index <= max; index++ ) {
+			for (int index = first; index <= max; index++) {
 				//TODO use indexSearcher.getIndexReader().document( hits.id(index), FieldSelector(indexProjection) );
-				Document document = hits.doc( index );
-				entityInfos.add( extractor.extract( document ) );
+				entityInfos.add( extractor.extract( hits, index ) );
 			}
-			Loader loader = getLoader(sess, searchFactoryImplementor);
+			Loader loader = getLoader( sess, searchFactoryImplementor );
 			return new IteratorImpl( entityInfos, loader );
 		}
 		catch (IOException e) {
@@ -122,24 +119,24 @@
 			try {
 				searchFactoryImplementor.getReaderProvider().closeReader( searcher.getIndexReader() );
 			}
-			catch( SearchException e ) {
+			catch (SearchException e) {
 				log.warn( "Unable to properly close searcher during lucene query: " + getQueryString(), e );
 			}
 		}
 	}
 
 	private Loader getLoader(Session session, SearchFactoryImplementor searchFactoryImplementor) {
-		if ( indexProjection != null) {
+		if ( indexProjection != null ) {
 			ProjectionLoader loader = new ProjectionLoader();
 			loader.init( session, searchFactoryImplementor );
 			return loader;
 		}
-		if (criteria != null) {
-			if (classes.length > 1) throw new SearchException("Cannot mix criteria and multiple entity types");
+		if ( criteria != null ) {
+			if ( classes.length > 1 ) throw new SearchException( "Cannot mix criteria and multiple entity types" );
 			if ( criteria instanceof CriteriaImpl ) {
-				String targetEntity = ( (CriteriaImpl) criteria).getEntityOrClassName();
+				String targetEntity = ( (CriteriaImpl) criteria ).getEntityOrClassName();
 				if ( classes.length == 1 && !classes[0].getName().equals( targetEntity ) ) {
-					throw new SearchException("Criteria query entity should match query entity");
+					throw new SearchException( "Criteria query entity should match query entity" );
 				}
 				else {
 					try {
@@ -147,17 +144,17 @@
 						classes = new Class[] { entityType };
 					}
 					catch (ClassNotFoundException e) {
-						throw new SearchException("Unable to load entity class from criteria: " + targetEntity, e);
+						throw new SearchException( "Unable to load entity class from criteria: " + targetEntity, e );
 					}
 				}
 			}
 			QueryLoader loader = new QueryLoader();
 			loader.init( session, searchFactoryImplementor );
 			loader.setEntityType( classes[0] );
-			loader.setCriteria(criteria);
+			loader.setCriteria( criteria );
 			return loader;
 		}
-		else if (classes.length == 1) {
+		else if ( classes.length == 1 ) {
 			QueryLoader loader = new QueryLoader();
 			loader.init( session, searchFactoryImplementor );
 			loader.setEntityType( classes[0] );
@@ -184,14 +181,14 @@
 			int max = max( first, hits );
 			DocumentExtractor extractor = new DocumentExtractor( searchFactory, indexProjection );
 			Loader loader = getLoader( (Session) this.session, searchFactory );
-			return new ScrollableResultsImpl( searcher, hits, first, max, extractor, loader, searchFactory);
+			return new ScrollableResultsImpl( searcher, hits, first, max, extractor, loader, searchFactory );
 		}
 		catch (IOException e) {
 			//close only in case of exception
 			try {
 				searchFactory.getReaderProvider().closeReader( searcher.getIndexReader() );
 			}
-			catch( SearchException ee ) {
+			catch (SearchException ee) {
 				//we have the initial issue already
 			}
 			throw new HibernateException( "Unable to query Lucene index", e );
@@ -207,7 +204,7 @@
 		SearchFactoryImplementor searchFactoryImplementor = ContextHelper.getSearchFactoryBySFI( session );
 		//find the directories
 		IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
-		if (searcher == null) return new ArrayList(0);
+		if ( searcher == null ) return new ArrayList( 0 );
 		Hits hits;
 		try {
 			hits = getHits( searcher );
@@ -216,12 +213,11 @@
 			Session sess = (Session) this.session;
 			List<EntityInfo> infos = new ArrayList<EntityInfo>( max - first + 1 );
 			DocumentExtractor extractor = new DocumentExtractor( searchFactoryImplementor, indexProjection );
-			for ( int index = first; index <= max; index++ ) {
-				Document document = hits.doc( index );
-				infos.add( extractor.extract( document ) );
+			for (int index = first; index <= max; index++) {
+				infos.add( extractor.extract( hits, index ) );
 			}
 			Loader loader = getLoader( sess, searchFactoryImplementor );
-			return loader.load( infos.toArray( new EntityInfo[infos.size()]) );
+			return loader.load( infos.toArray( new EntityInfo[infos.size()] ) );
 		}
 		catch (IOException e) {
 			throw new HibernateException( "Unable to query Lucene index", e );
@@ -246,7 +242,7 @@
 	private Hits getHits(Searcher searcher) throws IOException {
 		Hits hits;
 		org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
-		if(sort == null){
+		if ( sort == null ) {
 			hits = searcher.search( query );
 		}
 		else {
@@ -266,7 +262,7 @@
 			BooleanQuery classFilter = new BooleanQuery();
 			//annihilate the scoring impact of DocumentBuilder.CLASS_FIELDNAME
 			classFilter.setBoost( 0 );
-			for ( Class clazz : classesAndSubclasses ) {
+			for (Class clazz : classesAndSubclasses) {
 				Term t = new Term( DocumentBuilder.CLASS_FIELDNAME, clazz.getName() );
 				TermQuery termQuery = new TermQuery( t );
 				classFilter.add( termQuery, BooleanClause.Occur.SHOULD );
@@ -302,9 +298,9 @@
 		List<DirectoryProvider> directories = new ArrayList<DirectoryProvider>();
 		if ( classes == null || classes.length == 0 ) {
 			//no class means all classes
-			for ( DocumentBuilder builder : builders.values() ) {
+			for (DocumentBuilder builder : builders.values()) {
 				final DirectoryProvider directoryProvider = builder.getDirectoryProvider();
-				if ( ! directories.contains( directoryProvider ) ) {
+				if ( !directories.contains( directoryProvider ) ) {
 					directories.add( directoryProvider );
 				}
 			}
@@ -313,16 +309,17 @@
 		else {
 			Set<Class> involvedClasses = new HashSet<Class>( classes.length );
 			Collections.addAll( involvedClasses, classes );
-			for ( Class clazz : classes ) {
+			for (Class clazz : classes) {
 				DocumentBuilder builder = builders.get( clazz );
 				if ( builder != null ) involvedClasses.addAll( builder.getMappedSubclasses() );
 			}
-			for ( Class clazz : involvedClasses ) {
+			for (Class clazz : involvedClasses) {
 				DocumentBuilder builder = builders.get( clazz );
 				//TODO should we rather choose a polymorphic path and allow non mapped entities
-				if ( builder == null ) throw new HibernateException( "Not a mapped entity (don't forget to add @Indexed): " + clazz );
+				if ( builder == null )
+					throw new HibernateException( "Not a mapped entity (don't forget to add @Indexed): " + clazz );
 				final DirectoryProvider directoryProvider = builder.getDirectoryProvider();
-				if ( ! directories.contains( directoryProvider ) ) {
+				if ( !directories.contains( directoryProvider ) ) {
 					directories.add( directoryProvider );
 				}
 			}
@@ -340,11 +337,11 @@
 
 
 	public int getResultSize() {
-		if (resultSize == null) {
+		if ( resultSize == null ) {
 			//get result size without object initialization
 			SearchFactoryImplementor searchFactoryImplementor = ContextHelper.getSearchFactoryBySFI( session );
 			IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
-			if (searcher == null) {
+			if ( searcher == null ) {
 				resultSize = 0;
 			}
 			else {
@@ -361,7 +358,7 @@
 					try {
 						searchFactoryImplementor.getReaderProvider().closeReader( searcher.getIndexReader() );
 					}
-					catch( SearchException e ) {
+					catch (SearchException e) {
 						log.warn( "Unable to properly close searcher during lucene query: " + getQueryString(), e );
 					}
 				}
@@ -376,7 +373,7 @@
 	}
 
 	public FullTextQuery setIndexProjection(String... fields) {
-		if ( fields == null || fields.length == 0) {
+		if ( fields == null || fields.length == 0 ) {
 			this.indexProjection = null;
 		}
 		else {

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -112,18 +112,23 @@
 	}
 
 	public Object[] get() throws HibernateException {
-		if ( current < first || current > max ) return null; //or exception?
+		// don't throw an exception here just
+		// return 'null' this is similar to the
+		// RowSet spec in JDBC. It returns false
+		// (or 0 I can't remember) but we can't
+		// do that since we have to make up for
+		// an Object[]. J.G
+		if ( current < first || current > max ) return null;
 		EntityInfo info = entityInfos[current - first];
 		if ( info == null ) {
 			Document document = null;
 			try {
-				document = hits.doc( current );
+				info = documentExtractor.extract( hits, current );
 			}
 			catch (IOException e) {
 				throw new HibernateException( "Unable to read Lucene hits[" + current + "]", e );
 			}
 			//FIXME should check that clazz match classes but this complexify a lot the firstResult/maxResult
-			info = documentExtractor.extract( document );
 			entityInfos[current - first] = info;
 		}
 		if ( !resultContext.containsKey( info ) ) {

Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -0,0 +1,59 @@
+//$Id$
+package org.hibernate.search.test.query;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Store;
+
+/**
+ * @author John Grffin
+ */
+ at Entity
+ at Indexed
+public class Employee {
+	private Integer id;
+	private String lastname;
+	private String dept;
+
+	public Employee() {
+	}
+
+	public Employee(Integer id, String lastname, String dept) {
+		this.id = id;
+		this.lastname = lastname;
+		this.dept = dept;
+	}
+
+	@Id
+	@DocumentId
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer id) {
+		this.id = id;
+	}
+
+	@Field( index = Index.NO, store = Store.YES )
+	public String getLastname() {
+		return lastname;
+	}
+
+	public void setLastname(String lastname) {
+		this.lastname = lastname;
+	}
+
+	@Field( index = Index.TOKENIZED, store = Store.YES )
+	public String getDept() {
+		return dept;
+	}
+
+	public void setDept(String dept) {
+		this.dept = dept;
+	}
+}

Modified: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java	2007-07-20 23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java	2007-07-21 00:16:16 UTC (rev 12792)
@@ -2,21 +2,224 @@
 package org.hibernate.search.test.query;
 
 import java.util.List;
+import java.util.Iterator;
+import java.io.Serializable;
 
 import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.queryParser.QueryParser;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.document.Document;
 import org.hibernate.Transaction;
+import org.hibernate.ScrollableResults;
+import org.hibernate.Session;
 import org.hibernate.search.FullTextSession;
 import org.hibernate.search.Search;
 import org.hibernate.search.SearchException;
+import org.hibernate.search.FullTextQuery;
 import org.hibernate.search.test.SearchTestCase;
 
 /**
  * @author Emmanuel Bernard
+ * @author John Griffin
  */
 public class ProjectionQueryTest extends SearchTestCase {
 
+	public void testLuceneObjectsProjectionWithScroll() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		prepEmployeeIndex( s );
+
+		Transaction tx;
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser( "dept", new StandardAnalyzer() );
+
+		Query query = parser.parse( "dept:ITech" );
+		org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query, Employee.class );
+		// Is the 'FullTextQuery.ID' value correct here? Do we want the Lucene internal document number?
+		hibQuery.setIndexProjection( "id", "lastname", "dept", FullTextQuery.THIS, FullTextQuery.SCORE, FullTextQuery.BOOST, FullTextQuery.DOCUMENT, FullTextQuery.ID );
+
+		ScrollableResults projections = hibQuery.scroll();
+
+		// There are a lot of methods to check in ScrollableResultsImpl
+		// so, we'll use methods to check each projection as needed.
+
+		projections.beforeFirst();
+		projections.next();
+		Object[] projection = projections.get();
+		checkProjectionFirst( projection, s );
+		assertTrue(projections.isFirst());
+
+		projections.last();
+		projection = projections.get();
+		checkProjectionLast( projection, s );
+		assertTrue(projections.isLast());
+
+		projections.next();
+		projection = projections.get();
+		assertNull(projection);
+
+		projections.previous();
+		projection = projections.get();
+		checkProjectionLast( projection, s );
+
+		projections.first();
+		projection = projections.get();
+		checkProjectionFirst( projection, s );
+
+		projections.scroll( 2 );
+		projection = projections.get();
+		checkProjection2( projection, s );
+
+		projections.scroll( -5 );
+		projection = projections.get();
+		assertNull(projection);
+
+		//cleanup
+		for (Object element : s.createQuery( "from " + Employee.class.getName() ).list()) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	private void checkProjectionFirst(Object[] projection, Session s) {
+		assertEquals( "id incorrect", 1000, projection[0] );
+		assertEquals( "lastname incorrect", "Griffin", projection[1] );
+		assertEquals( "dept incorrect", "ITech", projection[2] );
+		assertEquals( "THIS incorrect", projection[3], s.get(Employee.class, (Serializable) projection[0]) );
+		assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+		assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+		assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+		assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6] ).getFields().size() );
+		assertEquals( "legacy ID incorrect", 1000, projection[7] );
+	}
+
+	private void checkProjectionLast(Object[] projection, Session s) {
+		assertEquals( "id incorrect", 1004, projection[0] );
+		assertEquals( "lastname incorrect", "Whetbrook", projection[1] );
+		assertEquals( "dept incorrect", "ITech", projection[2] );
+		assertEquals( "THIS incorrect", projection[3], s.get(Employee.class, (Serializable) projection[0]) );
+		assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+		assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+		assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+		assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6] ).getFields().size() );
+		assertEquals( "legacy ID incorrect", 1004, projection[7] );
+	}
+
+	private void checkProjection2(Object[] projection, Session s) {
+		assertEquals( "id incorrect", 1003, projection[0] );
+		assertEquals( "lastname incorrect", "Stejskal", projection[1] );
+		assertEquals( "dept incorrect", "ITech", projection[2] );
+		assertEquals( "THIS incorrect", projection[3], s.get(Employee.class, (Serializable) projection[0]) );
+		assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+		assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+		assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+		assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6] ).getFields().size() );
+		assertEquals( "legacy ID incorrect", 1003, projection[7] );
+	}
+
+	public void testLuceneObjectsProjectionWithIterate() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		prepEmployeeIndex( s );
+
+		Transaction tx;
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser( "dept", new StandardAnalyzer() );
+
+		Query query = parser.parse( "dept:ITech" );
+		org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query, Employee.class );
+		hibQuery.setIndexProjection( "id", "lastname", "dept", FullTextQuery.THIS, FullTextQuery.SCORE, FullTextQuery.BOOST, FullTextQuery.DOCUMENT, FullTextQuery.ID );
+
+		int counter = 0;
+
+		for (Iterator iter = hibQuery.iterate(); iter.hasNext();) {
+			Object[] projection = (Object[]) iter.next();
+			assertNotNull( projection );
+			counter++;
+			assertEquals( "dept incorrect", "ITech", projection[2] );
+			assertEquals( "THIS incorrect", projection[3], s.get(Employee.class, (Serializable) projection[0]) );
+			assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+			assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+			assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+			assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6] ).getFields().size() );
+		}
+		assertEquals( "incorrect number of results returned", 4, counter );
+
+		//cleanup
+		for (Object element : s.createQuery( "from " + Employee.class.getName() ).list()) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	public void testLuceneObjectsProjectionWithList() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		prepEmployeeIndex( s );
+
+		Transaction tx;
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser( "dept", new StandardAnalyzer() );
+
+		Query query = parser.parse( "dept:Accounting" );
+		org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query, Employee.class );
+		hibQuery.setIndexProjection( "id", "lastname", "dept", FullTextQuery.THIS, FullTextQuery.SCORE, FullTextQuery.BOOST, FullTextQuery.DOCUMENT, FullTextQuery.ID );
+
+		List result = hibQuery.list();
+		assertNotNull( result );
+
+		Object[] projection = (Object[]) result.get( 0 );
+		assertNotNull( projection );
+		assertEquals( "id incorrect", 1001, projection[0] );
+		assertEquals( "last name incorrect", "Jackson", projection[1] );
+		assertEquals( "dept incorrect", "Accounting", projection[2] );
+		assertEquals( "THIS incorrect", projection[3], s.get(Employee.class, (Serializable) projection[0]) );
+		assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+		assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+		assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+		assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6] ).getFields().size() );
+		assertEquals( "ID incorrect", 1001, projection[7] );
+
+		// Change the projection order and null one
+		hibQuery.setIndexProjection( FullTextQuery.DOCUMENT, FullTextQuery.THIS, FullTextQuery.SCORE, null, FullTextQuery.ID, "id", "lastname", "dept" );
+
+		result = hibQuery.list();
+		assertNotNull( result );
+
+		projection = (Object[]) result.get( 0 );
+		assertNotNull( projection );
+
+		assertTrue( "DOCUMENT incorrect", projection[0] instanceof Document );
+		assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[0] ).getFields().size() );
+		assertEquals( "THIS incorrect", projection[1], s.get(Employee.class, (Serializable) projection[4]) );
+		assertEquals( "SCORE incorrect", 1.0F, projection[2] );
+		assertNull( "BOOST not removed", projection[3] );
+		assertEquals( "ID incorrect", 1001, projection[4] );
+		assertEquals( "id incorrect", 1001, projection[5] );
+		assertEquals( "last name incorrect", "Jackson", projection[6] );
+		assertEquals( "dept incorrect", "Accounting", projection[7] );
+
+		//cleanup
+		for (Object element : s.createQuery( "from " + Employee.class.getName() ).list()) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+	private void prepEmployeeIndex(FullTextSession s) {
+		Transaction tx = s.beginTransaction();
+		Employee e1 = new Employee( 1000, "Griffin", "ITech" );
+		s.save( e1 );
+		Employee e2 = new Employee( 1001, "Jackson", "Accounting" );
+		s.save( e2 );
+		Employee e3 = new Employee( 1002, "Jimenez", "ITech" );
+		s.save( e3 );
+		Employee e4 = new Employee( 1003, "Stejskal", "ITech" );
+		s.save( e4 );
+		Employee e5 = new Employee( 1004, "Whetbrook", "ITech" );
+		s.save( e5 );
+
+		tx.commit();
+	}
+
 	public void testProjection() throws Exception {
 		FullTextSession s = Search.createFullTextSession( openSession() );
 		Transaction tx = s.beginTransaction();
@@ -27,7 +230,7 @@
 		Author emmanuel = new Author();
 		emmanuel.setName( "Emmanuel" );
 		s.save( emmanuel );
-		book.setMainAuthor(emmanuel);
+		book.setMainAuthor( emmanuel );
 		tx.commit();
 		s.clear();
 		tx = s.beginTransaction();
@@ -35,8 +238,8 @@
 
 		Query query = parser.parse( "summary:Festina" );
 		org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query, Book.class );
-		hibQuery.setIndexProjection( "id", "summary", "mainAuthor.name");
-		
+		hibQuery.setIndexProjection( "id", "summary", "mainAuthor.name" );
+
 		List result = hibQuery.list();
 		assertNotNull( result );
 		assertEquals( "Query with no explicit criteria", 1, result.size() );
@@ -46,11 +249,11 @@
 		assertEquals( "mainAuthor.name (embedded objects)", "Emmanuel", projection[2] );
 
 		hibQuery = s.createFullTextQuery( query, Book.class );
-		hibQuery.setIndexProjection( "id", "body", "mainAuthor.name");
+		hibQuery.setIndexProjection( "id", "body", "mainAuthor.name" );
 
 		try {
 			result = hibQuery.list();
-			fail("Projecting an unstored field should raise an exception");
+			fail( "Projecting an unstored field should raise an exception" );
 		}
 		catch (SearchException e) {
 			//success
@@ -62,18 +265,18 @@
 		result = hibQuery.list();
 		assertNotNull( result );
 		assertEquals( 1, result.size() );
-		assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+		assertTrue( "Should not trigger projection", result.get( 0 ) instanceof Book );
 
 		hibQuery = s.createFullTextQuery( query, Book.class );
-		hibQuery.setIndexProjection(null);
+		hibQuery.setIndexProjection( null );
 		result = hibQuery.list();
 		assertNotNull( result );
 		assertEquals( 1, result.size() );
-		assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+		assertTrue( "Should not trigger projection", result.get( 0 ) instanceof Book );
 
 		query = parser.parse( "summary:fleurs" );
 		hibQuery = s.createFullTextQuery( query, Book.class );
-		hibQuery.setIndexProjection( "id", "summary", "mainAuthor.name");
+		hibQuery.setIndexProjection( "id", "summary", "mainAuthor.name" );
 		result = hibQuery.list();
 		assertEquals( 1, result.size() );
 		projection = (Object[]) result.get( 0 );
@@ -90,7 +293,8 @@
 	protected Class[] getMappings() {
 		return new Class[] {
 				Book.class,
-				Author.class
+				Author.class,
+				Employee.class
 		};
 	}
 }




More information about the hibernate-commits mailing list