diff --git a/Jenkinsfile b/Jenkinsfile index 0f1bbdc5770..ace6dcd9a0c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -532,7 +532,6 @@ stage('Non-default environments') { String mavenBuildAdditionalArgs = ''' \ -pl !documentation \ -pl !integrationtest/mapper/orm-spring \ - -pl !integrationtest/mapper/orm-jakarta-batch \ -pl !integrationtest/v5migrationhelper/orm \ -pl !integrationtest/java/modules/orm-lucene \ -pl !integrationtest/java/modules/orm-elasticsearch \ diff --git a/documentation/src/main/asciidoc/migration/index.adoc b/documentation/src/main/asciidoc/migration/index.adoc index bcf955fad71..0577f030dcc 100644 --- a/documentation/src/main/asciidoc/migration/index.adoc +++ b/documentation/src/main/asciidoc/migration/index.adoc @@ -288,26 +288,48 @@ alter table hsearch_agent The configuration properties are backward-compatible with Hibernate Search {hibernateSearchPreviousStableVersionShort}. -`hibernate.search.coordination.entity.mapping.outboxevent.uuid_type` and `hibernate.search.coordination.entity.mapping.agent.uuid_type` +However, some configuration values are deprecated: + +* `hibernate.search.coordination.entity.mapping.outboxevent.uuid_type` and `hibernate.search.coordination.entity.mapping.agent.uuid_type` now accept names of SQL type codes from `org.hibernate.type.SqlTypes` or their corresponding int values. The value `default` is still valid. `uuid-binary` and `uuid-char` are accepted and converted to their corresponding `org.hibernate.type.SqlTypes` alternatives, but they are deprecated and will not be accepted in the future versions of Hibernate Search. [[api]] == API changes -The complement operator (`~`) used for link:{hibernateSearchDocUrl}#search-dsl-predicate-regexp-flags[matching regular expression patterns with flags] +The https://hibernate.org/community/compatibility-policy/#code-categorization[API] +is for the most part backward-compatible with Hibernate Search {hibernateSearchPreviousStableVersionShort}. + +However, some APIs changed: + +* The complement operator (`~`) used for link:{hibernateSearchDocUrl}#search-dsl-predicate-regexp-flags[matching regular expression patterns with flags] is now removed with no alternative to replace it. +* The Hibernate Search job for Jakarta Batch no longer accepts a `customQueryHQL` / `.restrictedBy(String)` parameter. +Use `.reindexOnly(String hql, Map parameters)` instead. +* The Hibernate Search job for Jakarta Batch no longer accepts a `sessionClearInterval` / `.sessionClearInterval(int)` parameter. +Use `entityFetchSize`/`.entityFetchSize(int)` instead. [[spi]] == SPI changes The https://hibernate.org/community/compatibility-policy/#code-categorization[SPI] -are backward-compatible with Hibernate Search {hibernateSearchPreviousStableVersionShort}. +are for the most part backward-compatible with Hibernate Search {hibernateSearchPreviousStableVersionShort}. [[behavior]] == Behavior changes -The default value for `hibernate.search.backend.query.shard_failure.ignore` is changed from `true` to `false` which means +The behavior of Hibernate Search {hibernateSearchVersion} +is for the most part backward-compatible with Hibernate Search {hibernateSearchPreviousStableVersionShort}. + +However, parts of Hibernate Search now behave differently: + +* The default value for `hibernate.search.backend.query.shard_failure.ignore` is changed from `true` to `false` which means that now Hibernate Search will throw an exception if at least one shard failed during a search operation. To get the previous behavior set this configuration property explicitly to `true`. -Note, this setting must be set for each elasticsearch backend, if multiple are defined. \ No newline at end of file +Note, this setting must be set for each elasticsearch backend, if multiple are defined. +* The Hibernate Search job for Jakarta Batch will now list identifiers in one session (with one DB connection), +while loading entities in another (with another DB connection). +This is to sidestep limitations of scrolling in some JDBC drivers. +* For entities whose document ID is based on a different property than the entity ID, +the Hibernate Search job for Jakarta Batch will now build the partition plan using that property +instead of using the entity ID indiscriminately. diff --git a/documentation/src/main/asciidoc/public/reference/_indexing-massindexer.adoc b/documentation/src/main/asciidoc/public/reference/_indexing-massindexer.adoc index d92a3abb5c6..ae08da239bb 100644 --- a/documentation/src/main/asciidoc/public/reference/_indexing-massindexer.adoc +++ b/documentation/src/main/asciidoc/public/reference/_indexing-massindexer.adoc @@ -197,9 +197,9 @@ include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/indexing/Hibe ---- <1> <>. <2> Create a `MassIndexer` targeting every indexed entity type. -<3> Reindex only the books published before year 2100. +<3> Reindex only the books published before year 1950. <4> Reindex only the authors born prior to a given local date. -<5> In this example the date is passed as a query parameter. +<5> In this example the cutoff date is passed as a query parameter. <6> Start the mass indexing process and return when it is over. ==== diff --git a/documentation/src/main/asciidoc/public/reference/_mapper-orm-indexing-jakarta-batch.adoc b/documentation/src/main/asciidoc/public/reference/_mapper-orm-indexing-jakarta-batch.adoc index acb19275a06..57893af8798 100644 --- a/documentation/src/main/asciidoc/public/reference/_mapper-orm-indexing-jakarta-batch.adoc +++ b/documentation/src/main/asciidoc/public/reference/_mapper-orm-indexing-jakarta-batch.adoc @@ -100,10 +100,9 @@ accept special values, for example MySQL might benefit from using `Integer#MIN_V will attempt to preload everything in memory. |`entityFetchSize` / `.entityFetchSize(int)` -|The value of `sessionClearInterval` -|Specifies the fetch size to be used when loading entities from database. Some databases -accept special values, for example MySQL might benefit from using `Integer#MIN_VALUE`, otherwise it -will attempt to preload everything in memory. +|200, or the value of `checkpointInterval` if it is smaller +|Specifies the fetch size to be used when loading entities from database. The value defined must be greater +than 0, and equal to or less than the value of `checkpointInterval`. |`customQueryHQL` / `.restrictedBy(String)` |- @@ -132,11 +131,6 @@ request maximum. |The number of entities to process before triggering a checkpoint. The value defined must be greater than 0, and equal to or less than the value of `rowsPerPartition`. -|`sessionClearInterval` / `.sessionClearInterval(int)` -|200, or the value of `checkpointInterval` if it is smaller -|The number of entities to process before clearing the session. The value defined must be greater -than 0, and equal to or less than the value of `checkpointInterval`. - |`entityManagerFactoryReference` / `.entityManagerFactoryReference(String)` |- |**This parameter is required** when there is more than one persistence unit. @@ -149,58 +143,37 @@ The string that will identify the `EntityManagerFactory`. |See <> |=== -[[mapper-orm-indexing-jakarta-batch-indexing-mode]] -== [[jsr-352-indexing-mode]] Indexing mode +[[mapper-orm-indexing-jakarta-batch-conditional]] +== [[mapper-orm-indexing-jakarta-batch-indexing-mode]] [[jsr-352-indexing-mode]] Conditional indexing + +You can select a subset of target entities to be indexed +by passing a condition as string to the mass indexing job. +The condition will be applied when querying the database for entities to index. -The mass indexing job allows you to define your own entities to be indexed -- you can start a full -indexing or a partial indexing through 2 different methods: selecting the desired entity types, -or using HQL. +The condition string is expected to follow the link:{hibernateDocUrl}#query-language[Hibernate Query Language (HQL)] syntax. +Accessible entity properties are those of the entity being reindexed (and nothing more). -.Conditional reindexing using a `restrictedBy` HQL parameter +.Conditional indexing using a `reindexOnly` HQL parameter ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmJakartaBatchIT.java[tags=hql] +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmJakartaBatchIT.java[tags=reindexOnly] ---- <1> Start building parameters for a mass-indexing job. <2> Define the entity type to be indexed. -<3> Restrict the scope of the job using an HQL restriction. -<4> Get `JobOperator` form the framework. -<5> Start the job. +<3> Reindex only the authors born prior to a given local date. +<4> In this example the cutoff date is passed as a query parameter. +<5> Get `JobOperator` from the framework. +<6> Start the job. ==== -While the full indexing is useful when you perform the very first indexing, or -after extensive changes to your whole database, it may also be time-consuming. -If your want to reindex only part of your data, you need to add restrictions using HQL: -they help you to define a customized selection, and only the entities inside that selection will be indexed. A typical -use-case is to index the new entities appeared since yesterday. - -Note that, as detailed below, some features may not be supported depending on the indexing mode. - -.Comparison of each indexing mode -|=== -| Indexing mode | Scope | Parallel Indexing - -| Full Indexation -| All entities -| Supported - -| HQL -| Some entities -| Not supported -|=== - [WARNING] ==== -When using the HQL mode, there isn't any query validation before the job's start. -If the query is invalid, the job will start and fail. - -Also, parallel indexing is disabled in HQL mode, -because our current parallelism implementations relies on selection order, -which might not be provided by the HQL given by user. +Even if the reindexing is applied on a subset of entities, by default *all entities* will be purged at the start. +The purge <>, +but when enabled there is no way to filter the entities that will be purged. -Because of those limitations, we suggest you use this approach only for indexing small numbers of entities, -and only if you know that no entities matching the query will be created during indexing. +See https://hibernate.atlassian.net/browse/HSEARCH-3304[HSEARCH-3304] for more information. ==== [[mapper-orm-indexing-jakarta-batch-parallel-indexing]] @@ -315,7 +288,7 @@ But the size of a chunk is not only about saving progress, it is also about perf * a new Hibernate session is opened for each chunk; * a new transaction is started for each chunk; * inside a chunk, the session is cleared periodically - according to the `sessionClearInterval` parameter, + according to the `entityFetchSize` parameter, which must thereby be smaller than (or equal to) the chunk size; * documents are flushed to the index at the end of each chunk. diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/AbstractHibernateOrmMassIndexingIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/AbstractHibernateOrmMassIndexingIT.java index b3762e86c60..45657519f2d 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/AbstractHibernateOrmMassIndexingIT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/AbstractHibernateOrmMassIndexingIT.java @@ -86,7 +86,7 @@ private Book newBook(int id) { return book; } - protected Author newAuthor(int id) { + private Author newAuthor(int id) { Author author = new Author(); author.setId( id ); author.setFirstName( "John" + id ); diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmJakartaBatchIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmJakartaBatchIT.java index fccb5cf0521..85da07b6a9c 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmJakartaBatchIT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmJakartaBatchIT.java @@ -10,6 +10,8 @@ import static org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils.with; import static org.junit.Assume.assumeTrue; +import java.time.Year; +import java.util.Map; import java.util.Properties; import jakarta.batch.operations.JobOperator; @@ -61,36 +63,27 @@ public void simple() throws Exception { } @Test - public void hql() throws Exception { - // tag::hql[] + public void reindexOnly() throws Exception { + // tag::reindexOnly[] Properties jobProps = MassIndexingJob.parameters() // <1> .forEntities( Author.class ) // <2> - .restrictedBy( "from Author a where a.lastName = 'Smith1'" ) // <3> + .reindexOnly( "birthDate < :cutoff", // <3> + Map.of( "cutoff", Year.of( 1950 ).atDay( 1 ) ) ) // <4> .build(); - JobOperator jobOperator = BatchRuntime.getJobOperator(); // <4> - long executionId = jobOperator.start( MassIndexingJob.NAME, jobProps ); // <5> - // end::hql[] + JobOperator jobOperator = BatchRuntime.getJobOperator(); // <5> + long executionId = jobOperator.start( MassIndexingJob.NAME, jobProps ); // <6> + // end::reindexOnly[] JobExecution jobExecution = jobOperator.getJobExecution( executionId ); jobExecution = waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); assertThat( jobExecution.getBatchStatus() ).isEqualTo( BatchStatus.COMPLETED ); with( entityManagerFactory ).runNoTransaction( entityManager -> { - assertBookAndAuthorCount( entityManager, 0, NUMBER_OF_BOOKS / 2 ); + assertBookAndAuthorCount( entityManager, 0, 500 ); } ); } - @Override - protected Author newAuthor(int id) { - Author author = new Author(); - author.setId( id ); - author.setFirstName( "John" + id ); - // use the id % 2 - author.setLastName( "Smith" + ( id % 2 ) ); - return author; - } - void assertBookAndAuthorCount(EntityManager entityManager, int expectedBookCount, int expectedAuthorCount) { setupHelper.assertions().searchAfterIndexChanges( entityManager.getEntityManagerFactory().unwrap( SessionFactory.class ), diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmMassIndexerIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmMassIndexerIT.java index 4080733430e..2b406bd491e 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmMassIndexerIT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/indexing/HibernateOrmMassIndexerIT.java @@ -12,7 +12,7 @@ import static org.hibernate.search.util.impl.test.FutureAssert.assertThatFuture; import java.lang.invoke.MethodHandles; -import java.time.LocalDate; +import java.time.Year; import java.util.concurrent.CompletionStage; import java.util.concurrent.Future; @@ -64,9 +64,9 @@ public void reindexOnly() { Search.session( entityManager ); // tag::reindexOnly[] MassIndexer massIndexer = searchSession.massIndexer(); // <2> - massIndexer.type( Book.class ).reindexOnly( "e.publicationYear <= 2100" ); // <3> - massIndexer.type( Author.class ).reindexOnly( "e.birthDate < :birthDate" ) // <4> - .param( "birthDate", LocalDate.ofYearDay( 2100, 77 ) ); // <5> + massIndexer.type( Book.class ).reindexOnly( "publicationYear < 1950" ); // <3> + massIndexer.type( Author.class ).reindexOnly( "birthDate < :cutoff" ) // <4> + .param( "cutoff", Year.of( 1950 ).atDay( 1 ) ); // <5> // end::reindexOnly[] if ( !BackendConfigurations.simple().supportsExplicitPurge() ) { massIndexer.purgeAllOnStart( false ); @@ -78,7 +78,7 @@ public void reindexOnly() { catch (InterruptedException e) { Thread.currentThread().interrupt(); } - assertBookAndAuthorCount( entityManager, 651, 651 ); + assertBookAndAuthorCount( entityManager, 500, 500 ); } ); } diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/EntityReaderComponentIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/EntityReaderComponentIT.java deleted file mode 100644 index caeb70c7d35..00000000000 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/EntityReaderComponentIT.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Hibernate Search, full-text search for your domain model - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.search.integrationtest.jakarta.batch.component; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.List; - -import jakarta.batch.runtime.context.JobContext; -import jakarta.batch.runtime.context.StepContext; -import jakarta.persistence.EntityManagerFactory; - -import org.hibernate.CacheMode; -import org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity.Company; -import org.hibernate.search.integrationtest.jakarta.batch.util.BackendConfigurations; -import org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil; -import org.hibernate.search.jakarta.batch.core.massindexing.impl.JobContextData; -import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.IndexScope; -import org.hibernate.search.jakarta.batch.core.massindexing.step.spi.EntityReader; -import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; -import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper; -import org.hibernate.search.util.impl.integrationtest.mapper.orm.ReusableOrmSetupHolder; - -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.MethodRule; - -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.mockito.quality.Strictness; - -/** - * Single-component test for item reader validation. - * - * @author Mincong Huang - */ -public class EntityReaderComponentIT { - - private static final List COMPANIES = Arrays.asList( - new Company( "Red Hat" ), - new Company( "Google" ), - new Company( "Microsoft" ) - ); - - @ClassRule - public static ReusableOrmSetupHolder setupHolder = - ReusableOrmSetupHolder.withSingleBackend( BackendConfigurations.simple() ); - @Rule - public MethodRule setupHolderMethodRule = setupHolder.methodRule(); - - @Rule - public final MockitoRule mockito = MockitoJUnit.rule().strictness( Strictness.STRICT_STUBS ); - - private EntityManagerFactory emf; - - private JobContext mockedJobContext; - - private StepContext mockedStepContext; - - private EntityReader entityReader; - - @ReusableOrmSetupHolder.Setup - public void setup(OrmSetupHelper.SetupContext setupContext) { - setupContext.withAnnotatedTypes( Company.class ) - .withProperty( HibernateOrmMapperSettings.INDEXING_LISTENERS_ENABLED, false ); - } - - @Before - public void init() { - emf = setupHolder.entityManagerFactory(); - - setupHolder.runInTransaction( session -> COMPANIES.forEach( session::persist ) ); - - final String cacheMode = CacheMode.IGNORE.name(); - final String entityName = Company.class.getName(); - final String entityFetchSize = String.valueOf( 1000 ); - final String checkpointInterval = String.valueOf( 1000 ); - final String sessionClearInterval = String.valueOf( 100 ); - final String hql = null; - final String maxResults = String.valueOf( Integer.MAX_VALUE ); - final String partitionId = String.valueOf( 0 ); - - mockedJobContext = mock( JobContext.class ); - mockedStepContext = mock( StepContext.class ); - - entityReader = new EntityReader( cacheMode, - entityName, - entityFetchSize, - checkpointInterval, - sessionClearInterval, - hql, - maxResults, - partitionId, - null, - null, - IndexScope.FULL_ENTITY.name(), - mockedJobContext, - mockedStepContext ); - } - - @Test - public void testReadItem_withoutBoundary() throws Exception { - JobContextData jobData = new JobContextData(); - jobData.setEntityManagerFactory( emf ); - jobData.setEntityTypeDescriptors( Arrays.asList( JobTestUtil.createSimpleEntityTypeDescriptor( emf, Company.class ) ) ); - - when( mockedJobContext.getTransientUserData() ).thenReturn( jobData ); - mockedStepContext.setTransientUserData( any() ); - - try { - entityReader.open( null ); - for ( Company expected : COMPANIES ) { - Company actual = (Company) entityReader.readItem(); - assertEquals( expected.getName(), actual.getName() ); - } - // no more item - assertNull( entityReader.readItem() ); - } - finally { - entityReader.close(); - } - } -} diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/HibernateSearchPartitionMapperComponentIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/HibernateSearchPartitionMapperComponentIT.java index 373c8fa41d3..c7bc758fca5 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/HibernateSearchPartitionMapperComponentIT.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/HibernateSearchPartitionMapperComponentIT.java @@ -22,7 +22,6 @@ import org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity.Person; import org.hibernate.search.integrationtest.jakarta.batch.util.BackendConfigurations; import org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil; -import org.hibernate.search.integrationtest.jakarta.batch.util.PersistenceUnitTestUtil; import org.hibernate.search.jakarta.batch.core.massindexing.impl.JobContextData; import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.HibernateSearchPartitionMapper; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.MassIndexingPartitionProperties; @@ -43,7 +42,6 @@ */ public class HibernateSearchPartitionMapperComponentIT { - private static final String PERSISTENCE_UNIT_NAME = PersistenceUnitTestUtil.getPersistenceUnitName(); private static final int COMP_ROWS = 3; private static final int PERS_ROWS = 8; @@ -79,14 +77,13 @@ public void init() { } ); final String fetchSize = String.valueOf( 200 * 1000 ); - final String hql = null; final String maxThreads = String.valueOf( 1 ); final String rowsPerPartition = String.valueOf( 3 ); mockedJobContext = mock( JobContext.class ); partitionMapper = new HibernateSearchPartitionMapper( fetchSize, - hql, + null, null, maxThreads, null, rowsPerPartition, @@ -97,7 +94,7 @@ public void init() { } /** - * Prove that there're N partitions for each root entity, + * Prove that there are N partitions for each root entity, * where N stands for the ceiling number of the division * between the rows to index and the max rows per partition. */ @@ -105,10 +102,9 @@ public void init() { public void testMapPartitions() throws Exception { JobContextData jobData = new JobContextData(); jobData.setEntityManagerFactory( emf ); - jobData.setEntityTypeDescriptors( Arrays.asList( - JobTestUtil.createSimpleEntityTypeDescriptor( emf, Company.class ), - JobTestUtil.createSimpleEntityTypeDescriptor( emf, Person.class ) - ) ); + var companyType = JobTestUtil.createEntityTypeDescriptor( emf, Company.class ); + var personType = JobTestUtil.createEntityTypeDescriptor( emf, Person.class ); + jobData.setEntityTypeDescriptors( Arrays.asList( companyType, personType ) ); when( mockedJobContext.getTransientUserData() ).thenReturn( jobData ); PartitionPlan partitionPlan = partitionMapper.mapPartitions(); @@ -117,10 +113,10 @@ public void testMapPartitions() throws Exception { int persPartitions = 0; for ( Properties p : partitionPlan.getPartitionProperties() ) { String entityName = p.getProperty( MassIndexingPartitionProperties.ENTITY_NAME ); - if ( entityName.equals( Company.class.getName() ) ) { + if ( entityName.equals( companyType.jpaEntityName() ) ) { compPartitions++; } - if ( entityName.equals( Person.class.getName() ) ) { + if ( entityName.equals( personType.jpaEntityName() ) ) { persPartitions++; } /* diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/ValidationUtilComponentIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/ValidationUtilComponentIT.java index c149525d97e..f1e2881f63d 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/ValidationUtilComponentIT.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/component/ValidationUtilComponentIT.java @@ -100,19 +100,19 @@ public void validateCheckpointInterval_greaterThanRowsPerPartition() throws Exce @Test public void validateSessionClearInterval_lessThanCheckpointInterval() throws Exception { - ValidationUtil.validateSessionClearInterval( 99, 100 ); + ValidationUtil.validateEntityFetchSize( 99, 100 ); // ok } @Test public void validateSessionClearInterval_equalToCheckpointInterval() { - ValidationUtil.validateSessionClearInterval( 100, 100 ); + ValidationUtil.validateEntityFetchSize( 100, 100 ); // ok } @Test(expected = SearchException.class) public void validateSessionClearInterval_greaterThanCheckpointInterval() throws Exception { - ValidationUtil.validateSessionClearInterval( 101, 100 ); + ValidationUtil.validateEntityFetchSize( 101, 100 ); } private static class NotIndexed { diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/EntityManagerFactoryRetrievalIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/EntityManagerFactoryRetrievalIT.java index 7afa6dc10aa..0061cf1ed5a 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/EntityManagerFactoryRetrievalIT.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/EntityManagerFactoryRetrievalIT.java @@ -6,15 +6,12 @@ */ package org.hibernate.search.integrationtest.jakarta.batch.massindexing; -import static org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil.JOB_TIMEOUT_MS; import static org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils.with; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.List; -import jakarta.batch.operations.JobOperator; -import jakarta.batch.runtime.JobExecution; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Persistence; @@ -49,12 +46,10 @@ public class EntityManagerFactoryRetrievalIT { private static final String SESSION_FACTORY_NAME = "primary_session_factory"; - protected JobOperator jobOperator; protected EntityManagerFactory emf; @Before public void setup() { - jobOperator = JobTestUtil.getAndCheckRuntime(); List companies = new ArrayList<>(); List people = new ArrayList<>(); List whos = new ArrayList<>(); @@ -97,16 +92,13 @@ public void defaultNamespace() throws Exception { List companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); assertEquals( 0, companies.size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) .entityManagerFactoryReference( getPersistenceUnitName() ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); assertEquals( INSTANCES_PER_DATA_TEMPLATE, companies.size() ); @@ -117,8 +109,7 @@ public void persistenceUnitNamespace() throws Exception { List companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); assertEquals( 0, companies.size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) @@ -126,8 +117,6 @@ public void persistenceUnitNamespace() throws Exception { .entityManagerFactoryReference( getPersistenceUnitName() ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); assertEquals( INSTANCES_PER_DATA_TEMPLATE, companies.size() ); @@ -138,8 +127,7 @@ public void sessionFactoryNamespace() throws Exception { List companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); assertEquals( 0, companies.size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) @@ -147,8 +135,6 @@ public void sessionFactoryNamespace() throws Exception { .entityManagerFactoryReference( SESSION_FACTORY_NAME ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); assertEquals( INSTANCES_PER_DATA_TEMPLATE, companies.size() ); diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/BatchIndexingJobIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobIT.java similarity index 74% rename from integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/BatchIndexingJobIT.java rename to integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobIT.java index 8280604776e..9d626c1d769 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/BatchIndexingJobIT.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobIT.java @@ -8,17 +8,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import static org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil.JOB_TIMEOUT_MS; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import java.io.IOException; -import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; import java.util.Map; -import jakarta.batch.operations.JobOperator; -import jakarta.batch.runtime.BatchStatus; import jakarta.batch.runtime.JobExecution; import jakarta.batch.runtime.StepExecution; import jakarta.persistence.EntityManagerFactory; @@ -27,13 +24,14 @@ import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Root; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity.Company; import org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity.CompanyGroup; import org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity.Person; import org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity.WhoAmI; import org.hibernate.search.integrationtest.jakarta.batch.util.BackendConfigurations; import org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil; -import org.hibernate.search.jakarta.batch.core.logging.impl.Log; import org.hibernate.search.jakarta.batch.core.massindexing.MassIndexingJob; import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.StepProgress; import org.hibernate.search.mapper.orm.Search; @@ -41,7 +39,6 @@ import org.hibernate.search.mapper.orm.session.SearchSession; import org.hibernate.search.mapper.orm.work.SearchIndexingPlan; import org.hibernate.search.util.common.AssertionFailure; -import org.hibernate.search.util.common.logging.impl.LoggerFactory; import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper; import org.hibernate.search.util.impl.integrationtest.mapper.orm.ReusableOrmSetupHolder; import org.hibernate.search.util.impl.test.annotation.TestForIssue; @@ -55,9 +52,7 @@ /** * @author Mincong Huang */ -public class BatchIndexingJobIT { - - private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); +public class MassIndexingJobIT { protected static final int INSTANCES_PER_DATA_TEMPLATE = 100; @@ -79,7 +74,6 @@ public class BatchIndexingJobIT { public MethodRule setupHolderMethodRule = setupHolder.methodRule(); private EntityManagerFactory emf; - private JobOperator jobOperator; @ReusableOrmSetupHolder.Setup public void setup(OrmSetupHelper.SetupContext setupContext, ReusableOrmSetupHolder.DataClearConfig dataClearConfig) { @@ -92,7 +86,6 @@ public void setup(OrmSetupHelper.SetupContext setupContext, ReusableOrmSetupHold @Before public void initData() { emf = setupHolder.entityManagerFactory(); - jobOperator = JobTestUtil.getAndCheckRuntime(); List companies = new ArrayList<>(); List people = new ArrayList<>(); List whos = new ArrayList<>(); @@ -116,6 +109,19 @@ public void initData() { people.forEach( session::persist ); whos.forEach( session::persist ); } ); + + setupHolder.runInTransaction( em -> { + List groups = new ArrayList<>(); + for ( int i = 0; i < INSTANCE_PER_ENTITY_TYPE; i += 3 ) { + int index1 = i; + int index2 = i + 1; + int index3 = i + 2; + groups.add( new CompanyGroup( "group" + index1, companies.get( index1 ) ) ); + groups.add( new CompanyGroup( "group" + index2, companies.get( index1 ), companies.get( index2 ) ) ); + groups.add( new CompanyGroup( "group" + index3, companies.get( index3 ) ) ); + } + groups.forEach( em::persist ); + } ); } @Test @@ -129,19 +135,15 @@ public void simple() assertEquals( 0, people.size() ); assertEquals( 0, whos.size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobExecution execution = JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntities( Company.class, Person.class, WhoAmI.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); - assertCompletion( executionId ); - assertProgress( executionId, Person.class, INSTANCE_PER_ENTITY_TYPE ); - assertProgress( executionId, Company.class, INSTANCE_PER_ENTITY_TYPE ); - assertProgress( executionId, WhoAmI.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, Person.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, Company.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, WhoAmI.class, INSTANCE_PER_ENTITY_TYPE ); companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); people = JobTestUtil.findIndexedResults( emf, Person.class, "firstName", "Linus" ); @@ -162,18 +164,14 @@ public void simple_defaultCheckpointInterval() assertEquals( 0, people.size() ); assertEquals( 0, whos.size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobExecution execution = JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntities( Company.class, Person.class, WhoAmI.class ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); - assertCompletion( executionId ); - assertProgress( executionId, Person.class, INSTANCE_PER_ENTITY_TYPE ); - assertProgress( executionId, Company.class, INSTANCE_PER_ENTITY_TYPE ); - assertProgress( executionId, WhoAmI.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, Person.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, Company.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, WhoAmI.class, INSTANCE_PER_ENTITY_TYPE ); companies = JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ); people = JobTestUtil.findIndexedResults( emf, Person.class, "firstName", "Linus" ); @@ -185,27 +183,41 @@ public void simple_defaultCheckpointInterval() @Test @TestForIssue(jiraKey = "HSEARCH-2637") - public void indexedEmbeddedCollection() - throws InterruptedException, - IOException { - setupHolder.runInTransaction( em -> { - CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); - CriteriaQuery criteria = criteriaBuilder.createQuery( Company.class ); - Root root = criteria.from( Company.class ); - Path id = root.get( root.getModel().getId( int.class ) ); - criteria.orderBy( criteriaBuilder.asc( id ) ); - List companies = em.createQuery( criteria ).getResultList(); - List groups = new ArrayList<>(); - for ( int i = 0; i < INSTANCE_PER_ENTITY_TYPE; i += 3 ) { - int index1 = i; - int index2 = i + 1; - int index3 = i + 2; - groups.add( new CompanyGroup( "group" + index1, companies.get( index1 ) ) ); - groups.add( new CompanyGroup( "group" + index2, companies.get( index1 ), companies.get( index2 ) ) ); - groups.add( new CompanyGroup( "group" + index3, companies.get( index3 ) ) ); - } - groups.forEach( em::persist ); - } ); + public void indexedEmbeddedCollection() throws InterruptedException { + List groupsContainingGoogle = + JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Google" ); + List groupsContainingRedHat = + JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Red Hat" ); + List groupsContainingMicrosoft = + JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Microsoft" ); + assertEquals( 0, groupsContainingGoogle.size() ); + assertEquals( 0, groupsContainingRedHat.size() ); + assertEquals( 0, groupsContainingMicrosoft.size() ); + + JobExecution execution = JobTestUtil.startJobAndWaitForSuccessNoRetry( + MassIndexingJob.parameters() + .forEntities( CompanyGroup.class ) + .checkpointInterval( CHECKPOINT_INTERVAL ) + .build() + ); + assertProgress( execution, CompanyGroup.class, INSTANCE_PER_ENTITY_TYPE ); + + groupsContainingGoogle = JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Google" ); + groupsContainingRedHat = JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Red Hat" ); + groupsContainingMicrosoft = JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Microsoft" ); + assertEquals( 2 * INSTANCES_PER_DATA_TEMPLATE, groupsContainingGoogle.size() ); + assertEquals( INSTANCES_PER_DATA_TEMPLATE, groupsContainingRedHat.size() ); + assertEquals( INSTANCES_PER_DATA_TEMPLATE, groupsContainingMicrosoft.size() ); + } + + @Test + @TestForIssue(jiraKey = "HSEARCH-4487") + public void indexedEmbeddedCollection_idFetchSize_entityFetchSize_mysql() throws InterruptedException { + assumeTrue( "This test only makes sense on MySQL," + + " which is the only JDBC driver that accepts (and, in a sense, requires)" + + " passing Integer.MIN_VALUE for the JDBC fetch size", + emf.unwrap( SessionFactoryImplementor.class ).getJdbcServices() + .getJdbcEnvironment().getDialect() instanceof MySQLDialect ); List groupsContainingGoogle = JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Google" ); @@ -217,17 +229,15 @@ public void indexedEmbeddedCollection() assertEquals( 0, groupsContainingRedHat.size() ); assertEquals( 0, groupsContainingMicrosoft.size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobExecution execution = JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntities( CompanyGroup.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) + // For MySQL, this is the only way to get proper scrolling + .idFetchSize( Integer.MIN_VALUE ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); - assertCompletion( executionId ); - assertProgress( executionId, CompanyGroup.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, CompanyGroup.class, INSTANCE_PER_ENTITY_TYPE ); groupsContainingGoogle = JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Google" ); groupsContainingRedHat = JobTestUtil.findIndexedResults( emf, CompanyGroup.class, "companies.name", "Red Hat" ); @@ -249,16 +259,13 @@ public void purge() throws InterruptedException, IOException { * Request a mass indexing with a filter matching nothing, * which should effectively amount to a simple purge. */ - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .purgeAllOnStart( true ) - .restrictedBy( "select c from Company c where c.name like 'NEVER_MATCH'" ) + .reindexOnly( "name like :name", Map.of( "name", "NEVER_MATCH" ) ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); assertEquals( 0, JobTestUtil.nbDocumentsInIndex( emf, Company.class ) ); } @@ -275,22 +282,19 @@ public void noPurge() throws InterruptedException, IOException { * Request a mass indexing with a filter matching nothing, and requesting no purge at all, * which should effectively amount to a no-op. */ - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .purgeAllOnStart( false ) - .restrictedBy( "select c from Company c where c.name like 'NEVER_MATCH'" ) + .reindexOnly( "name like :name", Map.of( "name", "NEVER_MATCH" ) ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); assertEquals( expectedCount, JobTestUtil.nbDocumentsInIndex( emf, Company.class ) ); } @Test - public void hql() + public void reindexOnly() throws InterruptedException, IOException { // searches before mass index, @@ -299,16 +303,13 @@ public void hql() assertEquals( 0, JobTestUtil.findIndexedResults( emf, Company.class, "name", "Red Hat" ).size() ); assertEquals( 0, JobTestUtil.findIndexedResults( emf, Company.class, "name", "Microsoft" ).size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) - .restrictedBy( "select c from Company c where c.name like 'Google%' or c.name like 'Red Hat%'" ) + .reindexOnly( "name like 'Google%' or name like 'Red Hat%'", Map.of() ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); assertEquals( INSTANCES_PER_DATA_TEMPLATE, JobTestUtil.findIndexedResults( emf, Company.class, "name", "Google" ).size() ); @@ -318,7 +319,7 @@ public void hql() } @Test - public void hql_maxResults() + public void reindexOnly_maxResults() throws InterruptedException, IOException { // searches before mass index, @@ -327,17 +328,14 @@ public void hql_maxResults() int maxResults = CHECKPOINT_INTERVAL + 1; - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) - .restrictedBy( "select c from Company c where c.name like 'Google%' or c.name like 'Red Hat%'" ) + .reindexOnly( "name like 'Google%' or name like 'Red Hat%'", Map.of() ) .maxResultsPerEntity( maxResults ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); assertEquals( maxResults, JobTestUtil.nbDocumentsInIndex( emf, Company.class ) ); } @@ -353,22 +351,18 @@ public void partitioned() assertEquals( 0, people.size() ); assertEquals( 0, whos.size() ); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobExecution execution = JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntities( Company.class, Person.class, WhoAmI.class ) .checkpointInterval( CHECKPOINT_INTERVAL ) .rowsPerPartition( INSTANCE_PER_ENTITY_TYPE - 1 ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); - assertCompletion( executionId ); - assertProgress( executionId, Person.class, INSTANCE_PER_ENTITY_TYPE ); - assertProgress( executionId, Company.class, INSTANCE_PER_ENTITY_TYPE ); - assertProgress( executionId, WhoAmI.class, INSTANCE_PER_ENTITY_TYPE ); - - StepProgress progress = getMainStepProgress( executionId ); + assertProgress( execution, Person.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, Company.class, INSTANCE_PER_ENTITY_TYPE ); + assertProgress( execution, WhoAmI.class, INSTANCE_PER_ENTITY_TYPE ); + + StepProgress progress = getMainStepProgress( execution ); Map partitionProgress = progress.getPartitionProgress(); assertThat( partitionProgress ) .as( "Entities processed per partition" ) @@ -393,28 +387,21 @@ public void partitioned() assertEquals( INSTANCES_PER_DATA_TEMPLATE, whos.size() ); } - private void assertCompletion(long executionId) { - List stepExecutions = jobOperator.getStepExecutions( executionId ); - for ( StepExecution stepExecution : stepExecutions ) { - BatchStatus batchStatus = stepExecution.getBatchStatus(); - log.infof( "step %s executed.", stepExecution.getStepName() ); - assertEquals( BatchStatus.COMPLETED, batchStatus ); - } - } - - private void assertProgress(long executionId, Class entityType, int progressValue) { + private void assertProgress(JobExecution execution, Class entityType, int progressValue) { /* * We cannot check the metrics, which in JBatch are set to 0 * for partitioned steps (the metrics are handled separately for * each partition). * Thus we check our own object. */ - StepProgress progress = getMainStepProgress( executionId ); - assertEquals( Long.valueOf( progressValue ), progress.getEntityProgress().get( entityType.getName() ) ); + StepProgress progress = getMainStepProgress( execution ); + assertEquals( Long.valueOf( progressValue ), + progress.getEntityProgress().get( emf.getMetamodel().entity( entityType ).getName() ) ); } - private StepProgress getMainStepProgress(long executionId) { - List stepExecutions = jobOperator.getStepExecutions( executionId ); + private StepProgress getMainStepProgress(JobExecution execution) { + List stepExecutions = JobTestUtil.getOperator() + .getStepExecutions( execution.getExecutionId() ); for ( StepExecution stepExecution : stepExecutions ) { switch ( stepExecution.getStepName() ) { case MAIN_STEP_NAME: diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithCompositeIdIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithCompositeIdIT.java index 7f1e8665882..fdb316817c3 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithCompositeIdIT.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithCompositeIdIT.java @@ -7,7 +7,6 @@ package org.hibernate.search.integrationtest.jakarta.batch.massindexing; import static org.assertj.core.api.Assertions.assertThat; -import static org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil.JOB_TIMEOUT_MS; import static org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils.with; import java.io.Serializable; @@ -15,7 +14,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Locale; -import java.util.Properties; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -93,13 +92,14 @@ public void initData() throws Exception { @Test @Ignore("HSEARCH-4033") // TODO HSEARCH-4033 Support mass-indexing of composite id entities - public void canHandleIdClass_strategyFull() throws Exception { - Properties props = MassIndexingJob.parameters() - .forEntities( EntityWithIdClass.class ) - .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. - .checkpointInterval( 4 ) - .build(); - JobTestUtil.startJobAndWait( MassIndexingJob.NAME, props, JOB_TIMEOUT_MS ); + public void canHandleIdClass() throws Exception { + JobTestUtil.startJobAndWaitForSuccessNoRetry( + MassIndexingJob.parameters() + .forEntities( EntityWithIdClass.class ) + .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. + .checkpointInterval( 4 ) + .build() + ); int expectedDays = (int) ChronoUnit.DAYS.between( START, END ); assertThat( JobTestUtil.nbDocumentsInIndex( emf, EntityWithIdClass.class ) ).isEqualTo( expectedDays ); @@ -107,14 +107,15 @@ public void canHandleIdClass_strategyFull() throws Exception { @Test @Ignore("HSEARCH-4033") // TODO HSEARCH-4033 Support mass-indexing of composite id entities - public void canHandleIdClass_strategyHql() throws Exception { - Properties props = MassIndexingJob.parameters() - .forEntities( EntityWithIdClass.class ) - .restrictedBy( "select e from EntityWithIdClass e where e.month = 6" ) - .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. - .checkpointInterval( 4 ) - .build(); - JobTestUtil.startJobAndWait( MassIndexingJob.NAME, props, JOB_TIMEOUT_MS ); + public void canHandleIdClass_reindexOnly() throws Exception { + JobTestUtil.startJobAndWaitForSuccessNoRetry( + MassIndexingJob.parameters() + .forEntities( EntityWithIdClass.class ) + .reindexOnly( "month >= :month", Map.of( "month", 7 ) ) + .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. + .checkpointInterval( 4 ) + .build() + ); int expectedDays = (int) ChronoUnit.DAYS.between( LocalDate.of( 2017, 7, 1 ), END ); int actualDays = JobTestUtil.nbDocumentsInIndex( emf, EntityWithIdClass.class ); @@ -122,15 +123,14 @@ public void canHandleIdClass_strategyHql() throws Exception { } @Test - @Ignore("HSEARCH-4033") // TODO HSEARCH-4033 Support mass-indexing of composite id entities - public void canHandleEmbeddedId_strategyFull() throws Exception { - Properties props = MassIndexingJob.parameters() - .forEntities( EntityWithEmbeddedId.class ) - .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. - .checkpointInterval( 4 ) - .build(); - - JobTestUtil.startJobAndWait( MassIndexingJob.NAME, props, JOB_TIMEOUT_MS ); + public void canHandleEmbeddedId() throws Exception { + JobTestUtil.startJobAndWaitForSuccessNoRetry( + MassIndexingJob.parameters() + .forEntities( EntityWithEmbeddedId.class ) + .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. + .checkpointInterval( 4 ) + .build() + ); int expectedDays = (int) ChronoUnit.DAYS.between( START, END ); int actualDays = JobTestUtil.nbDocumentsInIndex( emf, EntityWithEmbeddedId.class ); @@ -138,15 +138,15 @@ public void canHandleEmbeddedId_strategyFull() throws Exception { } @Test - @Ignore("HSEARCH-4033") // TODO HSEARCH-4033 Support mass-indexing of composite id entities - public void canHandleEmbeddedId_strategyHql() throws Exception { - Properties props = MassIndexingJob.parameters() - .forEntities( EntityWithEmbeddedId.class ) - .restrictedBy( "select e from EntityWithIdClass e where e.embeddableDateId.month = 6" ) - .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. - .checkpointInterval( 4 ) - .build(); - JobTestUtil.startJobAndWait( MassIndexingJob.NAME, props, JOB_TIMEOUT_MS ); + public void canHandleEmbeddedId_reindexOnly() throws Exception { + JobTestUtil.startJobAndWaitForSuccessNoRetry( + MassIndexingJob.parameters() + .forEntities( EntityWithEmbeddedId.class ) + .reindexOnly( "embeddableDateId.month >= :month", Map.of( "month", 7 ) ) + .rowsPerPartition( 13 ) // Ensure there're more than 1 partition, so that a WHERE clause is applied. + .checkpointInterval( 4 ) + .build() + ); int expectedDays = (int) ChronoUnit.DAYS.between( LocalDate.of( 2017, 7, 1 ), END ); int actualDays = JobTestUtil.nbDocumentsInIndex( emf, EntityWithEmbeddedId.class ); @@ -165,14 +165,14 @@ public static class EntityWithEmbeddedId { private EmbeddableDateId embeddableDateId; @FullTextField - private String value; + private String text; public EntityWithEmbeddedId() { } public EntityWithEmbeddedId(LocalDate d) { this.embeddableDateId = new EmbeddableDateId( d ); - this.value = DateTimeFormatter.ofPattern( "yyyyMMdd", Locale.ROOT ).format( d ); + this.text = DateTimeFormatter.ofPattern( "yyyyMMdd", Locale.ROOT ).format( d ); } public EmbeddableDateId getEmbeddableDateId() { @@ -183,17 +183,17 @@ public void setEmbeddableDateId(EmbeddableDateId embeddableDateId) { this.embeddableDateId = embeddableDateId; } - public String getValue() { - return value; + public String getText() { + return text; } - public void setValue(String value) { - this.value = value; + public void setText(String text) { + this.text = text; } @Override public String toString() { - return "MyDate [embeddableDateId=" + embeddableDateId + ", value=" + value + "]"; + return "MyDate [embeddableDateId=" + embeddableDateId + ", text=" + text + "]"; } /** diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithMultiTenancyIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithMultiTenancyIT.java index 865846fd67a..426a1388952 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithMultiTenancyIT.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/MassIndexingJobWithMultiTenancyIT.java @@ -7,16 +7,11 @@ package org.hibernate.search.integrationtest.jakarta.batch.massindexing; import static org.assertj.core.api.Assertions.assertThat; -import static org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil.JOB_TIMEOUT_MS; import static org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil.findIndexedResultsInTenant; import java.util.Arrays; import java.util.List; -import jakarta.batch.operations.JobOperator; -import jakarta.batch.runtime.BatchStatus; -import jakarta.batch.runtime.JobExecution; - import org.hibernate.SessionFactory; import org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity.Company; import org.hibernate.search.integrationtest.jakarta.batch.util.BackendConfigurations; @@ -48,8 +43,6 @@ public class MassIndexingJobWithMultiTenancyIT { @Rule public MethodRule setupHolderMethodRule = setupHolder.methodRule(); - private JobOperator jobOperator; - private final List companies = Arrays.asList( new Company( "Google" ), new Company( "Red Hat" ), @@ -66,8 +59,6 @@ public void setup(OrmSetupHelper.SetupContext setupContext) { @Before public void initData() { - jobOperator = JobTestUtil.getAndCheckRuntime(); - setupHolder.with( TARGET_TENANT_ID ) .runInTransaction( session -> companies.forEach( session::persist ) ); setupHolder.with( TARGET_TENANT_ID ) @@ -78,18 +69,13 @@ public void initData() { public void shouldHandleTenantIds() throws Exception { SessionFactory sessionFactory = setupHolder.sessionFactory(); - long executionId = jobOperator.start( - MassIndexingJob.NAME, + JobTestUtil.startJobAndWaitForSuccessNoRetry( MassIndexingJob.parameters() .forEntity( Company.class ) .tenantId( TARGET_TENANT_ID ) .build() ); - JobExecution jobExecution = jobOperator.getJobExecution( executionId ); - JobTestUtil.waitForTermination( jobOperator, jobExecution, JOB_TIMEOUT_MS ); - assertThat( jobExecution.getBatchStatus() ).isEqualTo( BatchStatus.COMPLETED ); - assertThat( findIndexedResultsInTenant( sessionFactory, Company.class, "name", "Google", TARGET_TENANT_ID ) ) .hasSize( 1 ); assertThat( findIndexedResultsInTenant( sessionFactory, Company.class, "name", "Red Hat", TARGET_TENANT_ID ) ) diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/RestartChunkIT.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/RestartChunkIT.java index be4dcfe5ee0..d8a6194325f 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/RestartChunkIT.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/RestartChunkIT.java @@ -6,11 +6,11 @@ */ package org.hibernate.search.integrationtest.jakarta.batch.massindexing; -import static org.hibernate.search.integrationtest.jakarta.batch.util.JobTestUtil.JOB_TIMEOUT_MS; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Properties; import jakarta.batch.operations.JobOperator; @@ -69,7 +69,7 @@ public void setup(OrmSetupHelper.SetupContext setupContext) { public void initData() { SimulatedFailure.reset(); emf = setupHolder.entityManagerFactory(); - jobOperator = JobTestUtil.getAndCheckRuntime(); + jobOperator = JobTestUtil.getOperator(); String[] str = new String[] { "Google", @@ -110,24 +110,24 @@ public void failureDuringNonFirstCheckpointBetweenTwoWrites_fullScope() throws I @TestForIssue(jiraKey = "HSEARCH-2616") public void failureBeforeFirstRead_hql() throws InterruptedException, IOException { SimulatedFailure.raiseExceptionOnNextRead(); - doTest( "select c from SimulatedFailureCompany c where c.name like 'Google%'", DB_COMP_ROWS / 5, DB_COMP_ROWS / 5 ); + doTest( "name like 'Google%'", DB_COMP_ROWS / 5, DB_COMP_ROWS / 5 ); } @Test @TestForIssue(jiraKey = "HSEARCH-2616") public void failureDuringFirstCheckpointBetweenTwoWrites_hql() throws InterruptedException, IOException { SimulatedFailure.raiseExceptionAfterXWrites( (int) ( CHECKPOINT_INTERVAL * 0.5 ) ); - doTest( "select c from SimulatedFailureCompany c where c.name like 'Google%'", DB_COMP_ROWS / 5, DB_COMP_ROWS / 5 ); + doTest( "name like 'Google%'", DB_COMP_ROWS / 5, DB_COMP_ROWS / 5 ); } @Test @TestForIssue(jiraKey = "HSEARCH-2616") public void failureDuringNonFirstCheckpointBetweenTwoWrites_hql() throws InterruptedException, IOException { SimulatedFailure.raiseExceptionAfterXWrites( (int) ( CHECKPOINT_INTERVAL * 2.5 ) ); - doTest( "select c from SimulatedFailureCompany c where c.name like 'Google%'", DB_COMP_ROWS / 5, DB_COMP_ROWS / 5 ); + doTest( "name like 'Google%'", DB_COMP_ROWS / 5, DB_COMP_ROWS / 5 ); } - private void doTest(String hql, long expectedTotal, long expectedGoogle) throws InterruptedException, IOException { + private void doTest(String reindexOnly, long expectedTotal, long expectedGoogle) throws InterruptedException, IOException { assertEquals( 0, JobTestUtil.nbDocumentsInIndex( emf, SimulatedFailureCompany.class ) ); List google = JobTestUtil.findIndexedResults( emf, SimulatedFailureCompany.class, "name", "Google" ); @@ -136,8 +136,8 @@ private void doTest(String hql, long expectedTotal, long expectedGoogle) throws // start the job MassIndexingJob.ParametersBuilder builder = MassIndexingJob.parameters() .forEntities( SimulatedFailureCompany.class ); - if ( hql != null ) { - builder = builder.restrictedBy( hql ); + if ( reindexOnly != null ) { + builder = builder.reindexOnly( reindexOnly, Map.of() ); } Properties parameters = builder .checkpointInterval( CHECKPOINT_INTERVAL ) @@ -147,7 +147,7 @@ private void doTest(String hql, long expectedTotal, long expectedGoogle) throws parameters ); JobExecution jobExec1 = jobOperator.getJobExecution( execId1 ); - JobTestUtil.waitForTermination( jobOperator, jobExec1, JOB_TIMEOUT_MS ); + JobTestUtil.waitForTermination( jobExec1 ); // job will be stopped by the SimulatedFailure assertEquals( BatchStatus.FAILED, getMainStepStatus( execId1 ) ); @@ -158,7 +158,7 @@ private void doTest(String hql, long expectedTotal, long expectedGoogle) throws */ long execId2 = jobOperator.restart( execId1, parameters ); JobExecution jobExec2 = jobOperator.getJobExecution( execId2 ); - JobTestUtil.waitForTermination( jobOperator, jobExec2, JOB_TIMEOUT_MS ); + JobTestUtil.waitForTermination( jobExec2 ); assertEquals( BatchStatus.COMPLETED, getMainStepStatus( execId2 ) ); // search again @@ -187,7 +187,7 @@ public static class SimulatedFailureCompany { public SimulatedFailureCompany() { // Called by Hibernate ORM entity loading, which in turn - // is called by the EntityReader phase of the batch. + // is called by the EntityIdReader phase of the batch. SimulatedFailure.read(); } diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/Person.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/Person.java index f019d7a4780..24e5a38ec9c 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/Person.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/Person.java @@ -26,15 +26,15 @@ public class Person { private String firstName; @FullTextField - private String famillyName; + private String familyName; public Person() { } - public Person(String id, String firstName, String famillyName) { + public Person(String id, String firstName, String familyName) { this.id = id; this.firstName = firstName; - this.famillyName = famillyName; + this.familyName = familyName; } public String getId() { @@ -53,17 +53,17 @@ public void setFirstName(String firstName) { this.firstName = firstName; } - public String getFamillyName() { - return famillyName; + public String getFamilyName() { + return familyName; } - public void setFamillyName(String famillyName) { - this.famillyName = famillyName; + public void setFamilyName(String familyName) { + this.familyName = familyName; } @Override public String toString() { - return "Person [id=" + id + ", firstName=" + firstName + ", famillyName=" + famillyName + return "Person [id=" + id + ", firstName=" + firstName + ", familyName=" + familyName + "]"; } } diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/WhoAmI.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/WhoAmI.java index 118f388be30..8472c3585bc 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/WhoAmI.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/massindexing/entity/WhoAmI.java @@ -6,6 +6,7 @@ */ package org.hibernate.search.integrationtest.jakarta.batch.massindexing.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -30,6 +31,7 @@ public class WhoAmI { private String id; @GenericField + @Column(name = "`uid`") private String uid; public WhoAmI() { diff --git a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/util/JobTestUtil.java b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/util/JobTestUtil.java index 7e5ffb28d84..ebc740e8339 100644 --- a/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/util/JobTestUtil.java +++ b/integrationtest/mapper/orm-jakarta-batch/src/test/java/org/hibernate/search/integrationtest/jakarta/batch/util/JobTestUtil.java @@ -16,17 +16,19 @@ import jakarta.batch.runtime.BatchRuntime; import jakarta.batch.runtime.BatchStatus; import jakarta.batch.runtime.JobExecution; +import jakarta.batch.runtime.StepExecution; import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.metamodel.EntityType; -import jakarta.persistence.metamodel.SingularAttribute; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.search.jakarta.batch.core.logging.impl.Log; +import org.hibernate.search.jakarta.batch.core.massindexing.MassIndexingJob; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.EntityTypeDescriptor; -import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.SingularIdOrder; import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; +import org.hibernate.search.mapper.orm.mapping.SearchMapping; import org.hibernate.search.mapper.orm.session.SearchSession; +import org.hibernate.search.mapper.orm.spi.BatchMappingContext; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** @@ -41,10 +43,24 @@ public final class JobTestUtil { private static final int THREAD_SLEEP_MS = 100; private static final String JAKARTA_BATCH_TYPE_FOR_IDE_TESTS = "jbatch"; + private static volatile JobOperator operator; + private JobTestUtil() { } - public static JobOperator getAndCheckRuntime() { + public static JobOperator getOperator() { + if ( operator == null ) { + synchronized (JobTestUtil.class) { + if ( operator == null ) { + operator = createAndCheckOperator(); + } + } + } + + return operator; + } + + private static JobOperator createAndCheckOperator() { JobOperator operator = BatchRuntime.getJobOperator(); String expectedType = System.getProperty( "org.hibernate.search.integrationtest.jakarta.batch.type" ); @@ -59,17 +75,26 @@ public static JobOperator getAndCheckRuntime() { return operator; } - public static void startJobAndWait(String jobName, Properties jobParams, int timeoutInMs) throws InterruptedException { - JobOperator jobOperator = getAndCheckRuntime(); - long execId = jobOperator.start( jobName, jobParams ); + public static JobExecution startJobAndWaitForSuccessNoRetry(Properties jobParams) throws InterruptedException { + JobOperator jobOperator = getOperator(); + long execId = jobOperator.start( MassIndexingJob.NAME, jobParams ); JobExecution jobExec = jobOperator.getJobExecution( execId ); - jobExec = JobTestUtil.waitForTermination( jobOperator, jobExec, timeoutInMs ); - assertThat( jobExec.getBatchStatus() ).isEqualTo( BatchStatus.COMPLETED ); + jobExec = JobTestUtil.waitForTermination( jobExec ); + assertThat( jobExec.getBatchStatus() ) + .as( "Status of job " + jobExec.getJobName() ) + .isEqualTo( BatchStatus.COMPLETED ); + List stepExecutions = jobOperator.getStepExecutions( jobExec.getExecutionId() ); + for ( StepExecution stepExecution : stepExecutions ) { + assertThat( stepExecution.getBatchStatus() ) + .as( "Status of step " + stepExecution.getStepName() ) + .isEqualTo( BatchStatus.COMPLETED ); + } + return jobExec; } - public static JobExecution waitForTermination(JobOperator jobOperator, JobExecution jobExecution, int timeoutInMs) + public static JobExecution waitForTermination(JobExecution jobExecution) throws InterruptedException { - long endTime = System.nanoTime() + timeoutInMs * 1_000_000L; + long endTime = System.nanoTime() + JOB_TIMEOUT_MS * 1_000_000L; while ( !jobExecution.getBatchStatus().equals( BatchStatus.COMPLETED ) && !jobExecution.getBatchStatus().equals( BatchStatus.STOPPED ) @@ -84,7 +109,7 @@ public static JobExecution waitForTermination(JobOperator jobOperator, JobExecut THREAD_SLEEP_MS ); Thread.sleep( THREAD_SLEEP_MS ); - jobExecution = jobOperator.getJobExecution( executionId ); + jobExecution = getOperator().getJobExecution( executionId ); } return jobExecution; @@ -122,9 +147,11 @@ private static List find(Session session, Class clazz, String key, Str .fetchHits( 1000 ); } - public static EntityTypeDescriptor createSimpleEntityTypeDescriptor(EntityManagerFactory emf, Class clazz) { - EntityType entityType = emf.getMetamodel().entity( clazz ); - SingularAttribute idAttribute = entityType.getId( entityType.getIdType().getJavaType() ); - return new EntityTypeDescriptor( clazz, new SingularIdOrder( idAttribute.getName() ) ); + public static EntityTypeDescriptor createEntityTypeDescriptor(EntityManagerFactory emf, Class clazz) { + SearchMapping mapping = Search.mapping( emf ); + BatchMappingContext mappingContext = (BatchMappingContext) mapping; + LoadingTypeContext type = mappingContext.typeContextProvider() + .byEntityName().getOrFail( mapping.indexedEntity( clazz ).jpaName() ); + return EntityTypeDescriptor.create( type ); } } diff --git a/integrationtest/mapper/orm/src/test/java/org/hibernate/search/integrationtest/mapper/orm/massindexing/MassIndexingConditionalExpressionsIT.java b/integrationtest/mapper/orm/src/test/java/org/hibernate/search/integrationtest/mapper/orm/massindexing/MassIndexingConditionalExpressionsIT.java index 34f90f657c5..67ae46ec510 100644 --- a/integrationtest/mapper/orm/src/test/java/org/hibernate/search/integrationtest/mapper/orm/massindexing/MassIndexingConditionalExpressionsIT.java +++ b/integrationtest/mapper/orm/src/test/java/org/hibernate/search/integrationtest/mapper/orm/massindexing/MassIndexingConditionalExpressionsIT.java @@ -137,7 +137,7 @@ public void noHierarchy() { setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H0_Indexed.class ); - indexer.type( H0_Indexed.class ).reindexOnly( "e.number = 2" ); + indexer.type( H0_Indexed.class ).reindexOnly( "number = 2" ); backendMock.expectWorks( H0_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) .add( "2", b -> b.field( "text", "text2" ) ) @@ -163,7 +163,7 @@ public void noHierarchy_withParams() { setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H0_Indexed.class ); - indexer.type( H0_Indexed.class ).reindexOnly( "e.number < :number and e.moment > :moment" ) + indexer.type( H0_Indexed.class ).reindexOnly( "number < :number and moment > :moment" ) .param( "moment", INSTANT_1 ) .param( "number", INT_1 ); @@ -190,7 +190,7 @@ public void rootNotIndexed_someSubclassesIndexed_requestMassIndexingOnRoot_condi setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H1_Root_NotIndexed.class ); - indexer.type( H1_Root_NotIndexed.class ).reindexOnly( "e.rootNumber = 2" ); + indexer.type( H1_Root_NotIndexed.class ).reindexOnly( "rootNumber = 2" ); backendMock.expectWorks( H1_B_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) .add( "10", b -> b.field( "rootText", "text10" ) @@ -220,7 +220,7 @@ public void rootNotIndexed_someSubclassesIndexed_requestMassIndexingOnRoot_condi setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H1_Root_NotIndexed.class ); - indexer.type( H1_B_Indexed.class ).reindexOnly( "e.rootNumber = 2" ); + indexer.type( H1_B_Indexed.class ).reindexOnly( "rootNumber = 2" ); backendMock.expectWorks( H1_B_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) .add( "10", b -> b.field( "rootText", "text10" ) @@ -250,7 +250,7 @@ public void rootNotIndexed_someSubclassesIndexed_requestMassIndexingOnRoot_condi setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H1_Root_NotIndexed.class ); - indexer.type( H1_B_Indexed.class ).reindexOnly( "e.bNumber = 2" ); + indexer.type( H1_B_Indexed.class ).reindexOnly( "bNumber = 2" ); backendMock.expectWorks( H1_B_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) .add( "10", b -> b.field( "rootText", "text10" ) @@ -280,7 +280,7 @@ public void rootNotIndexed_someSubclassesIndexed_requestMassIndexingOnIndexedSub setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H1_B_Indexed.class ); - indexer.type( H1_B_Indexed.class ).reindexOnly( "e.bNumber = 2" ); + indexer.type( H1_B_Indexed.class ).reindexOnly( "bNumber = 2" ); backendMock.expectWorks( H1_B_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) .add( "10", b -> b.field( "rootText", "text10" ) @@ -310,8 +310,8 @@ public void rootIndexed_someSubclassesIndexed_requestMassIndexingOnRoot() { setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H2_Root_Indexed.class ); - indexer.type( H2_Root_Indexed.class ).reindexOnly( "e.rootNumber = 2" ); - indexer.type( H2_B_Indexed.class ).reindexOnly( "e.rootNumber = 0" ); + indexer.type( H2_Root_Indexed.class ).reindexOnly( "rootNumber = 2" ); + indexer.type( H2_B_Indexed.class ).reindexOnly( "rootNumber = 0" ); backendMock.expectWorks( H2_Root_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) .add( "2", b -> b.field( "rootText", "text2" ) ) @@ -364,11 +364,11 @@ public void rootIndexed_someSubclassesIndexed_requestMassIndexingOnRoot_withPara SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H2_Root_Indexed.class ); - indexer.type( H2_Root_Indexed.class ).reindexOnly( "e.rootNumber = :number and e.rootMoment > :moment" ) + indexer.type( H2_Root_Indexed.class ).reindexOnly( "rootNumber = :number and rootMoment > :moment" ) .param( "number", INT_2 ) .param( "moment", INSTANT_0 ); - indexer.type( H2_B_Indexed.class ).reindexOnly( "e.bNumber = :number and e.rootMoment < :moment" ) + indexer.type( H2_B_Indexed.class ).reindexOnly( "bNumber = :number and rootMoment < :moment" ) .param( "number", INT_0 ) .param( "moment", INSTANT_1 ); @@ -415,7 +415,7 @@ public void rootIndexed_someSubclassesIndexed_requestMassIndexingOnIndexedSubcla setupHolder.runNoTransaction( session -> { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H2_B_Indexed.class ); - indexer.type( H2_B_Indexed.class ).reindexOnly( "e.rootNumber = 2" ); + indexer.type( H2_B_Indexed.class ).reindexOnly( "rootNumber = 2" ); backendMock.expectWorks( H2_B_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) .add( "14", b -> b @@ -448,9 +448,9 @@ public void sameFieldName_targetingEachClass() { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H3_Root.class ); - indexer.type( H3_A_Indexed.class ).reindexOnly( "e.myProperty = :myProperty" ) + indexer.type( H3_A_Indexed.class ).reindexOnly( "myProperty = :myProperty" ) .param( "myProperty", KEYWORD_A_1 ); - indexer.type( H3_B_Indexed.class ).reindexOnly( "e.myProperty = :myProperty" ) + indexer.type( H3_B_Indexed.class ).reindexOnly( "myProperty = :myProperty" ) .param( "myProperty", KEYWORD_B_1 ); backendMock.expectWorks( H3_A_Indexed.NAME, DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE ) @@ -484,7 +484,7 @@ public void sameFieldName_targetingInterface() { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H3_Root.class ); - indexer.type( H3_I.class ).reindexOnly( "e.myProperty = :p1 or e.myProperty = :p2" ) + indexer.type( H3_I.class ).reindexOnly( "myProperty = :p1 or myProperty = :p2" ) .param( "p1", KEYWORD_A_1 ) .param( "p2", KEYWORD_B_1 ); @@ -520,7 +520,7 @@ public void orCondition_filterIndexedTypeOnly() { SearchSession searchSession = Search.session( session ); MassIndexer indexer = searchSession.massIndexer( H2_Root_Indexed.class ); - indexer.type( H2_Root_Indexed.class ).reindexOnly( "e.rootNumber = :number or e.rootMoment > :moment" ) + indexer.type( H2_Root_Indexed.class ).reindexOnly( "rootNumber = :number or rootMoment > :moment" ) .param( "number", INT_2 ) .param( "moment", INSTANT_0 ); diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/logging/impl/Log.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/logging/impl/Log.java index 68b65eedee5..a15613743aa 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/logging/impl/Log.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/logging/impl/Log.java @@ -95,21 +95,21 @@ public interface Log extends BasicLogger { @LogMessage(level = Level.DEBUG) @Message(id = ID_OFFSET + 18, - value = "Opening EntityReader of partitionId='%1$s', entity='%2$s'." + value = "Opening EntityIdReader of partitionId='%1$s', entity='%2$s'." ) void openingReader(String partitionId, String entityName); @LogMessage(level = Level.DEBUG) @Message(id = ID_OFFSET + 19, - value = "Closing EntityReader of partitionId='%1$s', entity='%2$s'." + value = "Closing EntityIdReader of partitionId='%1$s', entity='%2$s'." ) void closingReader(String partitionId, String entityName); @LogMessage(level = Level.TRACE) @Message(id = ID_OFFSET + 21, - value = "Reading entity..." + value = "Reading entity identifier..." ) - void readingEntity(); + void readingEntityId(); @LogMessage(level = Level.INFO) @Message(id = ID_OFFSET + 22, @@ -119,9 +119,9 @@ public interface Log extends BasicLogger { @LogMessage(level = Level.TRACE) @Message(id = ID_OFFSET + 23, - value = "Processing entity with id: '%1$s'" + value = "Processing entity: '%1$s'" ) - void processEntity(Object entityId); + void processEntity(Object entity); @LogMessage(level = Level.INFO) @Message(id = ID_OFFSET + 26, @@ -159,11 +159,11 @@ SearchException unableToParseJobParameter(String parameterName, Object parameter SearchException failingEntityTypes(String failingEntityNames); @Message(id = ID_OFFSET + 33, - value = "The value of parameter '" + MassIndexingJobParameters.SESSION_CLEAR_INTERVAL + value = "The value of parameter '" + MassIndexingJobParameters.ENTITY_FETCH_SIZE + "' (value=%1$d) should be equal to or less than the value of parameter '" + MassIndexingJobParameters.CHECKPOINT_INTERVAL + "' (value=%2$d)." ) - SearchException illegalSessionClearInterval(int sessionClearInterval, int checkpointInterval); + SearchException illegalEntityFetchSize(int entityFetchSize, int checkpointInterval); @LogMessage(level = Level.DEBUG) @Message(id = ID_OFFSET + 34, diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJob.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJob.java index 498ec8d73f9..65dbece0b14 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJob.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJob.java @@ -6,9 +6,11 @@ */ package org.hibernate.search.jakarta.batch.core.massindexing; +import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; @@ -16,8 +18,10 @@ import jakarta.persistence.EntityManagerFactory; import org.hibernate.CacheMode; +import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.SerializationUtil; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.ValidationUtil; import org.hibernate.search.util.common.SearchException; +import org.hibernate.search.util.common.impl.Contracts; /** * A utility class to start the Hibernate Search Jakarta Batch mass indexing job. @@ -95,11 +99,11 @@ public static class ParametersBuilder { private Boolean purgeAllOnStart; private Integer idFetchSize; private Integer entityFetchSize; - private Integer sessionClearInterval; private Integer checkpointInterval; private Integer rowsPerPartition; private Integer maxThreads; - private String customQueryHql; + private String reindexOnlyHql; + private String serializedReindexOnlyParameters; private Integer maxResultsPerEntity; private String tenantId; @@ -159,23 +163,6 @@ public ParametersBuilder cacheMode(CacheMode cacheMode) { return this; } - /** - * The number of entities to process before clearing the session. The value defined must be greater - * than 0, and equal to or less than the value of {@link #checkpointInterval}. - *

- * This is an optional parameter, its default value is - * {@link MassIndexingJobParameters.Defaults#SESSION_CLEAR_INTERVAL_DEFAULT_RAW}, - * or the value of {@link #checkpointInterval} if it is smaller. - * - * @param sessionClearInterval the number of entities to process before clearing the session. - * - * @return itself - */ - public ParametersBuilder sessionClearInterval(int sessionClearInterval) { - this.sessionClearInterval = sessionClearInterval; - return this; - } - /** * The number of entities to process before triggering the next checkpoint. The value defined must be greater * than 0, and equal to or less than the value of {@link #rowsPerPartition}. @@ -212,19 +199,21 @@ public ParametersBuilder idFetchSize(int idFetchSize) { } /** - * Specifies the fetch size to be used when loading entities from - * database. Some databases accept special values, for example MySQL - * might benefit from using {@link Integer#MIN_VALUE}, otherwise it - * will attempt to preload everything in memory. + * Specifies the fetch size to be used when loading entities from the database. + *

+ * The value defined must be greater + * than 0, and equal to or less than the value of {@link #checkpointInterval}. *

* This is an optional parameter, its default value is - * the value of the session clear interval. + * {@link MassIndexingJobParameters.Defaults#ENTITY_FETCH_SIZE_RAW}, + * or the value of {@link #checkpointInterval} if it is smaller. * * @param entityFetchSize the fetch size to be used when loading entities * * @return itself */ public ParametersBuilder entityFetchSize(int entityFetchSize) { + Contracts.assertStrictlyPositive( entityFetchSize, "entityFetchSize" ); this.entityFetchSize = entityFetchSize; return this; } @@ -321,19 +310,43 @@ public ParametersBuilder purgeAllOnStart(boolean purgeAllOnStart) { } /** - * Use HQL / JPQL to index entities of a target entity type. Your query should contain only one entity type. - * Mixing this approach with the criteria restriction is not allowed. Please notice that there's no query - * validation for your input. - * - * @param hql HQL / JPQL. + * Use a JPQL/HQL conditional expression to limit the entities to be re-indexed. + *

+ * The letter {@code e} is supposed to be used here as query alias. + * For instance a valid expression could be the following: + *

+		 *     manager.level < 2
+		 * 
+ * ... to filter instances that have a manager whose level is strictly less than 2. + *

+ * Parameters can be used, so assuming the parameter "max" is defined + * in the {@code parameters} {@link Map}, + * this is valid as well: + *

+		 *     manager.level < :max
+		 * 
+ * ... to filter instances that have a manager whose level is strictly less than {@code :max}. + * + * @param hql A JPQL/HQL conditional expression, e.g. {@code manager.level < 2} + * @param parameters A map of named parameters parameters that may be used in the conditional expression + * with the usual JPQL/HQL colon-prefixed syntax (e.g. ":myparam"). * * @return itself */ - public ParametersBuilder restrictedBy(String hql) { - if ( hql == null ) { - throw new NullPointerException( "The HQL is null." ); + public ParametersBuilder reindexOnly(String hql, Map parameters) { + Contracts.assertNotNull( hql, "hql" ); + Contracts.assertNotNull( parameters, "parameters" ); + this.reindexOnlyHql = hql; + try { + this.serializedReindexOnlyParameters = parameters == null + ? null + : SerializationUtil.serialize( parameters ); + } + catch (IOException e) { + throw new IllegalArgumentException( + "Failed to serialize parameters; the parameters must be a serializable Map with serializable keys and values.", + e ); } - this.customQueryHql = hql; return this; } @@ -389,10 +402,10 @@ public Properties build() { defaultedCheckpointInterval, rowsPerPartition != null ? rowsPerPartition : MassIndexingJobParameters.Defaults.ROWS_PER_PARTITION ); - int defaultedSessionClearInterval = - MassIndexingJobParameters.Defaults.sessionClearInterval( sessionClearInterval, + int defaultedEntityFetchSize = + MassIndexingJobParameters.Defaults.entityFetchSize( entityFetchSize, defaultedCheckpointInterval ); - ValidationUtil.validateSessionClearInterval( defaultedSessionClearInterval, defaultedCheckpointInterval ); + ValidationUtil.validateEntityFetchSize( defaultedEntityFetchSize, defaultedCheckpointInterval ); Properties jobParams = new Properties(); @@ -402,9 +415,10 @@ public Properties build() { entityManagerFactoryReference ); addIfNotNull( jobParams, MassIndexingJobParameters.ID_FETCH_SIZE, idFetchSize ); addIfNotNull( jobParams, MassIndexingJobParameters.ENTITY_FETCH_SIZE, entityFetchSize ); - addIfNotNull( jobParams, MassIndexingJobParameters.CUSTOM_QUERY_HQL, customQueryHql ); + addIfNotNull( jobParams, MassIndexingJobParameters.REINDEX_ONLY_HQL, reindexOnlyHql ); + addIfNotNull( jobParams, MassIndexingJobParameters.REINDEX_ONLY_PARAMETERS, + serializedReindexOnlyParameters ); addIfNotNull( jobParams, MassIndexingJobParameters.CHECKPOINT_INTERVAL, checkpointInterval ); - addIfNotNull( jobParams, MassIndexingJobParameters.SESSION_CLEAR_INTERVAL, sessionClearInterval ); addIfNotNull( jobParams, MassIndexingJobParameters.MAX_RESULTS_PER_ENTITY, maxResultsPerEntity ); addIfNotNull( jobParams, MassIndexingJobParameters.MAX_THREADS, maxThreads ); addIfNotNull( jobParams, MassIndexingJobParameters.MERGE_SEGMENTS_AFTER_PURGE, mergeSegmentsAfterPurge ); diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParameters.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParameters.java index ca3a5b638df..78c369252c2 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParameters.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParameters.java @@ -43,9 +43,8 @@ private MassIndexingJobParameters() { public static final String CHECKPOINT_INTERVAL = "checkpointInterval"; - public static final String SESSION_CLEAR_INTERVAL = "sessionClearInterval"; - - public static final String CUSTOM_QUERY_HQL = "customQueryHQL"; + public static final String REINDEX_ONLY_HQL = "reindexOnlyHql"; + public static final String REINDEX_ONLY_PARAMETERS = "reindexOnlyParameters"; public static final String TENANT_ID = "tenantId"; @@ -77,14 +76,14 @@ public static int checkpointInterval(Integer checkpointIntervalRaw, Integer rows } } - public static final int SESSION_CLEAR_INTERVAL_DEFAULT_RAW = 200; + public static final int ENTITY_FETCH_SIZE_RAW = 200; - public static int sessionClearInterval(Integer sessionClearIntervalRaw, Integer checkpointInterval) { - if ( sessionClearIntervalRaw != null ) { - return sessionClearIntervalRaw; + public static int entityFetchSize(Integer entityFetchSizeRaw, Integer checkpointInterval) { + if ( entityFetchSizeRaw != null ) { + return entityFetchSizeRaw; } - if ( checkpointInterval == null || checkpointInterval > SESSION_CLEAR_INTERVAL_DEFAULT_RAW ) { - return SESSION_CLEAR_INTERVAL_DEFAULT_RAW; + if ( checkpointInterval == null || checkpointInterval > ENTITY_FETCH_SIZE_RAW ) { + return ENTITY_FETCH_SIZE_RAW; } else { return checkpointInterval; diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/impl/JobContextData.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/impl/JobContextData.java index 7121557bf17..2a06838f5b1 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/impl/JobContextData.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/impl/JobContextData.java @@ -6,6 +6,7 @@ */ package org.hibernate.search.jakarta.batch.core.massindexing.impl; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -32,7 +33,7 @@ public class JobContextData { * In Jakarta Batch standard, only string values can be propagated using job properties, but class types are frequently * used too. So this map has string keys to facilitate lookup for values extracted from job properties. */ - private Map entityTypeDescriptorMap; + private Map> entityTypeDescriptorMap; public JobContextData() { entityTypeDescriptorMap = new HashMap<>(); @@ -46,14 +47,14 @@ public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } - public void setEntityTypeDescriptors(Collection descriptors) { - for ( EntityTypeDescriptor descriptor : descriptors ) { - entityTypeDescriptorMap.put( descriptor.getJavaClass().getName(), descriptor ); + public void setEntityTypeDescriptors(Collection> descriptors) { + for ( EntityTypeDescriptor descriptor : descriptors ) { + entityTypeDescriptorMap.put( descriptor.jpaEntityName(), descriptor ); } } - public EntityTypeDescriptor getEntityTypeDescriptor(String entityName) { - EntityTypeDescriptor descriptor = entityTypeDescriptorMap.get( entityName ); + public EntityTypeDescriptor getEntityTypeDescriptor(String entityName) { + EntityTypeDescriptor descriptor = entityTypeDescriptorMap.get( entityName ); if ( descriptor == null ) { String msg = String.format( Locale.ROOT, "entity type %s not found.", entityName ); throw new NoSuchElementException( msg ); @@ -61,25 +62,16 @@ public EntityTypeDescriptor getEntityTypeDescriptor(String entityName) { return descriptor; } - public EntityTypeDescriptor getEntityTypeDescriptor(Class entityType) { - return getEntityTypeDescriptor( entityType.getName() ); - } - - public List getEntityTypeDescriptors() { - return entityTypeDescriptorMap.values().stream() - .collect( Collectors.toList() ); + public List> getEntityTypeDescriptors() { + return new ArrayList<>( entityTypeDescriptorMap.values() ); } public List> getEntityTypes() { return entityTypeDescriptorMap.values().stream() - .map( EntityTypeDescriptor::getJavaClass ) + .map( EntityTypeDescriptor::javaClass ) .collect( Collectors.toList() ); } - public Class getEntityType(String entityName) { - return getEntityTypeDescriptor( entityName ).getJavaClass(); - } - @Override public String toString() { return new StringBuilder() diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/spi/JobContextSetupListener.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/spi/JobContextSetupListener.java index b1b8d52b573..cfa43538a1f 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/spi/JobContextSetupListener.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/spi/JobContextSetupListener.java @@ -88,10 +88,6 @@ public class JobContextSetupListener extends AbstractJobListener { @BatchProperty(name = MassIndexingJobParameters.CHECKPOINT_INTERVAL) private String serializedCheckpointInterval; - @Inject - @BatchProperty(name = MassIndexingJobParameters.SESSION_CLEAR_INTERVAL) - private String serializedSessionClearInterval; - @Inject @BatchProperty(name = MassIndexingJobParameters.ROWS_PER_PARTITION) private String serializedRowsPerPartition; @@ -138,17 +134,17 @@ private void validateChunkSettings() { ); int checkpointInterval = MassIndexingJobParameters.Defaults.checkpointInterval( checkpointIntervalRaw, rowsPerPartition ); - Integer sessionClearIntervalRaw = SerializationUtil.parseIntegerParameterOptional( - MassIndexingJobParameters.SESSION_CLEAR_INTERVAL, serializedSessionClearInterval, null + Integer entityFetchSizeRaw = SerializationUtil.parseIntegerParameterOptional( + MassIndexingJobParameters.ENTITY_FETCH_SIZE, serializedEntityFetchSize, null ); - int sessionClearInterval = - MassIndexingJobParameters.Defaults.sessionClearInterval( sessionClearIntervalRaw, checkpointInterval ); + int entityFetchSize = + MassIndexingJobParameters.Defaults.entityFetchSize( entityFetchSizeRaw, checkpointInterval ); - ValidationUtil.validatePositive( MassIndexingJobParameters.SESSION_CLEAR_INTERVAL, sessionClearInterval ); + ValidationUtil.validatePositive( MassIndexingJobParameters.ENTITY_FETCH_SIZE, entityFetchSize ); ValidationUtil.validatePositive( MassIndexingJobParameters.CHECKPOINT_INTERVAL, checkpointInterval ); ValidationUtil.validatePositive( MassIndexingJobParameters.ROWS_PER_PARTITION, rowsPerPartition ); ValidationUtil.validateCheckpointInterval( checkpointInterval, rowsPerPartition ); - ValidationUtil.validateSessionClearInterval( sessionClearInterval, checkpointInterval ); + ValidationUtil.validateEntityFetchSize( entityFetchSize, checkpointInterval ); } private void validateJobSettings() { diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/EntityWriter.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/EntityWriter.java index 5e240d52637..7080fb49b32 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/EntityWriter.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/EntityWriter.java @@ -17,9 +17,12 @@ import jakarta.batch.runtime.context.StepContext; import jakarta.inject.Inject; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.LockModeType; +import org.hibernate.CacheMode; +import org.hibernate.FlushMode; import org.hibernate.Session; -import org.hibernate.SessionFactory; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.search.engine.backend.work.execution.DocumentCommitStrategy; import org.hibernate.search.engine.backend.work.execution.DocumentRefreshStrategy; import org.hibernate.search.engine.backend.work.execution.OperationSubmitter; @@ -27,11 +30,13 @@ import org.hibernate.search.jakarta.batch.core.logging.impl.Log; import org.hibernate.search.jakarta.batch.core.massindexing.MassIndexingJobParameters; import org.hibernate.search.jakarta.batch.core.massindexing.impl.JobContextData; +import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.EntityTypeDescriptor; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.MassIndexingPartitionProperties; +import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.PersistenceUtil; +import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.SerializationUtil; import org.hibernate.search.mapper.orm.Search; import org.hibernate.search.mapper.orm.mapping.SearchMapping; import org.hibernate.search.mapper.orm.spi.BatchMappingContext; -import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; import org.hibernate.search.mapper.pojo.work.spi.PojoIndexer; import org.hibernate.search.mapper.pojo.work.spi.PojoScopeWorkspace; import org.hibernate.search.util.common.SearchException; @@ -41,6 +46,7 @@ public class EntityWriter extends AbstractItemWriter { private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + private static final String ID_PARAMETER_NAME = "ids"; @Inject private JobContext jobContext; @@ -48,6 +54,18 @@ public class EntityWriter extends AbstractItemWriter { @Inject private StepContext stepContext; + @Inject + @BatchProperty(name = MassIndexingJobParameters.CHECKPOINT_INTERVAL) + private String serializedCheckpointInterval; + + @Inject + @BatchProperty(name = MassIndexingJobParameters.CACHE_MODE) + private String serializedCacheMode; + + @Inject + @BatchProperty(name = MassIndexingJobParameters.ENTITY_FETCH_SIZE) + private String serializedEntityFetchSize; + @Inject @BatchProperty(name = MassIndexingPartitionProperties.ENTITY_NAME) private String entityName; @@ -60,10 +78,12 @@ public class EntityWriter extends AbstractItemWriter { @BatchProperty(name = MassIndexingJobParameters.TENANT_ID) private String tenantId; + private CacheMode cacheMode; + private int entityFetchSize; + private EntityManagerFactory emf; - private SearchMapping searchMapping; private BatchMappingContext mappingContext; - private PojoRawTypeIdentifier typeIdentifier; + private EntityTypeDescriptor type; private PojoScopeWorkspace workspace; private WriteMode writeMode; @@ -81,10 +101,20 @@ public void open(Serializable checkpoint) { JobContextData jobContextData = (JobContextData) jobContext.getTransientUserData(); emf = jobContextData.getEntityManagerFactory(); - searchMapping = Search.mapping( emf ); + SearchMapping searchMapping = Search.mapping( emf ); mappingContext = (BatchMappingContext) searchMapping; - typeIdentifier = mappingContext.typeContextProvider().byEntityName().getOrFail( entityName ).typeIdentifier(); - workspace = mappingContext.scope( typeIdentifier.javaClass(), entityName ).pojoWorkspace( tenantId ); + type = jobContextData.getEntityTypeDescriptor( entityName ); + workspace = mappingContext.scope( type.javaClass(), entityName ).pojoWorkspace( tenantId ); + + cacheMode = SerializationUtil.parseCacheModeParameter( + MassIndexingJobParameters.CACHE_MODE, serializedCacheMode, MassIndexingJobParameters.Defaults.CACHE_MODE + ); + int checkpointInterval = SerializationUtil.parseIntegerParameter( + MassIndexingJobParameters.CHECKPOINT_INTERVAL, serializedCheckpointInterval + ); + Integer entityFetchSizeRaw = SerializationUtil.parseIntegerParameterOptional( + MassIndexingJobParameters.ENTITY_FETCH_SIZE, serializedEntityFetchSize, null ); + entityFetchSize = MassIndexingJobParameters.Defaults.entityFetchSize( entityFetchSizeRaw, checkpointInterval ); /* * Always execute works as updates on the first checkpoint interval, @@ -98,42 +128,64 @@ public void open(Serializable checkpoint) { } @Override - public void writeItems(List entities) { - try ( Session session = emf.unwrap( SessionFactory.class ) - .withOptions() - .tenantIdentifier( tenantId ) - .openSession() ) { + public void writeItems(List entityIds) { + try ( Session session = PersistenceUtil.openSession( emf, tenantId ) ) { + SessionImplementor sessionImplementor = session.unwrap( SessionImplementor.class ); PojoIndexer indexer = mappingContext.sessionContext( session ).createIndexer(); - indexAndWaitForCompletion( entities, indexer ); - - /* - * Flush after each write operation - * This ensures the writes have actually been persisted, - * which is necessary because the runtime will perform a checkpoint - * just after we return from this method. - */ - Futures.unwrappedExceptionJoin( workspace.flush( OperationSubmitter.blocking(), - // If not supported, we're on Amazon OpenSearch Serverless, - // and in this case purge writes are safe even without a flush. - UnsupportedOperationBehavior.IGNORE ) ); + int i = 0; + while ( i < entityIds.size() ) { + int fromIndex = i; + i += entityFetchSize; + int toIndex = Math.min( i, entityIds.size() ); + + List entities = loadEntities( sessionImplementor, entityIds.subList( fromIndex, toIndex ) ); + + indexAndWaitForCompletion( entities, indexer ); + } } + /* + * Flush after each write operation + * This ensures the writes have actually been persisted, + * which is necessary because the runtime will perform a checkpoint + * just after we return from this method. + */ + Futures.unwrappedExceptionJoin( workspace.flush( OperationSubmitter.blocking(), + // If not supported, we're on Amazon OpenSearch Serverless, + // and in this case purge writes are safe even without a flush. + UnsupportedOperationBehavior.IGNORE ) ); + // update work count PartitionContextData partitionData = (PartitionContextData) stepContext.getTransientUserData(); - partitionData.documentAdded( entities.size() ); + partitionData.documentAdded( entityIds.size() ); /* * We can switch to a faster mode, without checks, because we know the next items * we'll write haven't been written to the index yet. */ this.writeMode = WriteMode.ADD; + } + @Override + public void close() throws Exception { log.closingEntityWriter( partitionIdStr, entityName ); } - private void indexAndWaitForCompletion(List entities, PojoIndexer indexer) { + private List loadEntities(SessionImplementor session, List entityIds) { + return type.createLoadingQuery( session, ID_PARAMETER_NAME ) + .setParameter( ID_PARAMETER_NAME, entityIds ) + .setReadOnly( true ) + .setCacheable( false ) + .setLockMode( LockModeType.NONE ) + .setCacheMode( cacheMode ) + .setHibernateFlushMode( FlushMode.MANUAL ) + .setFetchSize( entityFetchSize ) + .list(); + } + + private void indexAndWaitForCompletion(List entities, PojoIndexer indexer) { if ( entities == null || entities.isEmpty() ) { return; } @@ -157,13 +209,13 @@ private CompletableFuture writeItem(PojoIndexer indexer, Object entity) { log.processEntity( entity ); if ( WriteMode.ADD.equals( writeMode ) ) { - return indexer.add( typeIdentifier, null, null, entity, + return indexer.add( type.typeIdentifier(), null, null, entity, // Commit and refresh are handled globally after all documents are indexed. DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE, OperationSubmitter.blocking() ); } - return indexer.addOrUpdate( typeIdentifier, null, null, entity, + return indexer.addOrUpdate( type.typeIdentifier(), null, null, entity, // Commit and refresh are handled globally after all documents are indexed. DocumentCommitStrategy.NONE, DocumentRefreshStrategy.NONE, OperationSubmitter.blocking() ); diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/HibernateSearchPartitionMapper.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/HibernateSearchPartitionMapper.java index 2e0ae58bd9e..ef2923373a2 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/HibernateSearchPartitionMapper.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/HibernateSearchPartitionMapper.java @@ -19,16 +19,12 @@ import jakarta.inject.Inject; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.LockModeType; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Root; -import jakarta.persistence.metamodel.EntityType; -import jakarta.persistence.metamodel.SingularAttribute; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.StatelessSession; -import org.hibernate.query.Query; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.SelectionQuery; import org.hibernate.search.jakarta.batch.core.logging.impl.Log; import org.hibernate.search.jakarta.batch.core.massindexing.MassIndexingJobParameters; import org.hibernate.search.jakarta.batch.core.massindexing.impl.JobContextData; @@ -37,6 +33,7 @@ import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.PartitionBound; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.PersistenceUtil; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.SerializationUtil; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** @@ -71,8 +68,12 @@ public class HibernateSearchPartitionMapper implements PartitionMapper { private String serializedIdFetchSize; @Inject - @BatchProperty(name = MassIndexingJobParameters.CUSTOM_QUERY_HQL) - private String customQueryHql; + @BatchProperty(name = MassIndexingJobParameters.REINDEX_ONLY_HQL) + private String reindexOnlyHql; + + @Inject + @BatchProperty(name = MassIndexingJobParameters.REINDEX_ONLY_PARAMETERS) + private String serializedReindexOnlyParameters; @Inject @BatchProperty(name = MassIndexingJobParameters.MAX_THREADS) @@ -104,7 +105,8 @@ public HibernateSearchPartitionMapper() { */ public HibernateSearchPartitionMapper( String serializedIdFetchSize, - String customQueryHql, + String reindexOnlyHql, + String serializedReindexOnlyParameters, String serializedMaxThreads, String serializedMaxResultsPerEntity, String serializedRowsPerPartition, @@ -112,7 +114,8 @@ public HibernateSearchPartitionMapper( String tenantId, JobContext jobContext) { this.serializedIdFetchSize = serializedIdFetchSize; - this.customQueryHql = customQueryHql; + this.reindexOnlyHql = reindexOnlyHql; + this.serializedReindexOnlyParameters = serializedReindexOnlyParameters; this.serializedMaxThreads = serializedMaxThreads; this.serializedMaxResultsPerEntity = serializedMaxResultsPerEntity; this.serializedRowsPerPartition = serializedRowsPerPartition; @@ -144,23 +147,15 @@ public PartitionPlan mapPartitions() throws Exception { MassIndexingJobParameters.ID_FETCH_SIZE, serializedIdFetchSize, MassIndexingJobParameters.Defaults.ID_FETCH_SIZE ); + ConditionalExpression reindexOnly = + SerializationUtil.parseReindexOnlyParameters( reindexOnlyHql, serializedReindexOnlyParameters ); - List entityTypeDescriptors = jobData.getEntityTypeDescriptors(); + List> entityTypeDescriptors = jobData.getEntityTypeDescriptors(); List partitionBounds = new ArrayList<>(); - switch ( PersistenceUtil.getIndexScope( customQueryHql ) ) { - case HQL: - Class clazz = entityTypeDescriptors.get( 0 ).getJavaClass(); - partitionBounds.add( new PartitionBound( clazz, null, null, IndexScope.HQL ) ); - break; - - case FULL_ENTITY: - for ( EntityTypeDescriptor entityTypeDescriptor : entityTypeDescriptors ) { - partitionBounds.addAll( buildPartitionUnitsFrom( emf, ss, entityTypeDescriptor, - maxResults, idFetchSize, rowsPerPartition, - IndexScope.FULL_ENTITY ) ); - } - break; + for ( EntityTypeDescriptor entityTypeDescriptor : entityTypeDescriptors ) { + partitionBounds.addAll( buildPartitionUnitsFrom( ss, entityTypeDescriptor, + maxResults, idFetchSize, rowsPerPartition, reindexOnly ) ); } // Build partition plan @@ -176,7 +171,6 @@ public PartitionPlan mapPartitions() throws Exception { SerializationUtil.serialize( bound.getLowerBound() ) ); props[i].setProperty( MassIndexingPartitionProperties.UPPER_BOUND, SerializationUtil.serialize( bound.getUpperBound() ) ); - props[i].setProperty( MassIndexingPartitionProperties.INDEX_SCOPE, bound.getIndexScope().name() ); props[i].setProperty( MassIndexingPartitionProperties.CHECKPOINT_INTERVAL, String.valueOf( checkpointInterval ) @@ -200,33 +194,17 @@ public PartitionPlan mapPartitions() throws Exception { } } - @SuppressWarnings("unchecked") // Can't do much better without adding generics to EntityTypeDescriptor - private List buildPartitionUnitsFrom(EntityManagerFactory emf, StatelessSession ss, - EntityTypeDescriptor entityTypeDescriptor, - Integer maxResults, int fetchSize, int rowsPerPartition, - IndexScope indexScope) { - Class javaClass = entityTypeDescriptor.getJavaClass(); + private List buildPartitionUnitsFrom(StatelessSession ss, + EntityTypeDescriptor type, + Integer maxResults, int fetchSize, int rowsPerPartition, ConditionalExpression reindexOnly) { List partitionUnits = new ArrayList<>(); Object lowerID; Object upperID = null; - CriteriaBuilder builder = emf.getCriteriaBuilder(); - CriteriaQuery criteria = builder.createQuery(); - Root root = criteria.from( javaClass ); - - entityTypeDescriptor.getIdOrder().addAscOrder( builder, criteria, root ); - - EntityType model = root.getModel(); - Class javaType = model.getIdType().getJavaType(); - - @SuppressWarnings("rawtypes") - SingularAttribute singularAttribute = model.getId( javaType ); - criteria.select( root.get( singularAttribute ) ); - - Query query = ss.createQuery( criteria ); - - query.setFetchSize( fetchSize ) + SelectionQuery query = type.createIdentifiersQuery( (SharedSessionContractImplementor) ss, + reindexOnly == null ? List.of() : List.of( reindexOnly ) ) + .setFetchSize( fetchSize ) .setReadOnly( true ) .setCacheable( false ) .setLockMode( LockModeType.NONE ); @@ -248,13 +226,13 @@ private List buildPartitionUnitsFrom(EntityManagerFactory emf, S while ( scroll.scroll( rowsPerPartition ) ) { lowerID = upperID; upperID = scroll.get(); - partitionUnits.add( new PartitionBound( javaClass, lowerID, upperID, indexScope ) ); + partitionUnits.add( new PartitionBound( type, lowerID, upperID ) ); } // add an additional partition on the tail lowerID = upperID; upperID = null; - partitionUnits.add( new PartitionBound( javaClass, lowerID, upperID, indexScope ) ); + partitionUnits.add( new PartitionBound( type, lowerID, upperID ) ); return partitionUnits; } } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/IndexScope.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/IndexScope.java deleted file mode 100644 index 8b422dac86a..00000000000 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/IndexScope.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Hibernate Search, full-text search for your domain model - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.search.jakarta.batch.core.massindexing.step.impl; - -/** - * The index scope of a given entity type. - * - * @author Mincong Huang - */ -public enum IndexScope { - /** - * Index entities restricted by the HQL / JPQL given by user. - */ - HQL, - /** - * Index all the entities found. - */ - FULL_ENTITY -} diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/StepProgressSetupListener.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/StepProgressSetupListener.java index 27e81c09418..6b085d008bc 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/StepProgressSetupListener.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/impl/StepProgressSetupListener.java @@ -6,8 +6,9 @@ */ package org.hibernate.search.jakarta.batch.core.massindexing.step.impl; +import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.util.function.BiFunction; +import java.util.List; import jakarta.batch.api.BatchProperty; import jakarta.batch.api.listener.AbstractStepListener; @@ -15,15 +16,17 @@ import jakarta.batch.runtime.context.StepContext; import jakarta.inject.Inject; import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.LockModeType; -import org.hibernate.Session; -import org.hibernate.query.Query; +import org.hibernate.StatelessSession; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.search.jakarta.batch.core.logging.impl.Log; import org.hibernate.search.jakarta.batch.core.massindexing.MassIndexingJobParameters; import org.hibernate.search.jakarta.batch.core.massindexing.impl.JobContextData; +import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.EntityTypeDescriptor; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.PersistenceUtil; +import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.SerializationUtil; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** @@ -46,8 +49,12 @@ public class StepProgressSetupListener extends AbstractStepListener { private String tenantId; @Inject - @BatchProperty(name = MassIndexingJobParameters.CUSTOM_QUERY_HQL) - private String customQueryHql; + @BatchProperty(name = MassIndexingJobParameters.REINDEX_ONLY_HQL) + private String reindexOnlyHql; + + @Inject + @BatchProperty(name = MassIndexingJobParameters.REINDEX_ONLY_PARAMETERS) + private String serializedReindexOnlyParameters; /** * Setup the step-level indexing progress. The {@code StepProgress} will be initialized if this is the first start, @@ -58,36 +65,21 @@ public class StepProgressSetupListener extends AbstractStepListener { * (*): for partitions' sub-threads, they store other things as a transient user data. */ @Override - public void beforeStep() { + public void beforeStep() throws IOException, ClassNotFoundException { StepProgress stepProgress = (StepProgress) stepContext.getPersistentUserData(); if ( stepProgress == null ) { stepProgress = new StepProgress(); JobContextData jobData = (JobContextData) jobContext.getTransientUserData(); EntityManagerFactory emf = jobData.getEntityManagerFactory(); - - IndexScope indexScope = PersistenceUtil.getIndexScope( customQueryHql ); - BiFunction, Long> rowCountFunction; - switch ( indexScope ) { - case HQL: - // We can't estimate the number of results when using HQL - rowCountFunction = (session, clazz) -> null; - break; - - case FULL_ENTITY: - rowCountFunction = StepProgressSetupListener::countAll; - break; - - default: - // This should never happen. - throw new IllegalStateException( "Unknown value from enum: " + IndexScope.class ); - } - - try ( Session session = PersistenceUtil.openSession( emf, tenantId ) ) { - for ( Class entityType : jobData.getEntityTypes() ) { - Long rowCount = rowCountFunction.apply( session, entityType ); - log.rowsToIndex( entityType.getName(), rowCount ); - stepProgress.setRowsToIndex( entityType.getName(), rowCount ); + ConditionalExpression reindexOnly = + SerializationUtil.parseReindexOnlyParameters( reindexOnlyHql, serializedReindexOnlyParameters ); + + try ( StatelessSession session = PersistenceUtil.openStatelessSession( emf, tenantId ) ) { + for ( EntityTypeDescriptor type : jobData.getEntityTypeDescriptors() ) { + Long rowCount = countAll( session, type, reindexOnly ); + log.rowsToIndex( type.jpaEntityName(), rowCount ); + stepProgress.setRowsToIndex( type.jpaEntityName(), rowCount ); } } } @@ -104,15 +96,12 @@ public void afterStep() { stepContext.setPersistentUserData( stepProgress ); } - private static Long countAll(Session session, Class entityType) { - CriteriaBuilder builder = session.getCriteriaBuilder(); - CriteriaQuery criteria = builder.createQuery( Long.class ); - criteria.select( builder.count( criteria.from( entityType ) ) ); - - Query query = session.createQuery( criteria ); - query.setCacheable( false ) + private static Long countAll(StatelessSession session, EntityTypeDescriptor type, ConditionalExpression reindexOnly) { + return type.createCountQuery( (SharedSessionContractImplementor) session, + reindexOnly == null ? List.of() : List.of( reindexOnly ) ) + .setReadOnly( true ) + .setCacheable( false ) + .setLockMode( LockModeType.NONE ) .uniqueResult(); - - return query.getSingleResult(); } } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/spi/EntityReader.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/spi/EntityIdReader.java similarity index 55% rename from mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/spi/EntityReader.java rename to mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/spi/EntityIdReader.java index 3f5d7936db6..e33a9f783a9 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/spi/EntityReader.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/step/spi/EntityIdReader.java @@ -10,6 +10,7 @@ import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.util.ArrayList; +import java.util.List; import jakarta.batch.api.BatchProperty; import jakarta.batch.api.chunk.AbstractItemReader; @@ -19,41 +20,37 @@ import jakarta.inject.Named; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.LockModeType; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import org.hibernate.CacheMode; -import org.hibernate.FlushMode; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; -import org.hibernate.Session; -import org.hibernate.query.Query; +import org.hibernate.StatelessSession; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.SelectionQuery; import org.hibernate.search.jakarta.batch.core.context.jpa.spi.EntityManagerFactoryRegistry; import org.hibernate.search.jakarta.batch.core.inject.scope.spi.HibernateSearchPartitionScoped; import org.hibernate.search.jakarta.batch.core.logging.impl.Log; import org.hibernate.search.jakarta.batch.core.massindexing.MassIndexingJobParameters; import org.hibernate.search.jakarta.batch.core.massindexing.impl.JobContextData; import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.HibernateSearchPartitionMapper; -import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.IndexScope; import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.PartitionContextData; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.EntityTypeDescriptor; -import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.IdOrder; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.JobContextUtil; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.MassIndexingPartitionProperties; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.PartitionBound; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.PersistenceUtil; import org.hibernate.search.jakarta.batch.core.massindexing.util.impl.SerializationUtil; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; import org.hibernate.search.util.common.impl.Closer; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** - * Entity reader reads entities from database. During the open of the read stream, this reader builds a scrollable - * result. Then, it scrolls from one entity to another at each reading. An entity reader reaches its end when there’s no - * more item to read. Each reader contains only one entity type. + * Reads entity identifiers from the database. *

- * The reading range is restricted by the {@link PartitionBound}, which always represents as a left-closed interval. + * This reader builds a scroll and outputs IDs from that scroll. + *

+ * Each reader pertains to only one entity type. + *

+ * The reading range is restricted by the {@link PartitionBound}, which always represents a left-closed interval. * See {@link HibernateSearchPartitionMapper} for more information about these bounds. * * @author Mincong Huang @@ -61,7 +58,7 @@ // Same hack as in JobContextSetupListener. @Named(value = "org.hibernate.search.jsr352.massindexing.impl.steps.lucene.EntityReader") @HibernateSearchPartitionScoped -public class EntityReader extends AbstractItemReader { +public class EntityIdReader extends AbstractItemReader { private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); @@ -86,29 +83,21 @@ public class EntityReader extends AbstractItemReader { @Inject private StepContext stepContext; - @Inject - @BatchProperty(name = MassIndexingJobParameters.CACHE_MODE) - private String serializedCacheMode; - @Inject @BatchProperty(name = MassIndexingPartitionProperties.ENTITY_NAME) private String entityName; @Inject - @BatchProperty(name = MassIndexingJobParameters.ENTITY_FETCH_SIZE) - private String serializedEntityFetchSize; + @BatchProperty(name = MassIndexingJobParameters.ID_FETCH_SIZE) + private String serializedIdFetchSize; @Inject - @BatchProperty(name = MassIndexingJobParameters.CHECKPOINT_INTERVAL) - private String serializedCheckpointInterval; + @BatchProperty(name = MassIndexingJobParameters.REINDEX_ONLY_HQL) + private String reindexOnlyHql; @Inject - @BatchProperty(name = MassIndexingJobParameters.SESSION_CLEAR_INTERVAL) - private String serializedSessionClearInterval; - - @Inject - @BatchProperty(name = MassIndexingJobParameters.CUSTOM_QUERY_HQL) - private String customQueryHql; + @BatchProperty(name = MassIndexingJobParameters.REINDEX_ONLY_PARAMETERS) + private String serializedReindexOnlyParameters; @Inject @BatchProperty(name = MassIndexingJobParameters.MAX_RESULTS_PER_ENTITY) @@ -130,42 +119,38 @@ public class EntityReader extends AbstractItemReader { @BatchProperty(name = MassIndexingPartitionProperties.UPPER_BOUND) private String serializedUpperBound; - @Inject - @BatchProperty(name = MassIndexingPartitionProperties.INDEX_SCOPE) - private String indexScopeName; - - private JobContextData jobData; private EntityManagerFactory emf; + private EntityTypeDescriptor type; + + private int idFetchSize; + private Integer maxResults; + private ConditionalExpression reindexOnly; + private Object upperBound; + private Object lowerBound; private ChunkState chunkState; - public EntityReader() { + public EntityIdReader() { } - public EntityReader(String serializedCacheMode, - String entityName, - String serializedEntityFetchSize, - String serializedCheckpointInterval, - String serializedSessionClearInterval, - String hql, + public EntityIdReader(String entityName, + String serializedIdFetchSize, + String reindexOnlyHql, + String serializedReindexOnlyParameters, String serializedMaxResultsPerEntity, String partitionIdStr, String serializedLowerBound, String serializedUpperBound, - String indexScopeName, JobContext jobContext, StepContext stepContext) { - this.serializedCacheMode = serializedCacheMode; this.entityName = entityName; - this.serializedEntityFetchSize = serializedEntityFetchSize; - this.serializedCheckpointInterval = serializedCheckpointInterval; - this.serializedSessionClearInterval = serializedSessionClearInterval; - this.customQueryHql = hql; + this.serializedIdFetchSize = serializedIdFetchSize; + this.reindexOnlyHql = reindexOnlyHql; + this.serializedReindexOnlyParameters = serializedReindexOnlyParameters; this.serializedMaxResultsPerEntity = serializedMaxResultsPerEntity; this.serializedPartitionId = partitionIdStr; this.serializedLowerBound = serializedLowerBound; this.serializedUpperBound = serializedUpperBound; - this.indexScopeName = indexScopeName; this.jobContext = jobContext; this.stepContext = stepContext; } @@ -181,53 +166,34 @@ public EntityReader(String serializedCacheMode, public void open(Serializable checkpointInfo) throws IOException, ClassNotFoundException { log.openingReader( serializedPartitionId, entityName ); - final int partitionId = - SerializationUtil.parseIntegerParameter( MassIndexingPartitionProperties.PARTITION_ID, serializedPartitionId ); - boolean isRestarted = checkpointInfo != null; - - jobData = getOrCreateJobContextData(); + JobContextData jobData = getOrCreateJobContextData(); emf = jobData.getEntityManagerFactory(); + type = jobData.getEntityTypeDescriptor( entityName ); - PartitionContextData partitionData; - IndexScope indexScope = IndexScope.valueOf( indexScopeName ); - CacheMode cacheMode = SerializationUtil.parseCacheModeParameter( - MassIndexingJobParameters.CACHE_MODE, serializedCacheMode, MassIndexingJobParameters.Defaults.CACHE_MODE - ); - int checkpointInterval = SerializationUtil.parseIntegerParameter( - MassIndexingJobParameters.CHECKPOINT_INTERVAL, serializedCheckpointInterval - ); - Integer sessionClearIntervalRaw = SerializationUtil.parseIntegerParameterOptional( - MassIndexingJobParameters.SESSION_CLEAR_INTERVAL, serializedSessionClearInterval, null ); - int sessionClearInterval = - MassIndexingJobParameters.Defaults.sessionClearInterval( sessionClearIntervalRaw, checkpointInterval ); - int entityFetchSize = SerializationUtil.parseIntegerParameterOptional( - MassIndexingJobParameters.ENTITY_FETCH_SIZE, serializedEntityFetchSize, sessionClearInterval + idFetchSize = SerializationUtil.parseIntegerParameterOptional( + MassIndexingJobParameters.ID_FETCH_SIZE, serializedIdFetchSize, + MassIndexingJobParameters.Defaults.ID_FETCH_SIZE ); - Integer maxResults = SerializationUtil.parseIntegerParameterOptional( + reindexOnly = SerializationUtil.parseReindexOnlyParameters( reindexOnlyHql, serializedReindexOnlyParameters ); + maxResults = SerializationUtil.parseIntegerParameterOptional( MassIndexingJobParameters.MAX_RESULTS_PER_ENTITY, serializedMaxResultsPerEntity, null ); - FetchingStrategy fetchingStrategy; - switch ( indexScope ) { - case HQL: - fetchingStrategy = createHqlFetchingStrategy( cacheMode, entityFetchSize, maxResults ); - break; - - case FULL_ENTITY: - fetchingStrategy = createCriteriaFetchingStrategy( cacheMode, entityFetchSize, maxResults ); - break; - - default: - // This should never happen. - throw new IllegalStateException( "Unknown value from enum: " + IndexScope.class ); - } - chunkState = new ChunkState( emf, tenantId, fetchingStrategy, sessionClearInterval, checkpointInfo ); + upperBound = SerializationUtil.deserialize( serializedUpperBound ); + lowerBound = SerializationUtil.deserialize( serializedLowerBound ); + + chunkState = new ChunkState( checkpointInfo ); + PartitionContextData partitionData; + boolean isRestarted = checkpointInfo != null; if ( isRestarted ) { partitionData = (PartitionContextData) stepContext.getPersistentUserData(); } else { + final int partitionId = + SerializationUtil.parseIntegerParameter( MassIndexingPartitionProperties.PARTITION_ID, + serializedPartitionId ); partitionData = new PartitionContextData( partitionId, entityName ); } @@ -249,17 +215,17 @@ public void close() { } /** - * Read item from database using JPA. Each read, there will be only one entity fetched. + * Read item from database using JPA. Each read, there will be only one identifier fetched. */ @Override public Object readItem() { - log.readingEntity(); + log.readingEntityId(); - Object entity = chunkState.next(); - if ( entity == null ) { + Object id = chunkState.next(); + if ( id == null ) { log.noMoreResults(); } - return entity; + return id; } /** @@ -290,136 +256,15 @@ private JobContextData getOrCreateJobContextData() { ); } - /** - * Create a {@link FetchingStrategy} that creates scrolls from HQL - * and uses the last item's index in the result list as a checkpoint ID. - *

- * We use the item index as a checkpoint ID only because it's the only solution, - * given that we cannot modify the HQL to dynamically add a constraint based on the last - * treated element. - */ - private FetchingStrategy createHqlFetchingStrategy( - CacheMode cacheMode, int entityFetchSize, Integer maxResults) { - String hql = customQueryHql; - - return (session, lastCheckpointInfo) -> { - Query query = session.createQuery( hql, Object.class ); - - if ( lastCheckpointInfo != null ) { - query.setFirstResult( lastCheckpointInfo.getProcessedEntityCount() ); - } - - if ( maxResults != null ) { - int remaining; - if ( lastCheckpointInfo != null ) { - remaining = maxResults - lastCheckpointInfo.getProcessedEntityCount(); - } - else { - remaining = maxResults; - } - query.setMaxResults( remaining ); - } - - return query.setReadOnly( true ) - .setCacheable( false ) - .setLockMode( LockModeType.NONE ) - .setHibernateFlushMode( FlushMode.MANUAL ) - .setCacheMode( cacheMode ) - .setFetchSize( entityFetchSize ) - // The FORWARD_ONLY mode is not enough for PostgreSQL when using setFirstResult - .scroll( ScrollMode.SCROLL_SENSITIVE ); - }; - } - - /** - * Create a {@link FetchingStrategy} that creates scrolls from criteria - * and uses the last returned entity's ID as a checkpoint ID. - */ - private FetchingStrategy createCriteriaFetchingStrategy( - CacheMode cacheMode, int entityFetchSize, Integer maxResults) - throws IOException, ClassNotFoundException { - Class entityType = jobData.getEntityType( entityName ); - Object upperBound = SerializationUtil.deserialize( serializedUpperBound ); - Object lowerBound = SerializationUtil.deserialize( serializedLowerBound ); - - EntityTypeDescriptor typeDescriptor = jobData.getEntityTypeDescriptor( entityType ); - IdOrder idOrder = typeDescriptor.getIdOrder(); - - return (session, lastCheckpointInfo) -> { - CriteriaBuilder builder = session.getCriteriaBuilder(); - CriteriaQuery criteria = builder.createQuery( entityType ); - Root root = criteria.from( entityType ); - - // build orders for this entity - idOrder.addAscOrder( builder, criteria, root ); - - ArrayList predicates = new ArrayList<>( 2 ); - - // build criteria using bounds - if ( upperBound != null ) { - predicates.add( idOrder.idLesser( builder, root, upperBound ) ); - } - if ( lastCheckpointInfo != null ) { - predicates.add( idOrder.idGreater( builder, root, lastCheckpointInfo.getLastProcessedEntityId() ) ); - } - else if ( lowerBound != null ) { - predicates.add( idOrder.idGreaterOrEqual( builder, root, lowerBound ) ); - } - - if ( !predicates.isEmpty() ) { - criteria.where( predicates.toArray( new Predicate[predicates.size()] ) ); - } - - Query query = session.createQuery( criteria ); - - if ( maxResults != null ) { - int remaining; - if ( lastCheckpointInfo != null ) { - remaining = maxResults - lastCheckpointInfo.getProcessedEntityCount(); - } - else { - remaining = maxResults; - } - query.setMaxResults( remaining ); - } - - return query - .setReadOnly( true ) - .setCacheable( false ) - .setLockMode( LockModeType.NONE ) - .setCacheMode( cacheMode ) - .setHibernateFlushMode( FlushMode.MANUAL ) - .setFetchSize( entityFetchSize ) - .scroll( ScrollMode.FORWARD_ONLY ); - }; - } - - private interface FetchingStrategy { - - ScrollableResults createScroll(Session session, CheckpointInfo lastCheckpointInfo); - - } - - private static class ChunkState implements AutoCloseable { - private final EntityManagerFactory emf; - private final String tenantId; - private final FetchingStrategy fetchingStrategy; - private final int clearInterval; - - private Session session; + private class ChunkState implements AutoCloseable { + private StatelessSession session; private ScrollableResults scroll; private CheckpointInfo lastCheckpointInfo; private int processedEntityCount = 0; private Object lastProcessedEntityId; - public ChunkState( - EntityManagerFactory emf, String tenantId, FetchingStrategy fetchingStrategy, int clearInterval, - Serializable checkpointInfo) { - this.emf = emf; - this.tenantId = tenantId; - this.fetchingStrategy = fetchingStrategy; - this.clearInterval = clearInterval; + public ChunkState(Serializable checkpointInfo) { this.lastCheckpointInfo = (CheckpointInfo) checkpointInfo; } @@ -431,21 +276,13 @@ public Object next() { if ( scroll == null ) { start(); } - // Mind the "else": we don't clear a session we just created. - else if ( processedEntityCount % clearInterval == 0 ) { - /* - * This must be executed before we extract the entity, - * because the returned entity must be attached to the session. - */ - session.clear(); - } if ( !scroll.next() ) { return null; } - Object entity = scroll.get(); - lastProcessedEntityId = session.getIdentifier( entity ); + Object id = scroll.get(); + lastProcessedEntityId = id; ++processedEntityCount; - return entity; + return id; } /** @@ -479,16 +316,16 @@ public void close() { scroll = null; } if ( session != null ) { - closer.push( Session::close, session ); + closer.push( StatelessSession::close, session ); session = null; } } } private void start() { - session = PersistenceUtil.openSession( emf, tenantId ); + session = PersistenceUtil.openStatelessSession( emf, tenantId ); try { - scroll = fetchingStrategy.createScroll( session, lastCheckpointInfo ); + scroll = createScroll( type, session ); } catch (Throwable t) { try { @@ -501,6 +338,42 @@ private void start() { } } + private ScrollableResults createScroll(EntityTypeDescriptor type, StatelessSession session) { + List conditions = new ArrayList<>(); + if ( reindexOnly != null ) { + conditions.add( reindexOnly ); + } + if ( upperBound != null ) { + conditions.add( type.idOrder().idLesser( "HIBERNATE_SEARCH_PARTITION_UPPER_BOUND_", upperBound ) ); + } + if ( lastCheckpointInfo != null ) { + conditions.add( type.idOrder().idGreater( "HIBERNATE_SEARCH_LAST_CHECKPOINT_", + lastCheckpointInfo.getLastProcessedEntityId() ) ); + } + else if ( lowerBound != null ) { + conditions.add( type.idOrder().idGreaterOrEqual( "HIBERNATE_SEARCH_PARTITION_LOWER_BOUND_", lowerBound ) ); + } + + SelectionQuery query = type.createIdentifiersQuery( (SharedSessionContractImplementor) session, conditions ); + + if ( maxResults != null ) { + int remaining; + if ( lastCheckpointInfo != null ) { + remaining = maxResults - lastCheckpointInfo.getProcessedEntityCount(); + } + else { + remaining = maxResults; + } + query.setMaxResults( remaining ); + } + + return query + .setReadOnly( true ) + .setCacheable( false ) + .setLockMode( LockModeType.NONE ) + .setFetchSize( idFetchSize ) + .scroll( ScrollMode.FORWARD_ONLY ); + } } private static class CheckpointInfo implements Serializable { diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/CompositeIdOrder.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/CompositeIdOrder.java index 9a7a0d16257..8231a362493 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/CompositeIdOrder.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/CompositeIdOrder.java @@ -7,19 +7,18 @@ package org.hibernate.search.jakarta.batch.core.massindexing.util.impl; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.function.BiFunction; import jakarta.persistence.EmbeddedId; import jakarta.persistence.IdClass; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Order; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; /** * Order over multiple ID attributes. @@ -45,91 +44,110 @@ * @author Mincong Huang * @author Yoann Rodiere */ -public class CompositeIdOrder implements IdOrder { +public class CompositeIdOrder implements IdOrder { - private final EntityIdentifierMapping mapping; - private final EmbeddableMappingType mappingType; + private final EntityIdentifierMapping idMapping; + private final EmbeddableMappingType idMappingType; - public CompositeIdOrder(EntityIdentifierMapping mapping, EmbeddableMappingType mappingType) { - this.mapping = mapping; - this.mappingType = mappingType; + public CompositeIdOrder(LoadingTypeContext type) { + this.idMapping = type.entityMappingType().getIdentifierMapping(); + this.idMappingType = (EmbeddableMappingType) idMapping.getPartMappingType(); } @Override - @SuppressWarnings("unchecked") - public Predicate idGreater(CriteriaBuilder builder, Root root, Object idObj) { - BiFunction strictOperator = - (String path, Object obj) -> builder.greaterThan( root.get( path ), (Comparable) idObj ); - return restrictLexicographically( strictOperator, builder, root, idObj, false ); + public ConditionalExpression idGreater(String paramNamePrefix, Object idObj) { + return restrictLexicographically( paramNamePrefix, idObj, ">", false ); } @Override - @SuppressWarnings("unchecked") - public Predicate idGreaterOrEqual(CriteriaBuilder builder, Root root, Object idObj) { - /* - * Caution, using Restrictions::ge here won't cut it, we really need - * to separate the strict operator from the equals. - */ - BiFunction strictOperator = - (String path, Object obj) -> builder.greaterThan( root.get( path ), (Comparable) idObj ); - return restrictLexicographically( strictOperator, builder, root, idObj, true ); + public ConditionalExpression idGreaterOrEqual(String paramNamePrefix, Object idObj) { + // Caution, using ">=" here won't cut it, we really need to separate the strict operator from the equals. + return restrictLexicographically( paramNamePrefix, idObj, ">", true ); } @Override - @SuppressWarnings("unchecked") - public Predicate idLesser(CriteriaBuilder builder, Root root, Object idObj) { - BiFunction strictOperator = - (String path, Object obj) -> builder.lessThan( root.get( path ), (Comparable) idObj ); - return restrictLexicographically( strictOperator, builder, root, idObj, false ); + public ConditionalExpression idLesser(String paramNamePrefix, Object idObj) { + return restrictLexicographically( paramNamePrefix, idObj, "<", false ); } @Override - public void addAscOrder(CriteriaBuilder builder, CriteriaQuery criteria, Root root) { - ArrayList orders = new ArrayList<>(); - mapping.forEachSelectable( - (i, selectable) -> orders.add( builder.asc( root.get( selectable.getSelectablePath().getFullPath() ) ) ) ); - criteria.orderBy( orders ); + public String ascOrder() { + StringBuilder builder = new StringBuilder(); + idMappingType.forEachSubPart( (i, subPart) -> { + if ( builder.length() != 0 ) { + builder.append( ", " ); + } + toPath( builder, subPart.getNavigableRole() ); + builder.append( " asc" ); + } ); + return builder.toString(); } - private Predicate restrictLexicographically(BiFunction strictOperator, - CriteriaBuilder builder, Root root, Object idObj, boolean orEquals) { - Object[] selectableValues = mappingType.getValues( idObj ); - int selectablesSize = selectableValues.length; - - List or = new ArrayList<>(); + private ConditionalExpression restrictLexicographically(String paramNamePrefix, Object idObj, + String strictOperator, boolean orEquals) { + List orClauses = new ArrayList<>(); - mapping.forEachSelectable( (i, selectable) -> { + idMappingType.forEachSubPart( (i, subPart) -> { // Group expressions together in a single conjunction (A and B and C...). - Predicate[] and = new Predicate[i + 1]; + String[] andClauses = new String[i + 1]; - mapping.forEachSelectable( (j, previousSelectable) -> { + idMappingType.forEachSubPart( (j, previousSubPart) -> { if ( j < i ) { // The first N-1 expressions have symbol `=` - String path = previousSelectable.getSelectablePath().getFullPath(); - Object val = selectableValues[j]; - and[j] = builder.equal( root.get( path ), val ); + andClauses[j] = toPath( previousSubPart ) + " = :" + paramNamePrefix + j; } } ); + // The last expression has whatever symbol is defined by "strictOperator" - String path = selectable.getSelectablePath().getFullPath(); - Object val = selectableValues[i]; - and[i] = strictOperator.apply( path, val ); + andClauses[i] = toPath( subPart ) + " " + strictOperator + " :" + paramNamePrefix + i; - or.add( builder.and( and ) ); + orClauses.add( junction( Arrays.asList( andClauses ), " and " ) ); } ); + if ( orEquals ) { - Predicate[] and = new Predicate[selectablesSize]; - mapping.forEachSelectable( (i, previousSelectable) -> { - String path = previousSelectable.getSelectablePath().getFullPath(); - Object val = selectableValues[i]; - and[i] = builder.equal( root.get( path ), val ); - } ); - or.add( builder.and( and ) ); + List andClauses = new ArrayList<>(); + idMappingType.forEachSubPart( (i, subPart) -> andClauses.add( toPath( subPart ) + " = :" + paramNamePrefix + i ) ); + orClauses.add( junction( andClauses, " and " ) ); + } + + var expression = new ConditionalExpression( junction( orClauses, " or " ) ); + Object[] selectableValues = idMappingType.getValues( idObj ); + for ( int i = 0; i < selectableValues.length; i++ ) { + expression.param( paramNamePrefix + i, selectableValues[i] ); } + return expression; + } - // Group the disjunction of multiple expressions (X or Y or Z...). - return builder.or( or.toArray( new Predicate[0] ) ); + private String toPath(ModelPart subPart) { + StringBuilder builder = new StringBuilder(); + toPath( builder, subPart.getNavigableRole() ); + return builder.toString(); } + private void toPath(StringBuilder builder, NavigableRole role) { + if ( role == idMappingType.getNavigableRole() ) { + builder.append( idMapping.getAttributeName() ); + } + else { + toPath( builder, role.getParent() ); + builder.append( "." ); + builder.append( role.getLocalName() ); + } + } + + private String junction(List clauses, String operator) { + StringBuilder junctionBuilder = new StringBuilder(); + boolean first = true; + for ( String clause : clauses ) { + if ( first ) { + first = false; + } + else { + junctionBuilder.append( operator ); + } + junctionBuilder.append( "(" ).append( clause ).append( ")" ); + } + return junctionBuilder.toString(); + } } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/EntityTypeDescriptor.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/EntityTypeDescriptor.java index f747d5f50f4..d966ed88ac9 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/EntityTypeDescriptor.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/EntityTypeDescriptor.java @@ -6,28 +6,76 @@ */ package org.hibernate.search.jakarta.batch.core.massindexing.util.impl; +import java.util.List; -/** - * @author Yoann Rodiere - */ -public class EntityTypeDescriptor { +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.query.SelectionQuery; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmQueryLoader; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; +import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; + +public class EntityTypeDescriptor { - private final Class javaClass; + public static EntityTypeDescriptor create(LoadingTypeContext type) { + EntityIdentifierMapping identifierMapping = type.entityMappingType().getIdentifierMapping(); + IdOrder idOrder; + if ( identifierMapping.getPartMappingType() instanceof EmbeddableMappingType ) { + idOrder = new CompositeIdOrder<>( type ); + } + else { + idOrder = new SingularIdOrder<>( type ); + } + return new EntityTypeDescriptor<>( type, type.loadingStrategy(), idOrder ); + } + private final LoadingTypeContext delegate; + private final HibernateOrmEntityLoadingStrategy loadingStrategy; private final IdOrder idOrder; - public EntityTypeDescriptor(Class javaClass, IdOrder idOrder) { - super(); - this.javaClass = javaClass; + public EntityTypeDescriptor(LoadingTypeContext delegate, + HibernateOrmEntityLoadingStrategy loadingStrategy, IdOrder idOrder) { + this.delegate = delegate; + this.loadingStrategy = loadingStrategy; this.idOrder = idOrder; } - public Class getJavaClass() { - return javaClass; + public PojoRawTypeIdentifier typeIdentifier() { + return delegate.typeIdentifier(); + } + + public Class javaClass() { + return delegate.typeIdentifier().javaClass(); + } + + public String jpaEntityName() { + return delegate.jpaEntityName(); } - public IdOrder getIdOrder() { + public IdOrder idOrder() { return idOrder; } + public SelectionQuery createCountQuery(SharedSessionContractImplementor session, + List conditions) { + return queryLoader( conditions, null ).createCountQuery( session ); + } + + public SelectionQuery createIdentifiersQuery(SharedSessionContractImplementor session, + List conditions) { + return queryLoader( conditions, idOrder.ascOrder() ).createIdentifiersQuery( session ); + } + + public SelectionQuery createLoadingQuery(SessionImplementor session, String idParameterName) { + return queryLoader( List.of(), null ).createLoadingQuery( session, idParameterName ); + } + + private HibernateOrmQueryLoader queryLoader(List conditions, String order) { + return loadingStrategy.createQueryLoader( List.of( delegate ), conditions, order ); + } + } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/IdOrder.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/IdOrder.java index 28949aa29f2..1a4cf66fba7 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/IdOrder.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/IdOrder.java @@ -6,10 +6,7 @@ */ package org.hibernate.search.jakarta.batch.core.massindexing.util.impl; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; /** * Provides ID-based, order-sensitive restrictions @@ -22,23 +19,26 @@ public interface IdOrder { /** - * @param idObj The ID all results should be greater than. - * @return A "greater than" restriction on the ID. + * @param paramNamePrefix A unique prefix for the name of parameters added by the resulting expression. + * @param idObj The ID all results should be lesser than. + * @return A "strictly greater than" restriction on the ID. */ - Predicate idGreater(CriteriaBuilder builder, Root root, Object idObj); + ConditionalExpression idGreater(String paramNamePrefix, Object idObj); /** - * @param idObj The ID all results should be greater than or equal to. + * @param paramNamePrefix A unique prefix for the name of parameters added by the resulting expression. + * @param idObj The ID all results should be lesser than. * @return A "greater or equal" restriction on the ID. */ - Predicate idGreaterOrEqual(CriteriaBuilder builder, Root root, Object idObj); + ConditionalExpression idGreaterOrEqual(String paramNamePrefix, Object idObj); /** + * @param paramNamePrefix A unique prefix for the name of parameters added by the resulting expression. * @param idObj The ID all results should be lesser than. * @return A "lesser than" restriction on the ID. */ - Predicate idLesser(CriteriaBuilder builder, Root root, Object idObj); + ConditionalExpression idLesser(String paramNamePrefix, Object idObj); - void addAscOrder(CriteriaBuilder builder, CriteriaQuery criteria, Root root); + String ascOrder(); } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/JobContextUtil.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/JobContextUtil.java index 9e65b1da2c0..9b87df8433e 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/JobContextUtil.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/JobContextUtil.java @@ -20,7 +20,8 @@ import org.hibernate.search.jakarta.batch.core.logging.impl.Log; import org.hibernate.search.jakarta.batch.core.massindexing.impl.JobContextData; import org.hibernate.search.mapper.orm.Search; -import org.hibernate.search.mapper.orm.mapping.SearchMapping; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; +import org.hibernate.search.mapper.orm.spi.BatchMappingContext; import org.hibernate.search.util.common.impl.StringHelper; import org.hibernate.search.util.common.logging.impl.LoggerFactory; @@ -80,15 +81,15 @@ static EntityManagerFactory getEntityManagerFactory(EntityManagerFactoryRegistry } private static JobContextData createData(EntityManagerFactory emf, String entityTypes) { - SearchMapping mapping = Search.mapping( emf ); + BatchMappingContext mapping = (BatchMappingContext) Search.mapping( emf ); List entityNamesToIndex = Arrays.asList( entityTypes.split( "," ) ); - Set> entityTypesToIndex = new LinkedHashSet<>(); + Set> entityTypesToIndex = new LinkedHashSet<>(); for ( String s : entityNamesToIndex ) { - entityTypesToIndex.add( mapping.indexedEntity( s ).javaClass() ); + entityTypesToIndex.add( mapping.typeContextProvider().byEntityName().getOrFail( s ) ); } - List descriptors = PersistenceUtil.createDescriptors( emf, entityTypesToIndex ); + List> descriptors = PersistenceUtil.createDescriptors( entityTypesToIndex ); JobContextData jobContextData = new JobContextData(); jobContextData.setEntityManagerFactory( emf ); diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/MassIndexingPartitionProperties.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/MassIndexingPartitionProperties.java index 8bfb4bd0d25..3c97e72c026 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/MassIndexingPartitionProperties.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/MassIndexingPartitionProperties.java @@ -27,5 +27,4 @@ private MassIndexingPartitionProperties() { public static final String UPPER_BOUND = "upperBound"; - public static final String INDEX_SCOPE = "indexScope"; } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PartitionBound.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PartitionBound.java index a964803295b..119b07ed2dc 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PartitionBound.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PartitionBound.java @@ -6,8 +6,6 @@ */ package org.hibernate.search.jakarta.batch.core.massindexing.util.impl; -import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.IndexScope; - /** * Information about a target partition which can not be stored in the partition properties as String values. In * particular, the boundary properties help us to identify the lower boundary and upper boundary of a given partition, @@ -18,24 +16,18 @@ */ public class PartitionBound { - private Class entityType; + private EntityTypeDescriptor entityType; private Object lowerBound; private Object upperBound; - private IndexScope indexScope; - public PartitionBound(Class entityType, Object lowerBound, Object upperBound, IndexScope indexScope) { + public PartitionBound(EntityTypeDescriptor entityType, Object lowerBound, Object upperBound) { this.entityType = entityType; this.lowerBound = lowerBound; this.upperBound = upperBound; - this.indexScope = indexScope; - } - - public IndexScope getIndexScope() { - return indexScope; } public String getEntityName() { - return entityType.getName(); + return entityType.jpaEntityName(); } public Object getLowerBound() { @@ -46,17 +38,9 @@ public Object getUpperBound() { return upperBound; } - public boolean hasUpperBound() { - return upperBound != null; - } - - public boolean hasLowerBound() { - return lowerBound != null; - } - @Override public String toString() { return "PartitionBound [entityType=" + entityType + ", lowerBound=" + lowerBound + ", upperBound=" + upperBound - + ", indexScope=" + indexScope + "]"; + + "]"; } } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PersistenceUtil.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PersistenceUtil.java index 510981b4412..e0f60d8cb65 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PersistenceUtil.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/PersistenceUtil.java @@ -18,12 +18,7 @@ import org.hibernate.SessionFactory; import org.hibernate.StatelessSession; import org.hibernate.StatelessSessionBuilder; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.MappingMetamodel; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; -import org.hibernate.metamodel.mapping.EntityIdentifierMapping; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.search.jakarta.batch.core.massindexing.step.impl.IndexScope; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; import org.hibernate.search.util.common.impl.StringHelper; /** @@ -79,45 +74,12 @@ public static StatelessSession openStatelessSession(EntityManagerFactory entityM return builder.openStatelessSession(); } - /** - * Determines the index scope using the input parameters. - * - * @see IndexScope - */ - public static IndexScope getIndexScope(String hql) { - if ( StringHelper.isNotEmpty( hql ) ) { - return IndexScope.HQL; - } - else { - return IndexScope.FULL_ENTITY; - } - } - - public static List createDescriptors(EntityManagerFactory entityManagerFactory, Set> types) { - SessionFactoryImplementor sessionFactory = entityManagerFactory.unwrap( SessionFactoryImplementor.class ); - List result = new ArrayList<>( types.size() ); - MappingMetamodel metamodel = sessionFactory.getMappingMetamodel(); - for ( Class type : types ) { - result.add( createDescriptor( metamodel, type ) ); + public static List> createDescriptors(Set> types) { + List> result = new ArrayList<>( types.size() ); + for ( LoadingTypeContext type : types ) { + result.add( EntityTypeDescriptor.create( type ) ); } return result; } - private static EntityTypeDescriptor createDescriptor(MappingMetamodel metamodel, Class type) { - EntityMappingType entityMappingType = metamodel.findEntityDescriptor( type ); - IdOrder idOrder = createIdOrder( entityMappingType ); - return new EntityTypeDescriptor( type, idOrder ); - } - - private static IdOrder createIdOrder(EntityMappingType entityMappingType) { - EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping(); - if ( identifierMapping.getPartMappingType() instanceof EmbeddableMappingType ) { - return new CompositeIdOrder( identifierMapping, - (EmbeddableMappingType) identifierMapping.getPartMappingType() ); - } - else { - return new SingularIdOrder( identifierMapping.getAttributeName() ); - } - } - } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SerializationUtil.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SerializationUtil.java index 8ca8e29f006..b666d67fc82 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SerializationUtil.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SerializationUtil.java @@ -14,9 +14,11 @@ import java.lang.invoke.MethodHandles; import java.util.Base64; import java.util.Locale; +import java.util.Map; import org.hibernate.CacheMode; import org.hibernate.search.jakarta.batch.core.logging.impl.Log; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; import org.hibernate.search.util.common.SearchException; import org.hibernate.search.util.common.impl.StringHelper; import org.hibernate.search.util.common.logging.impl.LoggerFactory; @@ -110,4 +112,21 @@ private static > T parseEnumParameter(Class clazz, String k } } + public static ConditionalExpression parseReindexOnlyParameters(String reindexOnlyHql, + String serializedReindexOnlyParameters) + throws IOException, ClassNotFoundException { + if ( reindexOnlyHql == null ) { + return null; + } + else { + ConditionalExpression reindexOnly = new ConditionalExpression( reindexOnlyHql ); + @SuppressWarnings("unchecked") + Map params = (Map) SerializationUtil.deserialize( serializedReindexOnlyParameters ); + if ( params != null ) { + params.forEach( reindexOnly::param ); + } + return reindexOnly; + } + + } } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SingularIdOrder.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SingularIdOrder.java index 8fafd997586..69c0715ac03 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SingularIdOrder.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/SingularIdOrder.java @@ -6,10 +6,8 @@ */ package org.hibernate.search.jakarta.batch.core.massindexing.util.impl; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; /** * Order over a single ID attribute. @@ -19,35 +17,39 @@ * @author Mincong Huang * @author Yoann Rodiere */ -public class SingularIdOrder implements IdOrder { +public class SingularIdOrder implements IdOrder { private final String idPropertyName; - public SingularIdOrder(String idPropertyName) { - this.idPropertyName = idPropertyName; + public SingularIdOrder(LoadingTypeContext type) { + this.idPropertyName = type.entityMappingType().getIdentifierMapping().getAttributeName(); } @Override - @SuppressWarnings("unchecked") // Can't do better without addings generics for the ID type everywhere - public Predicate idGreater(CriteriaBuilder builder, Root root, Object idObj) { - return builder.greaterThan( root.get( idPropertyName ), (Comparable) idObj ); + public ConditionalExpression idGreater(String paramNamePrefix, Object idObj) { + return restrict( paramNamePrefix, ">", idObj ); } @Override - @SuppressWarnings("unchecked") // Can't do better without addings generics for the ID type everywhere - public Predicate idGreaterOrEqual(CriteriaBuilder builder, Root root, Object idObj) { - return builder.greaterThanOrEqualTo( root.get( idPropertyName ), (Comparable) idObj ); + public ConditionalExpression idGreaterOrEqual(String paramNamePrefix, Object idObj) { + return restrict( paramNamePrefix, ">=", idObj ); } @Override - @SuppressWarnings("unchecked") // Can't do better without addings generics for the ID type everywhere - public Predicate idLesser(CriteriaBuilder builder, Root root, Object idObj) { - return builder.lessThan( root.get( idPropertyName ), (Comparable) idObj ); + public ConditionalExpression idLesser(String paramNamePrefix, Object idObj) { + return restrict( paramNamePrefix, "<", idObj ); } @Override - public void addAscOrder(CriteriaBuilder builder, CriteriaQuery criteria, Root root) { - criteria.orderBy( builder.asc( root.get( idPropertyName ) ) ); + public String ascOrder() { + return idPropertyName + " asc"; + } + + private ConditionalExpression restrict(String paramNamePrefix, String operator, Object idObj) { + String paramName = paramNamePrefix + "REF"; + var expression = new ConditionalExpression( idPropertyName + " " + operator + " :" + paramName ); + expression.param( paramName, idObj ); + return expression; } } diff --git a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/ValidationUtil.java b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/ValidationUtil.java index 8c63920c9e0..14922870a60 100644 --- a/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/ValidationUtil.java +++ b/mapper/orm-jakarta-batch/core/src/main/java/org/hibernate/search/jakarta/batch/core/massindexing/util/impl/ValidationUtil.java @@ -38,9 +38,9 @@ public static void validateCheckpointInterval(int checkpointInterval, int rowsPe } } - public static void validateSessionClearInterval(int sessionClearInterval, int checkpointInterval) { - if ( sessionClearInterval > checkpointInterval ) { - throw log.illegalSessionClearInterval( sessionClearInterval, checkpointInterval ); + public static void validateEntityFetchSize(int entityFetchSize, int checkpointInterval) { + if ( entityFetchSize > checkpointInterval ) { + throw log.illegalEntityFetchSize( entityFetchSize, checkpointInterval ); } } diff --git a/mapper/orm-jakarta-batch/core/src/main/resources/META-INF/batch-jobs/hibernate-search-mass-indexing.xml b/mapper/orm-jakarta-batch/core/src/main/resources/META-INF/batch-jobs/hibernate-search-mass-indexing.xml index 1721f70548d..b5ab66ce065 100644 --- a/mapper/orm-jakarta-batch/core/src/main/resources/META-INF/batch-jobs/hibernate-search-mass-indexing.xml +++ b/mapper/orm-jakarta-batch/core/src/main/resources/META-INF/batch-jobs/hibernate-search-mass-indexing.xml @@ -26,8 +26,8 @@ - - + + @@ -53,7 +53,7 @@ - + @@ -67,12 +67,11 @@ - - + - - + + @@ -81,6 +80,10 @@ + + + + @@ -89,7 +92,8 @@ - + + diff --git a/mapper/orm-jakarta-batch/core/src/test/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParametersBuilderTest.java b/mapper/orm-jakarta-batch/core/src/test/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParametersBuilderTest.java index cb0c2890a0d..5bfc60e1825 100644 --- a/mapper/orm-jakarta-batch/core/src/test/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParametersBuilderTest.java +++ b/mapper/orm-jakarta-batch/core/src/test/java/org/hibernate/search/jakarta/batch/core/massindexing/MassIndexingJobParametersBuilderTest.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Properties; import org.hibernate.CacheMode; @@ -33,7 +34,7 @@ public class MassIndexingJobParametersBuilderTest { private static final boolean MERGE_SEGMENTS_ON_FINISH = true; private static final boolean PURGE_ALL_ON_START = true; private static final int ID_FETCH_SIZE = Integer.MIN_VALUE; - private static final int ENTITY_FETCH_SIZE = Integer.MIN_VALUE + 1; + private static final int ENTITY_FETCH_SIZE = 12; private static final int MAX_RESULTS_PER_ENTITY = 10_000; private static final int MAX_THREADS = 2; private static final int ROWS_PER_PARTITION = 500; @@ -131,34 +132,39 @@ public void testForEntity_null() { MassIndexingJob.parameters().forEntity( null ); } - @Test(expected = NullPointerException.class) + @Test(expected = IllegalArgumentException.class) public void testRestrictedBy_stringNull() { - MassIndexingJob.parameters().forEntity( String.class ).restrictedBy( (String) null ); + MassIndexingJob.parameters().forEntity( String.class ).reindexOnly( null, Map.of() ); + } + + @Test(expected = IllegalArgumentException.class) + public void testRestrictedBy_mapNull() { + MassIndexingJob.parameters().forEntity( String.class ).reindexOnly( "foo", null ); } @Test(expected = SearchException.class) - public void testSessionClearInterval_greaterThanCheckpointInterval() { + public void testEntityFetchSize_greaterThanCheckpointInterval() { MassIndexingJob.parameters() .forEntity( UnusedEntity.class ) - .sessionClearInterval( 5 ) + .entityFetchSize( 5 ) .checkpointInterval( 4 ) .build(); } @Test - public void testSessionClearInterval_defaultGreaterThanCheckpointInterval() { + public void testEntityFetchSize_defaultGreaterThanCheckpointInterval() { MassIndexingJob.parameters() .forEntity( UnusedEntity.class ) - .checkpointInterval( MassIndexingJobParameters.Defaults.SESSION_CLEAR_INTERVAL_DEFAULT_RAW - 1 ) + .checkpointInterval( MassIndexingJobParameters.Defaults.ENTITY_FETCH_SIZE_RAW - 1 ) .build(); // ok, session clear interval will default to the value of checkpointInterval } @Test(expected = SearchException.class) - public void testSessionClearInterval_greaterThanDefaultCheckpointInterval() { + public void testEntityFetchSize_greaterThanDefaultCheckpointInterval() { MassIndexingJob.parameters() .forEntity( UnusedEntity.class ) - .sessionClearInterval( MassIndexingJobParameters.Defaults.CHECKPOINT_INTERVAL_DEFAULT_RAW + 1 ) + .entityFetchSize( MassIndexingJobParameters.Defaults.CHECKPOINT_INTERVAL_DEFAULT_RAW + 1 ) .build(); } @@ -217,13 +223,27 @@ public void testIdFetchSize() throws Exception { @Test public void testEntityFetchSize() throws Exception { - for ( int allowedValue : Arrays.asList( Integer.MAX_VALUE, 0, Integer.MIN_VALUE ) ) { + for ( int allowedValue : Arrays.asList( Integer.MAX_VALUE, 1 ) ) { MassIndexingJob.parameters() .forEntity( UnusedEntity.class ) .entityFetchSize( allowedValue ); } } + @Test(expected = IllegalArgumentException.class) + public void testEntityFetchSize_zero() throws Exception { + MassIndexingJob.parameters() + .forEntity( UnusedEntity.class ) + .entityFetchSize( 0 ); + } + + @Test(expected = IllegalArgumentException.class) + public void testEntityFetchSize_negative() throws Exception { + MassIndexingJob.parameters() + .forEntity( UnusedEntity.class ) + .entityFetchSize( -1 ); + } + private static class UnusedEntity { private UnusedEntity() { } diff --git a/mapper/orm-jakarta-batch/jberet/src/main/java/org/hibernate/search/jakarta/batch/jberet/impl/HibernateSearchJakartaBatchExtension.java b/mapper/orm-jakarta-batch/jberet/src/main/java/org/hibernate/search/jakarta/batch/jberet/impl/HibernateSearchJakartaBatchExtension.java index bbd056fc388..f4c36e9083e 100644 --- a/mapper/orm-jakarta-batch/jberet/src/main/java/org/hibernate/search/jakarta/batch/jberet/impl/HibernateSearchJakartaBatchExtension.java +++ b/mapper/orm-jakarta-batch/jberet/src/main/java/org/hibernate/search/jakarta/batch/jberet/impl/HibernateSearchJakartaBatchExtension.java @@ -23,7 +23,7 @@ import org.hibernate.search.jakarta.batch.core.inject.scope.spi.HibernateSearchJobScoped; import org.hibernate.search.jakarta.batch.core.inject.scope.spi.HibernateSearchPartitionScoped; import org.hibernate.search.jakarta.batch.core.massindexing.spi.JobContextSetupListener; -import org.hibernate.search.jakarta.batch.core.massindexing.step.spi.EntityReader; +import org.hibernate.search.jakarta.batch.core.massindexing.step.spi.EntityIdReader; import org.jberet.cdi.JobScoped; import org.jberet.cdi.PartitionScoped; @@ -57,7 +57,7 @@ public class HibernateSearchJakartaBatchExtension implements Extension { public void afterTypeDiscovery(@Observes AfterTypeDiscovery event, BeanManager beanManager) { registerType( event, beanManager, JobContextSetupListener.class ); - registerType( event, beanManager, EntityReader.class ); + registerType( event, beanManager, EntityIdReader.class ); } public void afterBeanDiscovery(@Observes AfterBeanDiscovery event, BeanManager beanManager) { diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmLoadingStrategy.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmLoadingStrategy.java index 45316ede5d5..aa80593de8f 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmLoadingStrategy.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmLoadingStrategy.java @@ -9,13 +9,15 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.search.mapper.orm.common.impl.HibernateOrmUtils; -import org.hibernate.search.mapper.orm.massindexing.impl.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmQueryLoader; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; import org.hibernate.search.util.common.AssertionFailure; public abstract class AbstractHibernateOrmLoadingStrategy @@ -33,8 +35,14 @@ public abstract class AbstractHibernateOrmLoadingStrategy } @Override - public HibernateOrmQueryLoader createQueryLoader( - List> typeContexts, Optional conditionalExpression) { + public HibernateOrmQueryLoader createQueryLoader(List> typeContexts, + List conditionalExpressions) { + return createQueryLoader( typeContexts, conditionalExpressions, null ); + } + + @Override + public HibernateOrmQueryLoader createQueryLoader(List> typeContexts, + List conditionalExpressions, String order) { Set> includedTypesFilter; if ( HibernateOrmUtils.targetsAllConcreteSubTypes( sessionFactory, rootEntityMappingType, typeContexts ) ) { // All concrete types are included, no need to filter by type. @@ -47,16 +55,16 @@ public HibernateOrmQueryLoader createQueryLoader( } } - if ( conditionalExpression.isPresent() ) { + if ( !conditionalExpressions.isEmpty() || order != null ) { if ( typeContexts.size() != 1 ) { - throw new AssertionFailure( "conditional expression is always defined on a single type" ); + throw new AssertionFailure( "conditional/order expression is always defined on a single type" ); } EntityMappingType entityMappingType = typeContexts.get( 0 ).entityMappingType(); - return new HibernateOrmQueryLoader<>( - queryFactory, entityMappingType, includedTypesFilter, conditionalExpression.get() ); + return new HibernateOrmQueryLoaderImpl<>( queryFactory, entityMappingType, + includedTypesFilter, conditionalExpressions, order ); } - return new HibernateOrmQueryLoader<>( queryFactory, includedTypesFilter ); + return new HibernateOrmQueryLoaderImpl<>( queryFactory, includedTypesFilter ); } } diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmSelectionEntityLoader.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmSelectionEntityLoader.java index 1dabd67a964..1f2ccc1ac23 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmSelectionEntityLoader.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/AbstractHibernateOrmSelectionEntityLoader.java @@ -13,6 +13,9 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.Query; import org.hibernate.search.engine.common.timing.Deadline; +import org.hibernate.search.mapper.orm.loading.spi.EntityGraphHint; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; +import org.hibernate.search.mapper.orm.loading.spi.MutableEntityLoadingOptions; import org.hibernate.search.mapper.orm.search.query.spi.HibernateOrmSearchQueryHints; import org.hibernate.search.mapper.pojo.loading.spi.PojoSelectionEntityLoader; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/ConditionalExpressionQueryFactory.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/ConditionalExpressionQueryFactory.java index 592e6ff1d8c..7bb3c7b0891 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/ConditionalExpressionQueryFactory.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/ConditionalExpressionQueryFactory.java @@ -6,12 +6,14 @@ */ package org.hibernate.search.mapper.orm.loading.impl; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.Query; -import org.hibernate.search.mapper.orm.massindexing.impl.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; public abstract class ConditionalExpressionQueryFactory implements TypeQueryFactory { @@ -26,43 +28,69 @@ public ConditionalExpressionQueryFactory(Class uniquePropertyType, String uni @Override public Query createQueryForCount(SharedSessionContractImplementor session, EntityMappingType entityMappingType, - Set> includedTypesFilter, ConditionalExpression conditionalExpression) { - return createQueryWithConditionalExpression( session, + Set> includedTypesFilter, + List conditionalExpressions) { + return createQueryWithConditionalExpressionsOrOrder( session, "select count(e) from " + entityMappingType.getEntityName() + " e", - Long.class, "e", includedTypesFilter, conditionalExpression + Long.class, "e", includedTypesFilter, conditionalExpressions, null ); } @Override public Query createQueryForIdentifierListing(SharedSessionContractImplementor session, EntityMappingType entityMappingType, - Set> includedTypesFilter, ConditionalExpression conditionalExpression) { - return createQueryWithConditionalExpression( session, - "select e. " + uniquePropertyName + " from " + entityMappingType.getEntityName() + " e", + Set> includedTypesFilter, + List conditionalExpressions, String order) { + return createQueryWithConditionalExpressionsOrOrder( session, + "select e." + uniquePropertyName + " from " + entityMappingType.getEntityName() + " e", uniquePropertyType, "e", - includedTypesFilter, conditionalExpression + includedTypesFilter, conditionalExpressions, order ); } - private Query createQueryWithConditionalExpression(SharedSessionContractImplementor session, + private Query createQueryWithConditionalExpressionsOrOrder(SharedSessionContractImplementor session, String hql, Class returnedType, String entityAlias, - Set> includedTypesFilter, ConditionalExpression conditionalExpression) { - if ( includedTypesFilter.isEmpty() ) { - return createQueryWithConditionalExpression( session, hql, returnedType, conditionalExpression ); + Set> includedTypesFilter, + List conditionalExpressions, String order) { + List allConditionalExpressions; + if ( !includedTypesFilter.isEmpty() ) { + ConditionalExpression typeFilter = + new ConditionalExpression( "type(" + entityAlias + ") in (:" + TYPES_PARAM_NAME + ")" ); + typeFilter.param( TYPES_PARAM_NAME, includedTypesFilter ); + allConditionalExpressions = new ArrayList<>(); + allConditionalExpressions.add( typeFilter ); + allConditionalExpressions.addAll( conditionalExpressions ); } - - hql += " where type(" + entityAlias + ") in (:" + TYPES_PARAM_NAME + ") and ( " + conditionalExpression.hql() + " )"; - Query query = session.createQuery( hql, returnedType ); - query.setParameterList( TYPES_PARAM_NAME, includedTypesFilter ); - conditionalExpression.applyParams( query ); - return query; + else { + allConditionalExpressions = conditionalExpressions; + } + return createQueryWithConditionalExpressionsOrOrder( session, hql, returnedType, allConditionalExpressions, order ); } - private Query createQueryWithConditionalExpression(SharedSessionContractImplementor session, - String hql, Class returnedType, ConditionalExpression conditionalExpression) { - hql += " where " + conditionalExpression.hql(); - Query query = session.createQuery( hql, returnedType ); - conditionalExpression.applyParams( query ); + private Query createQueryWithConditionalExpressionsOrOrder(SharedSessionContractImplementor session, + String hql, Class returnedType, + List conditionalExpressions, String order) { + StringBuilder hqlBuilder = new StringBuilder( hql ); + if ( !conditionalExpressions.isEmpty() ) { + hqlBuilder.append( " where " ); + boolean first = true; + for ( ConditionalExpression expression : conditionalExpressions ) { + if ( first ) { + first = false; + } + else { + hqlBuilder.append( " and " ); + } + hqlBuilder.append( "(" ).append( expression.hql() ).append( ")" ); + } + } + if ( order != null ) { + hqlBuilder.append( " order by " ).append( order ); + } + Query query = session.createQuery( hqlBuilder.toString(), returnedType ); + for ( ConditionalExpression expression : conditionalExpressions ) { + expression.applyParams( query ); + } return query; } } diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmEntityIdEntityLoadingStrategy.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmEntityIdEntityLoadingStrategy.java index c7215af77db..57d35f497d0 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmEntityIdEntityLoadingStrategy.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmEntityIdEntityLoadingStrategy.java @@ -14,6 +14,10 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.search.mapper.orm.common.impl.HibernateOrmUtils; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.MutableEntityLoadingOptions; import org.hibernate.search.mapper.orm.search.loading.EntityLoadingCacheLookupStrategy; import org.hibernate.search.mapper.pojo.loading.spi.PojoSelectionEntityLoader; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassEntityLoader.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassEntityLoader.java index 87f96d84419..eb8b004eb9e 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassEntityLoader.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassEntityLoader.java @@ -12,6 +12,7 @@ import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.search.mapper.orm.common.spi.TransactionHelper; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmQueryLoader; import org.hibernate.search.mapper.pojo.loading.spi.PojoMassEntityLoader; import org.hibernate.search.mapper.pojo.loading.spi.PojoMassEntitySink; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassIdentifierLoader.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassIdentifierLoader.java index 033ccecf9e7..5234e6cf934 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassIdentifierLoader.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmMassIdentifierLoader.java @@ -13,6 +13,7 @@ import org.hibernate.ScrollableResults; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.search.mapper.orm.common.spi.TransactionHelper; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmQueryLoader; import org.hibernate.search.mapper.orm.logging.impl.Log; import org.hibernate.search.mapper.pojo.loading.spi.PojoMassIdentifierLoader; import org.hibernate.search.mapper.pojo.loading.spi.PojoMassIdentifierSink; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmNonEntityIdPropertyEntityLoadingStrategy.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmNonEntityIdPropertyEntityLoadingStrategy.java index 48140e8931f..fc7b4e77dd3 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmNonEntityIdPropertyEntityLoadingStrategy.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmNonEntityIdPropertyEntityLoadingStrategy.java @@ -13,6 +13,10 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.MutableEntityLoadingOptions; import org.hibernate.search.mapper.orm.logging.impl.Log; import org.hibernate.search.mapper.orm.search.loading.EntityLoadingCacheLookupStrategy; import org.hibernate.search.mapper.pojo.loading.spi.PojoSelectionEntityLoader; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmQueryLoader.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmQueryLoaderImpl.java similarity index 62% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmQueryLoader.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmQueryLoaderImpl.java index 83a8b93fb74..75666a029d9 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmQueryLoader.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmQueryLoaderImpl.java @@ -6,6 +6,7 @@ */ package org.hibernate.search.mapper.orm.loading.impl; +import java.util.List; import java.util.Set; import org.hibernate.MultiIdentifierLoadAccess; @@ -13,55 +14,62 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.Query; -import org.hibernate.search.mapper.orm.massindexing.impl.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmQueryLoader; -public class HibernateOrmQueryLoader { +class HibernateOrmQueryLoaderImpl implements HibernateOrmQueryLoader { private final TypeQueryFactory queryFactory; private final Set> includedTypesFilter; private final EntityMappingType entityMappingType; - private final ConditionalExpression conditionalExpression; + private final List conditionalExpressions; + private final String order; - public HibernateOrmQueryLoader(TypeQueryFactory queryFactory, + public HibernateOrmQueryLoaderImpl(TypeQueryFactory queryFactory, Set> includedTypesFilter) { this.queryFactory = queryFactory; this.includedTypesFilter = includedTypesFilter; this.entityMappingType = null; - this.conditionalExpression = null; + this.conditionalExpressions = List.of(); + this.order = null; } - public HibernateOrmQueryLoader(TypeQueryFactory queryFactory, + public HibernateOrmQueryLoaderImpl(TypeQueryFactory queryFactory, EntityMappingType entityMappingType, Set> includedTypesFilter, - ConditionalExpression conditionalExpression) { + List conditionalExpressions, String order) { this.queryFactory = queryFactory; this.includedTypesFilter = includedTypesFilter; this.entityMappingType = entityMappingType; - this.conditionalExpression = conditionalExpression; + this.conditionalExpressions = conditionalExpressions; + this.order = order; } + @Override public Query createCountQuery(SharedSessionContractImplementor session) { - return ( conditionalExpression == null ) + return conditionalExpressions.isEmpty() ? queryFactory.createQueryForCount( session, includedTypesFilter ) - : queryFactory.createQueryForCount( - session, entityMappingType, includedTypesFilter, conditionalExpression - ); + : queryFactory.createQueryForCount( session, entityMappingType, includedTypesFilter, + conditionalExpressions ); } + @Override public Query createIdentifiersQuery(SharedSessionContractImplementor session) { - return ( conditionalExpression == null ) + return conditionalExpressions.isEmpty() && order == null ? queryFactory.createQueryForIdentifierListing( session, includedTypesFilter ) - : queryFactory.createQueryForIdentifierListing( - session, entityMappingType, includedTypesFilter, conditionalExpression - ); + : queryFactory.createQueryForIdentifierListing( session, entityMappingType, includedTypesFilter, + conditionalExpressions, order ); } + @Override public Query createLoadingQuery(SessionImplementor session, String idParameterName) { return queryFactory.createQueryForLoadByUniqueProperty( session, idParameterName ); } + @Override public MultiIdentifierLoadAccess createMultiIdentifierLoadAccess(SessionImplementor session) { return queryFactory.createMultiIdentifierLoadAccess( session ); } + @Override public boolean uniquePropertyIsTheEntityId() { return queryFactory.uniquePropertyIsTheEntityId(); } diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByIdLoader.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByIdLoader.java index 3c20b4003be..b04b90afb8b 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByIdLoader.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByIdLoader.java @@ -12,6 +12,8 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.Query; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; +import org.hibernate.search.mapper.orm.loading.spi.MutableEntityLoadingOptions; import org.hibernate.search.util.common.annotation.impl.SuppressForbiddenApis; /** diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByNonIdPropertyLoader.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByNonIdPropertyLoader.java index 58b36111c56..81e6f978f52 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByNonIdPropertyLoader.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionEntityByNonIdPropertyLoader.java @@ -14,6 +14,9 @@ import org.hibernate.Hibernate; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.Query; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.MutableEntityLoadingOptions; import org.hibernate.search.mapper.orm.logging.impl.Log; import org.hibernate.search.util.common.impl.CollectionHelper; import org.hibernate.search.util.common.logging.impl.LoggerFactory; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionLoadingContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionLoadingContext.java index ce14905339c..ce883373374 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionLoadingContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmSelectionLoadingContext.java @@ -16,6 +16,12 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; +import org.hibernate.search.mapper.orm.loading.spi.EntityGraphHint; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.LoadingMappingContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.MutableEntityLoadingOptions; import org.hibernate.search.mapper.orm.logging.impl.Log; import org.hibernate.search.mapper.orm.search.loading.EntityLoadingCacheLookupStrategy; import org.hibernate.search.mapper.orm.search.loading.dsl.SearchLoadingOptionsStep; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingIndexedTypeContextProvider.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingIndexedTypeContextProvider.java index 1585c525458..a373ee3d22a 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingIndexedTypeContextProvider.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingIndexedTypeContextProvider.java @@ -6,6 +6,7 @@ */ package org.hibernate.search.mapper.orm.loading.impl; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; public interface LoadingIndexedTypeContextProvider { diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/TypeQueryFactory.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/TypeQueryFactory.java index 1d289634b7e..645b9b2c050 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/TypeQueryFactory.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/TypeQueryFactory.java @@ -6,6 +6,7 @@ */ package org.hibernate.search.mapper.orm.loading.impl; +import java.util.List; import java.util.Map; import java.util.Set; @@ -17,7 +18,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.query.Query; -import org.hibernate.search.mapper.orm.massindexing.impl.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; public interface TypeQueryFactory { @@ -43,10 +44,12 @@ Query createQueryForIdentifierListing(SharedSessionContractImplementor sessio Set> includedTypesFilter); Query createQueryForCount(SharedSessionContractImplementor session, EntityMappingType entityMappingType, - Set> includedTypesFilter, ConditionalExpression conditionalExpression); + Set> includedTypesFilter, + List conditionalExpressions); Query createQueryForIdentifierListing(SharedSessionContractImplementor session, EntityMappingType entityMappingType, - Set> includedTypesFilter, ConditionalExpression conditionalExpression); + Set> includedTypesFilter, + List conditionalExpressions, String order); Query createQueryForLoadByUniqueProperty(SessionImplementor session, String parameterName); diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/ConditionalExpression.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/ConditionalExpression.java similarity index 76% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/ConditionalExpression.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/ConditionalExpression.java index 662d17b7da3..b9274f16847 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/ConditionalExpression.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/ConditionalExpression.java @@ -4,14 +4,14 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.mapper.orm.massindexing.impl; +package org.hibernate.search.mapper.orm.loading.spi; import java.util.HashMap; import java.util.Map; import org.hibernate.query.Query; -public class ConditionalExpression { +public final class ConditionalExpression { private final String hql; private final Map params = new HashMap<>(); @@ -20,6 +20,14 @@ public ConditionalExpression(String hql) { this.hql = hql; } + @Override + public String toString() { + return "ConditionalExpression[" + + "hql='" + hql + '\'' + + ", params=" + params + + ']'; + } + public String hql() { return hql; } diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/EntityGraphHint.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/EntityGraphHint.java similarity index 92% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/EntityGraphHint.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/EntityGraphHint.java index f3e463925c2..f3bf83a7430 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/EntityGraphHint.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/EntityGraphHint.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.mapper.orm.loading.impl; +package org.hibernate.search.mapper.orm.loading.spi; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmEntityLoadingStrategy.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/HibernateOrmEntityLoadingStrategy.java similarity index 76% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmEntityLoadingStrategy.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/HibernateOrmEntityLoadingStrategy.java index 64410e0707e..8f517383b9e 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/HibernateOrmEntityLoadingStrategy.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/HibernateOrmEntityLoadingStrategy.java @@ -4,13 +4,11 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.mapper.orm.loading.impl; +package org.hibernate.search.mapper.orm.loading.spi; import java.util.List; -import java.util.Optional; import java.util.Set; -import org.hibernate.search.mapper.orm.massindexing.impl.ConditionalExpression; import org.hibernate.search.mapper.orm.search.loading.EntityLoadingCacheLookupStrategy; import org.hibernate.search.mapper.pojo.loading.spi.PojoSelectionEntityLoader; @@ -39,7 +37,10 @@ PojoSelectionEntityLoader createLoader(Set createQueryLoader( - List> typeContexts, Optional conditionalExpression); + HibernateOrmQueryLoader createQueryLoader(List> typeContexts, + List conditionalExpressions); + + HibernateOrmQueryLoader createQueryLoader(List> typeContexts, + List conditionalExpressions, String order); } diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/HibernateOrmQueryLoader.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/HibernateOrmQueryLoader.java new file mode 100644 index 00000000000..5a89931522b --- /dev/null +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/HibernateOrmQueryLoader.java @@ -0,0 +1,24 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.mapper.orm.loading.spi; + +import org.hibernate.MultiIdentifierLoadAccess; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.Query; + +public interface HibernateOrmQueryLoader { + Query createCountQuery(SharedSessionContractImplementor session); + + Query createIdentifiersQuery(SharedSessionContractImplementor session); + + Query createLoadingQuery(SessionImplementor session, String idParameterName); + + MultiIdentifierLoadAccess createMultiIdentifierLoadAccess(SessionImplementor session); + + boolean uniquePropertyIsTheEntityId(); +} diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingMappingContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingMappingContext.java similarity index 91% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingMappingContext.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingMappingContext.java index bbb5a5c80a5..b551ce65454 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingMappingContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingMappingContext.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.mapper.orm.loading.impl; +package org.hibernate.search.mapper.orm.loading.spi; import jakarta.persistence.EntityManager; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingSessionContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingSessionContext.java similarity index 90% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingSessionContext.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingSessionContext.java index dd8e8f0ba01..3a14cb9a2ee 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingSessionContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingSessionContext.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.mapper.orm.loading.impl; +package org.hibernate.search.mapper.orm.loading.spi; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.search.mapper.pojo.model.spi.PojoRuntimeIntrospector; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingTypeContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingTypeContext.java similarity index 94% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingTypeContext.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingTypeContext.java index eaac4243ea7..90fa5d20e3f 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/LoadingTypeContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/LoadingTypeContext.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.mapper.orm.loading.impl; +package org.hibernate.search.mapper.orm.loading.spi; import java.util.List; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/MutableEntityLoadingOptions.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/MutableEntityLoadingOptions.java similarity index 94% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/MutableEntityLoadingOptions.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/MutableEntityLoadingOptions.java index 48e2214a607..438c9534bfd 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/impl/MutableEntityLoadingOptions.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/loading/spi/MutableEntityLoadingOptions.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.mapper.orm.loading.impl; +package org.hibernate.search.mapper.orm.loading.spi; import java.util.ArrayList; import java.util.List; @@ -13,7 +13,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.search.util.common.impl.Contracts; -public class MutableEntityLoadingOptions { +public final class MutableEntityLoadingOptions { private int fetchSize; private List> entityGraphHints; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/AbstractHibernateOrmTypeContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/AbstractHibernateOrmTypeContext.java index 1837b1c0692..1e811f7aa60 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/AbstractHibernateOrmTypeContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/AbstractHibernateOrmTypeContext.java @@ -14,9 +14,9 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.search.mapper.orm.event.impl.HibernateOrmListenerTypeContext; import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmEntityIdEntityLoadingStrategy; -import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmEntityLoadingStrategy; import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmNonEntityIdPropertyEntityLoadingStrategy; -import org.hibernate.search.mapper.orm.loading.impl.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; import org.hibernate.search.mapper.orm.session.impl.HibernateOrmSessionTypeContext; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoTypeExtendedMappingCollector; import org.hibernate.search.mapper.pojo.model.path.spi.PojoPathFilter; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/HibernateOrmTypeContextContainer.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/HibernateOrmTypeContextContainer.java index 164526a02f4..3d6319b703b 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/HibernateOrmTypeContextContainer.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/mapping/impl/HibernateOrmTypeContextContainer.java @@ -21,7 +21,7 @@ import org.hibernate.search.mapper.orm.model.impl.HibernateOrmBasicTypeMetadataProvider; import org.hibernate.search.mapper.orm.model.impl.HibernateOrmRawTypeIdentifierResolver; import org.hibernate.search.mapper.orm.session.impl.HibernateOrmSessionTypeContextProvider; -import org.hibernate.search.mapper.orm.spi.BatchTypeIdentifierProvider; +import org.hibernate.search.mapper.orm.spi.BatchTypeContextProvider; import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeModel; import org.hibernate.search.util.common.data.spi.KeyValueProvider; @@ -29,7 +29,7 @@ class HibernateOrmTypeContextContainer implements HibernateOrmListenerTypeContextProvider, HibernateOrmSessionTypeContextProvider, - AutomaticIndexingTypeContextProvider, LoadingIndexedTypeContextProvider, BatchTypeIdentifierProvider { + AutomaticIndexingTypeContextProvider, LoadingIndexedTypeContextProvider, BatchTypeContextProvider { private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/MassIndexerFilteringTypeStep.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/MassIndexerFilteringTypeStep.java index 51f49b286e6..448b8636ec1 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/MassIndexerFilteringTypeStep.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/MassIndexerFilteringTypeStep.java @@ -14,14 +14,21 @@ public interface MassIndexerFilteringTypeStep { /** * Use a JPQL/HQL conditional expression to limit the entities to be re-indexed. *

- * The letter {@code e} is supposed to be used here as query alias. * For instance a valid expression could be the following: *

-	 *     e.manager.level < 2
+	 *     manager.level < 2
 	 * 
- * To filter instances that have a manager whose level is strictly less than 2. + * ... to filter instances that have a manager whose level is strictly less than 2. + *

+ * Parameters can be used, so assuming the parameter "max" is defined + * in the {@link MassIndexerReindexParameterStep#param(String, Object) next step}, + * this is valid as well: + *

+	 *     manager.level < :max
+	 * 
+ * ... to filter instances that have a manager whose level is strictly less than {@code :max}. * - * @param conditionalExpression A JPQL/HQL query text which express the condition to apply + * @param conditionalExpression A JPQL/HQL query text which expresses the condition to apply * @return A new step to define optional parameters for the JPQL/HQL conditional expression or other expressions. */ MassIndexerReindexParameterStep reindexOnly(String conditionalExpression); diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexer.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexer.java index eb6a3dead55..15548e42d25 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexer.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexer.java @@ -9,6 +9,7 @@ import java.util.concurrent.CompletionStage; import org.hibernate.CacheMode; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; import org.hibernate.search.mapper.orm.massindexing.MassIndexer; import org.hibernate.search.mapper.orm.massindexing.MassIndexerFilteringTypeStep; import org.hibernate.search.mapper.pojo.massindexing.MassIndexingEnvironment; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerFilteringTypeStep.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerFilteringTypeStep.java index 62f0ace1865..e623f84b053 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerFilteringTypeStep.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerFilteringTypeStep.java @@ -6,6 +6,7 @@ */ package org.hibernate.search.mapper.orm.massindexing.impl; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; import org.hibernate.search.mapper.orm.massindexing.MassIndexerFilteringTypeStep; import org.hibernate.search.mapper.orm.massindexing.MassIndexerReindexParameterStep; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerReindexParameterStep.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerReindexParameterStep.java index e72ebea8133..221eab2b91f 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerReindexParameterStep.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexerReindexParameterStep.java @@ -6,6 +6,7 @@ */ package org.hibernate.search.mapper.orm.massindexing.impl; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; import org.hibernate.search.mapper.orm.massindexing.MassIndexerReindexParameterStep; public class HibernateOrmMassIndexerReindexParameterStep extends HibernateOrmMassIndexerFilteringTypeStep diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingContext.java index d401aed50cf..16c58ce97c6 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingContext.java @@ -18,12 +18,13 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmEntityLoadingStrategy; import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmMassEntityLoader; import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmMassIdentifierLoader; import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmMassLoadingOptions; -import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmQueryLoader; -import org.hibernate.search.mapper.orm.loading.impl.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.ConditionalExpression; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmQueryLoader; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; import org.hibernate.search.mapper.orm.session.impl.HibernateOrmSessionTypeContextProvider; import org.hibernate.search.mapper.pojo.loading.spi.PojoMassEntityLoader; import org.hibernate.search.mapper.pojo.loading.spi.PojoMassEntitySink; @@ -171,8 +172,7 @@ public PojoMassIdentifierLoader createIdentifierLoader(PojoMassIndexingIdentifie .map( typeContextProvider::forExactType ) .collect( Collectors.toList() ); - HibernateOrmQueryLoader typeQueryLoader = delegate.createQueryLoader( - typeContexts, conditionalExpression ); + HibernateOrmQueryLoader typeQueryLoader = createQueryLoader( typeContexts ); SharedSessionContractImplementor session = (SharedSessionContractImplementor) sessionFactory .withStatelessOptions() .tenantIdentifier( context.tenantIdentifier() ) @@ -195,8 +195,7 @@ public PojoMassEntityLoader createEntityLoader(PojoMassIndexingEntityLoadingC .map( typeContextProvider::forExactType ) .collect( Collectors.toList() ); - HibernateOrmQueryLoader typeQueryLoader = delegate.createQueryLoader( - typeContexts, conditionalExpression ); + HibernateOrmQueryLoader typeQueryLoader = createQueryLoader( typeContexts ); SessionImplementor session = (SessionImplementor) sessionFactory .withOptions() .tenantIdentifier( context.tenantIdentifier() ) @@ -216,5 +215,11 @@ public PojoMassEntityLoader createEntityLoader(PojoMassIndexingEntityLoadingC } } + private HibernateOrmQueryLoader createQueryLoader(List> typeContexts) { + return delegate.createQueryLoader( typeContexts, conditionalExpression.isPresent() + ? List.of( conditionalExpression.get() ) + : List.of() ); + } + } } diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingIndexedTypeContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingIndexedTypeContext.java index bd3a149e822..8d1586d3b01 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingIndexedTypeContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/massindexing/impl/HibernateOrmMassIndexingIndexedTypeContext.java @@ -6,7 +6,7 @@ */ package org.hibernate.search.mapper.orm.massindexing.impl; -import org.hibernate.search.mapper.orm.loading.impl.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; public interface HibernateOrmMassIndexingIndexedTypeContext extends LoadingTypeContext { diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeIndexedTypeContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeIndexedTypeContext.java index 4ba4f29173f..f366858a1f7 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeIndexedTypeContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeIndexedTypeContext.java @@ -7,8 +7,8 @@ package org.hibernate.search.mapper.orm.scope.impl; import org.hibernate.search.mapper.orm.entity.SearchIndexedEntity; -import org.hibernate.search.mapper.orm.loading.impl.HibernateOrmEntityLoadingStrategy; -import org.hibernate.search.mapper.orm.loading.impl.LoadingTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.HibernateOrmEntityLoadingStrategy; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; import org.hibernate.search.mapper.orm.massindexing.impl.HibernateOrmMassIndexingIndexedTypeContext; import org.hibernate.search.mapper.pojo.scope.spi.PojoScopeDelegate; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeSessionContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeSessionContext.java index 63d79daddeb..ccef99ce9f9 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeSessionContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/scope/impl/HibernateOrmScopeSessionContext.java @@ -6,7 +6,7 @@ */ package org.hibernate.search.mapper.orm.scope.impl; -import org.hibernate.search.mapper.orm.loading.impl.LoadingSessionContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; import org.hibernate.search.mapper.orm.massindexing.impl.HibernateOrmMassIndexingSessionContext; import org.hibernate.search.mapper.orm.spi.BatchSessionContext; import org.hibernate.search.mapper.pojo.scope.spi.PojoScopeSessionContext; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/search/query/impl/HibernateOrmSearchQueryAdapter.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/search/query/impl/HibernateOrmSearchQueryAdapter.java index 65306ff958e..91db1ceec6d 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/search/query/impl/HibernateOrmSearchQueryAdapter.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/search/query/impl/HibernateOrmSearchQueryAdapter.java @@ -38,8 +38,8 @@ import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.search.engine.search.query.SearchQuery; import org.hibernate.search.engine.search.query.spi.SearchQueryImplementor; -import org.hibernate.search.mapper.orm.loading.impl.EntityGraphHint; -import org.hibernate.search.mapper.orm.loading.impl.MutableEntityLoadingOptions; +import org.hibernate.search.mapper.orm.loading.spi.EntityGraphHint; +import org.hibernate.search.mapper.orm.loading.spi.MutableEntityLoadingOptions; import org.hibernate.search.mapper.orm.logging.impl.Log; import org.hibernate.search.mapper.orm.search.query.spi.HibernateOrmSearchQueryHints; import org.hibernate.search.mapper.orm.search.query.spi.HibernateOrmSearchScrollableResultsAdapter; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/session/impl/HibernateOrmSearchSessionMappingContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/session/impl/HibernateOrmSearchSessionMappingContext.java index 7fd89a8d0c9..861f95867e9 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/session/impl/HibernateOrmSearchSessionMappingContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/session/impl/HibernateOrmSearchSessionMappingContext.java @@ -11,7 +11,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.search.engine.reporting.FailureHandler; -import org.hibernate.search.mapper.orm.loading.impl.LoadingMappingContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingMappingContext; import org.hibernate.search.mapper.orm.scope.impl.SearchScopeImpl; import org.hibernate.search.mapper.pojo.session.spi.PojoSearchSessionMappingContext; import org.hibernate.search.mapper.pojo.work.SearchIndexingPlanFilter; diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchMappingContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchMappingContext.java index 76e279854b1..d737fbd074a 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchMappingContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchMappingContext.java @@ -10,7 +10,7 @@ public interface BatchMappingContext { - BatchTypeIdentifierProvider typeContextProvider(); + BatchTypeContextProvider typeContextProvider(); BatchSessionContext sessionContext(EntityManager entityManager); diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchSessionContext.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchSessionContext.java index 1000bf6e40d..48438b24177 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchSessionContext.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchSessionContext.java @@ -6,9 +6,10 @@ */ package org.hibernate.search.mapper.orm.spi; +import org.hibernate.search.mapper.orm.loading.spi.LoadingSessionContext; import org.hibernate.search.mapper.pojo.work.spi.PojoIndexer; -public interface BatchSessionContext { +public interface BatchSessionContext extends LoadingSessionContext { PojoIndexer createIndexer(); diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchTypeIdentifierProvider.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchTypeContextProvider.java similarity index 65% rename from mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchTypeIdentifierProvider.java rename to mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchTypeContextProvider.java index 96774899301..4e4a82683cc 100644 --- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchTypeIdentifierProvider.java +++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/spi/BatchTypeContextProvider.java @@ -6,11 +6,11 @@ */ package org.hibernate.search.mapper.orm.spi; -import org.hibernate.search.mapper.pojo.model.spi.PojoTypeContext; +import org.hibernate.search.mapper.orm.loading.spi.LoadingTypeContext; import org.hibernate.search.util.common.data.spi.KeyValueProvider; -public interface BatchTypeIdentifierProvider { +public interface BatchTypeContextProvider { - KeyValueProvider> byEntityName(); + KeyValueProvider> byEntityName(); }