|
7 | 7 | package org.hibernate.search.integrationtest.backend.tck.search.query;
|
8 | 8 |
|
9 | 9 | import static org.assertj.core.api.Assertions.assertThat;
|
| 10 | +import static org.assertj.core.api.Assertions.assertThatThrownBy; |
10 | 11 | import static org.hibernate.search.util.impl.integrationtest.common.assertion.SearchHitsAssert.assertThatHits;
|
11 | 12 | import static org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMapperUtils.documentProvider;
|
12 | 13 | import static org.junit.Assert.assertFalse;
|
13 | 14 | import static org.junit.Assert.assertNotNull;
|
| 15 | +import static org.junit.Assume.assumeTrue; |
14 | 16 |
|
15 | 17 | import java.util.List;
|
16 | 18 | import java.util.Locale;
|
|
19 | 21 | import org.hibernate.search.engine.backend.document.IndexFieldReference;
|
20 | 22 | import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement;
|
21 | 23 | import org.hibernate.search.engine.backend.types.Sortable;
|
| 24 | +import org.hibernate.search.engine.search.query.SearchResultTotal; |
22 | 25 | import org.hibernate.search.engine.search.query.SearchScroll;
|
23 | 26 | import org.hibernate.search.engine.search.query.SearchScrollResult;
|
24 | 27 | import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep;
|
| 28 | +import org.hibernate.search.integrationtest.backend.tck.testsupport.util.TckConfiguration; |
25 | 29 | import org.hibernate.search.integrationtest.backend.tck.testsupport.util.rule.SearchSetupHelper;
|
| 30 | +import org.hibernate.search.util.common.SearchException; |
26 | 31 | import org.hibernate.search.util.impl.integrationtest.mapper.stub.SimpleMappedIndex;
|
27 | 32 |
|
28 | 33 | import org.junit.BeforeClass;
|
|
31 | 36 |
|
32 | 37 | public class SearchQueryScrollIT {
|
33 | 38 |
|
34 |
| - private static final int DOCUMENT_COUNT = 200; |
| 39 | + private static final int DOCUMENT_COUNT = 2000; |
35 | 40 | private static final int CHUNK_SIZE = 30;
|
36 | 41 | private static final int EXACT_DIVISOR_CHUNK_SIZE = 25;
|
37 | 42 |
|
@@ -117,6 +122,100 @@ public void tookAndTimedOut() {
|
117 | 122 | }
|
118 | 123 | }
|
119 | 124 |
|
| 125 | + @Test |
| 126 | + public void resultTotal() { |
| 127 | + try ( SearchScroll<DocumentReference> scroll = matchAllSortedByScoreQuery() |
| 128 | + .scroll( CHUNK_SIZE ) ) { |
| 129 | + for ( SearchScrollResult<DocumentReference> chunk = scroll.next(); chunk.hasHits(); |
| 130 | + chunk = scroll.next() ) { |
| 131 | + SearchResultTotal total = scroll.next().total(); |
| 132 | + |
| 133 | + assertThat( total.isHitCountExact() ).isTrue(); |
| 134 | + assertThat( total.isHitCountLowerBound() ).isFalse(); |
| 135 | + assertThat( total.hitCount() ).isEqualTo( DOCUMENT_COUNT ); |
| 136 | + assertThat( total.hitCountLowerBound() ).isEqualTo( DOCUMENT_COUNT ); |
| 137 | + } |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + @Test |
| 142 | + public void resultTotal_totalHitCountThreshold() { |
| 143 | + assumeTrue( |
| 144 | + "This backend doesn't take totalHitsThreshold() into account for scrolls.", |
| 145 | + TckConfiguration.get().getBackendFeatures().supportsTotalHitsThresholdForScroll() |
| 146 | + ); |
| 147 | + |
| 148 | + try ( SearchScroll<DocumentReference> scroll = matchAllSortedByScoreQuery() |
| 149 | + .totalHitCountThreshold( 100 ) |
| 150 | + .scroll( CHUNK_SIZE ) ) { |
| 151 | + int chunkCountSoFar = 0; |
| 152 | + for ( SearchScrollResult<DocumentReference> chunk = scroll.next(); chunk.hasHits(); |
| 153 | + chunk = scroll.next() ) { |
| 154 | + SearchResultTotal total = scroll.next().total(); |
| 155 | + |
| 156 | + ++chunkCountSoFar; |
| 157 | + |
| 158 | + // Even when approximate, the total hit count should be greater than or equal to |
| 159 | + // the number of hits processed so far. |
| 160 | + assertThat( total.hitCountLowerBound() ).isGreaterThanOrEqualTo( CHUNK_SIZE * chunkCountSoFar ); |
| 161 | + |
| 162 | + if ( chunkCountSoFar == 1 ) { |
| 163 | + // The first chunk definitely cannot have an exact count, |
| 164 | + // considering the high number of hits. |
| 165 | + assertThat( total.isHitCountExact() ).isFalse(); |
| 166 | + assertThat( total.isHitCountLowerBound() ).isTrue(); |
| 167 | + assertThat( total.hitCountLowerBound() ).isLessThanOrEqualTo( DOCUMENT_COUNT ); |
| 168 | + |
| 169 | + assertThatThrownBy( () -> total.hitCount() ) |
| 170 | + .isInstanceOf( SearchException.class ) |
| 171 | + .hasMessageContaining( |
| 172 | + "Trying to get the exact total hit count, but it is a lower bound." ); |
| 173 | + } |
| 174 | + else { |
| 175 | + // The next chunks *may* have an exact count, |
| 176 | + // depending on the internal implementation (depending on how many hits are retrieved). |
| 177 | + |
| 178 | + if ( total.isHitCountExact() ) { |
| 179 | + assertThat( total.isHitCountLowerBound() ).isFalse(); |
| 180 | + assertThat( total.hitCount() ).isEqualTo( DOCUMENT_COUNT ); |
| 181 | + assertThat( total.hitCountLowerBound() ).isEqualTo( DOCUMENT_COUNT ); |
| 182 | + } |
| 183 | + else { |
| 184 | + assertThat( total.isHitCountLowerBound() ).isTrue(); |
| 185 | + assertThat( total.hitCountLowerBound() ).isLessThanOrEqualTo( DOCUMENT_COUNT ); |
| 186 | + |
| 187 | + assertThatThrownBy( () -> total.hitCount() ) |
| 188 | + .isInstanceOf( SearchException.class ) |
| 189 | + .hasMessageContaining( |
| 190 | + "Trying to get the exact total hit count, but it is a lower bound." ); |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + @Test |
| 198 | + public void resultTotal_totalHitCountThreshold_veryHigh() { |
| 199 | + assumeTrue( |
| 200 | + "This backend doesn't take totalHitsThreshold() into account for scrolls.", |
| 201 | + TckConfiguration.get().getBackendFeatures().supportsTotalHitsThresholdForScroll() |
| 202 | + ); |
| 203 | + |
| 204 | + try ( SearchScroll<DocumentReference> scroll = matchAllSortedByScoreQuery() |
| 205 | + .totalHitCountThreshold( DOCUMENT_COUNT * 2 ) |
| 206 | + .scroll( CHUNK_SIZE ) ) { |
| 207 | + for ( SearchScrollResult<DocumentReference> chunk = scroll.next(); chunk.hasHits(); |
| 208 | + chunk = scroll.next() ) { |
| 209 | + SearchResultTotal total = scroll.next().total(); |
| 210 | + |
| 211 | + assertThat( total.isHitCountExact() ).isTrue(); |
| 212 | + assertThat( total.isHitCountLowerBound() ).isFalse(); |
| 213 | + assertThat( total.hitCount() ).isEqualTo( DOCUMENT_COUNT ); |
| 214 | + assertThat( total.hitCountLowerBound() ).isEqualTo( DOCUMENT_COUNT ); |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | + |
120 | 219 | private void checkScrolling(SearchScroll<DocumentReference> scroll, int documentCount, int chunkSize) {
|
121 | 220 | int docIndex = 0;
|
122 | 221 | int quotient = documentCount / chunkSize;
|
@@ -162,6 +261,11 @@ private void checkScrolling(SearchScroll<DocumentReference> scroll, int document
|
162 | 261 | .sort( f -> f.field( "integer" ).asc() );
|
163 | 262 | }
|
164 | 263 |
|
| 264 | + private SearchQueryOptionsStep<?, DocumentReference, ?, ?, ?> matchAllSortedByScoreQuery() { |
| 265 | + return index.query() |
| 266 | + .where( f -> f.matchAll() ); |
| 267 | + } |
| 268 | + |
165 | 269 | private SearchQueryOptionsStep<?, DocumentReference, ?, ?, ?> matchFirstHalfQuery() {
|
166 | 270 | return index.query()
|
167 | 271 | .where( f -> f.range().field( "integer" ).lessThan( DOCUMENT_COUNT / 2 ) )
|
|
0 commit comments