Skip to content

Commit

Permalink
HSEARCH-3349 Test the number of SQL statements triggered by entity lo…
Browse files Browse the repository at this point in the history
…ading in search queries
  • Loading branch information
yrodiere committed Jul 3, 2019
1 parent 579715b commit 2438781
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 19 deletions.
Expand Up @@ -6,7 +6,6 @@
*/
package org.hibernate.search.integrationtest.mapper.orm.search.loading;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.search.util.impl.integrationtest.common.stub.backend.StubBackendUtils.reference;

import java.util.ArrayList;
Expand All @@ -22,7 +21,7 @@
import org.hibernate.search.util.impl.integrationtest.common.rule.BackendMock;
import org.hibernate.search.util.impl.integrationtest.common.rule.StubSearchWorkBehavior;
import org.hibernate.search.util.impl.integrationtest.orm.OrmSetupHelper;
import org.hibernate.search.util.impl.integrationtest.orm.OrmUtils;
import org.hibernate.search.util.impl.integrationtest.orm.OrmSoftAssertions;

import org.junit.Rule;

Expand All @@ -39,8 +38,9 @@ public abstract class AbstractSearchQueryEntityLoadingIT {
protected final <T> void testLoading(List<? extends Class<? extends T>> targetClasses,
List<String> targetIndexes,
Consumer<DocumentReferenceCollector> hitDocumentReferencesContributor,
Consumer<EntityCollector<T>> expectedLoadedEntitiesContributor) {
OrmUtils.withinSession( sessionFactory(), session -> {
Consumer<EntityCollector<T>> expectedLoadedEntitiesContributor,
Consumer<OrmSoftAssertions> assertionsContributor) {
OrmSoftAssertions.withinSession( sessionFactory(), (session, softAssertions) -> {
SearchSession searchSession = Search.session( session );

SearchQuery<T> query = searchSession.search( targetClasses )
Expand All @@ -67,12 +67,13 @@ protected final <T> void testLoading(List<? extends Class<? extends T>> targetCl
expectedLoadedEntitiesContributor.accept( entityCollector );
List<T> expectedLoadedEntities = entityCollector.collected;

assertThat( loadedEntities )
softAssertions.assertThat( loadedEntities )
.as(
"Loaded entities when targeting types " + targetClasses
+ " and when the backend returns document references " + hitDocumentReferences
)
.containsExactlyElementsOf( expectedLoadedEntities );
assertionsContributor.accept( softAssertions );
} );
}

Expand Down
Expand Up @@ -11,6 +11,7 @@
import java.util.List;
import java.util.function.Consumer;

import org.hibernate.search.util.impl.integrationtest.orm.OrmSoftAssertions;
import org.hibernate.search.integrationtest.mapper.orm.search.loading.model.singletype.EntityIdDocumentIdIndexedEntity;
import org.hibernate.search.integrationtest.mapper.orm.search.loading.model.singletype.NonEntityIdDocumentIdIndexedEntity;

Expand All @@ -30,12 +31,14 @@ protected static List<SingleTypeLoadingModelPrimitives<?>> allSingleTypeLoadingM
}

protected final void testLoading(Consumer<DocumentReferenceCollector> hitDocumentReferencesContributor,
Consumer<EntityCollector<T>> expectedLoadedEntitiesContributor) {
Consumer<EntityCollector<T>> expectedLoadedEntitiesContributor,
Consumer<OrmSoftAssertions> assertionsContributor) {
testLoading(
Collections.singletonList( primitives.getIndexedClass() ),
Collections.singletonList( primitives.getIndexName() ),
hitDocumentReferencesContributor,
expectedLoadedEntitiesContributor
expectedLoadedEntitiesContributor,
assertionsContributor
);
}

Expand Down
Expand Up @@ -67,7 +67,9 @@ public void simple() {
c -> c
.entity( primitives.getIndexedClass(), 1 )
.entity( primitives.getIndexedClass(), 2 )
.entity( primitives.getIndexedClass(), 3 )
.entity( primitives.getIndexedClass(), 3 ),
// Only one entity type means only one statement should be executed, even if there are multiple hits
c -> c.assertStatementExecutionCount().isEqualTo( 1 )
);
}

Expand Down Expand Up @@ -95,7 +97,9 @@ public void notFound() {
.doc( primitives.getIndexName(), primitives.getDocumentIdForEntityId( 3 ) ),
c -> c
.entity( primitives.getIndexedClass(), 1 )
.entity( primitives.getIndexedClass(), 3 )
.entity( primitives.getIndexedClass(), 3 ),
// Only one entity type means only one statement should be executed, even if there are multiple hits
c -> c.assertStatementExecutionCount().isEqualTo( 1 )
);
}

Expand Down
Expand Up @@ -91,8 +91,6 @@ public void setup() {
@Test
@TestForIssue(jiraKey = "HSEARCH-3349")
public void singleHierarchy() {
// TODO HSEARCH-3349 check that we optimize this by running a single query even if multiple types are loaded

testLoading(
Arrays.asList(
Hierarchy1_A_B.class,
Expand All @@ -107,7 +105,8 @@ public void singleHierarchy() {
.doc( Hierarchy1_A_C.NAME, "3" ),
c -> c
.entity( Hierarchy1_A_B.class, 2 )
.entity( Hierarchy1_A_C.class, 3 )
.entity( Hierarchy1_A_C.class, 3 ),
c -> c.assertStatementExecutionCount().isEqualTo( 1 ) // Optimized: only one query per entity hierarchy
);
}

Expand All @@ -133,7 +132,8 @@ public void singleHierarchy_middleMappedSuperClass() {
.doc( Hierarchy5_A_B_D.NAME, "4" ),
c -> c
.entity( Hierarchy5_A_B_C.class, 3 )
.entity( Hierarchy5_A_B_D.class, 4 )
.entity( Hierarchy5_A_B_D.class, 4 ),
c -> c.assertStatementExecutionCount().isEqualTo( 1 ) // Optimized: only one query per entity hierarchy
);
}

Expand All @@ -145,8 +145,6 @@ public void singleHierarchy_middleMappedSuperClass() {
@Test
@TestForIssue(jiraKey = "HSEARCH-3349")
public void mixedHierarchies() {
// TODO HSEARCH-3349 check that we optimize this by running a single query for each hierarchy

testLoading(
Arrays.asList(
Hierarchy1_A_B.class,
Expand Down Expand Up @@ -181,7 +179,8 @@ public void mixedHierarchies() {
.entity( Hierarchy2_A_B.class, 2 )
.entity( Hierarchy2_A_C.class, 3 )
.entity( Hierarchy3_A_B.class, 2 )
.entity( Hierarchy3_A_C.class, 3 )
.entity( Hierarchy3_A_C.class, 3 ),
c -> c.assertStatementExecutionCount().isEqualTo( 3 ) // Optimized: only one query per entity hierarchy
);
}

Expand All @@ -206,7 +205,8 @@ public void mixedDocumentIdMapping_entityIdAndProperty_mixedHierarchies() {
.doc( Hierarchy1_A_B.NAME, "2" ),
c -> c
.entity( Hierarchy4_A_B__integer1DocumentId.class, 2 )
.entity( Hierarchy1_A_B.class, 2 )
.entity( Hierarchy1_A_B.class, 2 ),
c -> c.assertStatementExecutionCount().isEqualTo( 2 ) // Different loading method => need two separate statements
);
}

Expand All @@ -231,7 +231,8 @@ public void mixedDocumentIdMapping_entityIdAndProperty_singleHierarchy() {
.doc( Hierarchy4_A_D.NAME, "4" ),
c -> c
.entity( Hierarchy4_A_B__integer1DocumentId.class, 2 )
.entity( Hierarchy4_A_D.class, 4 )
.entity( Hierarchy4_A_D.class, 4 ),
c -> c.assertStatementExecutionCount().isEqualTo( 2 ) // Different loading method => need two separate statements
);
}

Expand All @@ -257,7 +258,8 @@ public void mixedDocumentIdMapping_differentProperty() {
.doc( Hierarchy4_A_C__integer2DocumentId.NAME, "43" ),
c -> c
.entity( Hierarchy4_A_B__integer1DocumentId.class, 2 )
.entity( Hierarchy4_A_C__integer2DocumentId.class, 3 )
.entity( Hierarchy4_A_C__integer2DocumentId.class, 3 ),
c -> c.assertStatementExecutionCount().isEqualTo( 2 ) // Different loading method => need two separate statements
);
}

Expand Down
@@ -0,0 +1,66 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.util.impl.integrationtest.orm;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

import org.hibernate.BaseSessionEventListener;
import org.hibernate.Session;
import org.hibernate.SessionEventListener;
import org.hibernate.SessionFactory;
import org.hibernate.resource.jdbc.spi.StatementInspector;

import org.assertj.core.api.AbstractIntegerAssert;
import org.assertj.core.api.AutoCloseableSoftAssertions;

public class OrmSoftAssertions extends AutoCloseableSoftAssertions {

public static void withinSession(SessionFactory sessionFactory,
BiConsumer<Session, OrmSoftAssertions> action) {
try ( OrmSoftAssertions softAssertions = new OrmSoftAssertions() ) {
try ( Session session = sessionFactory.withOptions()
.eventListeners( softAssertions.sessionEventListener )
.statementInspector( softAssertions.statementInspector )
.openSession() ) {
action.accept( session, softAssertions );
}
}
}

private final StatementInspector statementInspector;
private final SessionEventListener sessionEventListener;

private int statementExecutionCount = 0;
private final List<String> statements = new ArrayList<>();

private OrmSoftAssertions() {
sessionEventListener = new BaseSessionEventListener() {
@Override
public void jdbcPrepareStatementStart() {
++statementExecutionCount;
}
};
statementInspector = this::inspectSql;
}

public AbstractIntegerAssert<?> assertStatementExecutionCount() {
// Don't use statement.size(), just in case a statement is executed twice... not sure it can happen, though.
return assertThat( statementExecutionCount )
.as( "Statement execution count for statements [\n"
+ statements.stream().collect( Collectors.joining( "\n" ) )
+ "\n]" );
}

private String inspectSql(String sql) {
statements.add( sql );
return sql;
}

}

0 comments on commit 2438781

Please sign in to comment.