Author: sannegrinovero
Date: 2009-04-26 17:49:41 -0400 (Sun, 26 Apr 2009)
New Revision: 16439
Added:
search/trunk/src/test/java/org/hibernate/search/test/worker/duplication/WorkSequencesTest.java
Modified:
search/trunk/src/main/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java
search/trunk/src/test/java/org/hibernate/search/test/session/Domain.java
Log:
HSEARCH-353 bugfix (removing an entity and adding another with same PK (in same TX) will
not add second entity to index)
Modified:
search/trunk/src/main/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java
===================================================================
---
search/trunk/src/main/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java 2009-04-26
03:21:45 UTC (rev 16438)
+++
search/trunk/src/main/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java 2009-04-26
21:49:41 UTC (rev 16439)
@@ -259,16 +259,22 @@
//TODO could we use T instead of EntityClass?
public void addWorkToQueue(Class<T> entityClass, T entity, Serializable id,
WorkType workType, List<LuceneWork> queue, SearchFactoryImplementor
searchFactoryImplementor) {
//TODO with the caller loop we are in a n^2: optimize it using a HashMap for work
recognition
-
+
+ boolean sameIdWasSetToBeDeleted = false;
List<LuceneWork> toDelete = new ArrayList<LuceneWork>();
boolean duplicateDelete = false;
for ( LuceneWork luceneWork : queue ) {
- //avoid unecessary duplicated work
- if ( luceneWork.getEntityClass() == entityClass
- ) {
+ if ( luceneWork.getEntityClass() == entityClass ) {
Serializable currentId = luceneWork.getId();
- //currentId != null => either ADD or Delete work
if ( currentId != null && currentId.equals( id ) ) { //find a way to use
Type.equals(x,y)
+ if ( luceneWork instanceof DeleteLuceneWork ) {
+ //flag this work as related to a to-be-deleted entity
+ sameIdWasSetToBeDeleted = true;
+ }
+ else if ( luceneWork instanceof AddLuceneWork ) {
+ //if a later work in the queue is adding it back, undo deletion flag:
+ sameIdWasSetToBeDeleted = false;
+ }
if ( workType == WorkType.DELETE ) { //TODO add PURGE?
//DELETE should have precedence over any update before (HSEARCH-257)
//if an Add work is here, remove it
@@ -280,14 +286,23 @@
duplicateDelete = true;
}
}
- else {
- //we can safely say we are out, the other work is an ADD
- return;
+ if ( workType == WorkType.ADD ) {
+ if ( luceneWork instanceof AddLuceneWork ) {
+ //embedded objects may issue an "UPDATE" right before the
"ADD",
+ //leading to double insertions in the index
+ toDelete.add( luceneWork );
+ }
}
+ //TODO do something to avoid multiple PURGE ALL and OPTIMIZE
}
- //TODO do something to avoid multiple PURGE ALL and OPTIMIZE
}
}
+
+ if ( sameIdWasSetToBeDeleted && workType == WorkType.COLLECTION ) {
+ //avoid updating (and thus adding) objects which are going to be deleted
+ return;
+ }
+
for ( LuceneWork luceneWork : toDelete ) {
queue.remove( luceneWork );
}
Modified: search/trunk/src/test/java/org/hibernate/search/test/session/Domain.java
===================================================================
--- search/trunk/src/test/java/org/hibernate/search/test/session/Domain.java 2009-04-26
03:21:45 UTC (rev 16438)
+++ search/trunk/src/test/java/org/hibernate/search/test/session/Domain.java 2009-04-26
21:49:41 UTC (rev 16439)
@@ -19,6 +19,13 @@
private Integer id;
@Field
private String name;
+
+ public Domain(){ }
+
+ public Domain(Integer id, String name) {
+ this.id = id;
+ this.name = name;
+ }
public Integer getId() {
return id;
Added:
search/trunk/src/test/java/org/hibernate/search/test/worker/duplication/WorkSequencesTest.java
===================================================================
---
search/trunk/src/test/java/org/hibernate/search/test/worker/duplication/WorkSequencesTest.java
(rev 0)
+++
search/trunk/src/test/java/org/hibernate/search/test/worker/duplication/WorkSequencesTest.java 2009-04-26
21:49:41 UTC (rev 16439)
@@ -0,0 +1,124 @@
+package org.hibernate.search.test.worker.duplication;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.hibernate.Session;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.SearchFactory;
+import org.hibernate.search.reader.ReaderProvider;
+import org.hibernate.search.store.DirectoryProvider;
+import org.hibernate.search.test.SearchTestCase;
+import org.hibernate.search.test.session.Domain;
+
+/**
+ * Testcase for HSEARCH-353
+ * Verify that different kinds of work (add/delete) found in the same
+ * queue are all executed; having special care about different entities
+ * being deleted/persisted but sharing the same PK (to replace the old
+ * instance with another one).
+ *
+ * @author Sanne Grinovero
+ */
+public class WorkSequencesTest extends SearchTestCase {
+
+ private SearchFactory searchFactory;
+
+ public void testComplexTransactionSequence() throws IOException {
+ Session classicSession = openSession( );
+ FullTextSession session = Search.getFullTextSession( classicSession );
+ searchFactory = session.getSearchFactory();
+
+ // create some different domains:
+ {
+ session.beginTransaction();
+ session.persist( new Domain( 1, "jboss.org" ) );
+ session.persist( new Domain( 2, "jboss.com" ) );
+ session.persist( new Domain( 3, "hibernate.org" ) );
+ session.persist( new Domain( 4, "geocities.com" ) );
+ session.getTransaction().commit();
+ }
+ assertEquals( 2, countDomainsByFullText( "jboss" ) );
+ assertEquals( 1, countDomainsByFullText( "hibernate" ) );
+ assertEquals( 1, countDomainsByFullText( "geocities" ) );
+
+ // now create some and delete others:
+ {
+ session.beginTransaction();
+ session.persist( new Domain( 5, "sun.com" ) );
+ session.persist( new Domain( 6, "mysql.com" ) );
+ session.persist( new Domain( 7, "oracle.com" ) );
+ Domain hibernateDomain = (Domain) session.get( Domain.class, 3 );
+ session.delete( hibernateDomain );
+ Domain geocitiesDomain = (Domain) session.get( Domain.class, 4 );
+ session.delete( geocitiesDomain );
+ session.getTransaction().commit();
+ }
+ assertEquals( 0, countDomainsByFullText( "hibernate" ) );
+ assertEquals( 0, countDomainsByFullText( "geocities" ) );
+ assertEquals( 2, countDomainsByFullText( "jboss" ) );
+ assertEquals( 1, countDomainsByFullText( "sun" ) );
+ assertEquals( 1, countDomainsByFullText( "mysql" ) );
+ assertEquals( 1, countDomainsByFullText( "oracle" ) );
+
+ // use create/update/delete:
+ {
+ session.beginTransaction();
+ session.persist( new Domain( 3, "hibernate.org" ) );
+ Domain mysqlDomain = (Domain) session.get( Domain.class, 6 );
+ session.delete( mysqlDomain );
+ //persisting a new entity having the same PK as a deleted one:
+ session.persist( new Domain( 6, "myhql.org" ) );
+ Domain sunDomain = (Domain) session.get( Domain.class, 5 );
+ sunDomain.setName( "community.oracle.com" );
+ session.getTransaction().commit();
+ }
+ assertEquals( 1, countDomainsByFullText( "hibernate" ) );
+ assertEquals( 2, countDomainsByFullText( "oracle" ) );
+ assertEquals( 1, countDomainsByFullText( "myhql" ) );
+ assertEquals( 1, countDomainsByFullText( "community" ) );
+ assertEquals( 0, countDomainsByFullText( "mysql" ) );
+
+ // now creating and deleting the "same" (as by pk) entity several times in
same transaction:
+ {
+ session.beginTransaction();
+ session.persist( new Domain( 8, "mysql.org" ) );
+ Domain mysqlDomain = (Domain) session.load( Domain.class, 8 );
+ session.delete( mysqlDomain );
+ Domain newDomain = new Domain( 8, "something.org" );
+ session.persist( newDomain );
+ session.delete( newDomain );
+ session.persist( new Domain( 8, "somethingnew.org" ) );
+ session.getTransaction().commit();
+ }
+ assertEquals( 1, countDomainsByFullText( "somethingnew" ) );
+
+ session.close();
+ }
+
+ //helper method to verify how many instances are found in the index by doing a simple FT
query
+ private int countDomainsByFullText(String name) throws IOException {
+ Query luceneQuery = new TermQuery( new Term( "name", name ) );
+ DirectoryProvider<?> directoryProvider = searchFactory.getDirectoryProviders(
Domain.class )[0];
+ ReaderProvider readerProvider = searchFactory.getReaderProvider();
+ IndexReader reader = readerProvider.openReader( directoryProvider );
+ IndexSearcher searcher = new IndexSearcher( reader );
+ TopDocs topDocs = searcher.search( luceneQuery, null, 100 );
+ readerProvider.closeReader( reader );
+ return topDocs.totalHits;
+ }
+
+ @Override
+ protected Class<?>[] getMappings() {
+ return new Class[] {
+ Domain.class
+ };
+ }
+
+}
\ No newline at end of file