Skip to content

Commit a7f1c6b

Browse files
fix(sort): fix out of bounds exception with count 0 (#15085)
1 parent b9c263b commit a7f1c6b

File tree

5 files changed

+88
-3
lines changed

5 files changed

+88
-3
lines changed

metadata-io/src/main/java/com/linkedin/metadata/dataHubUsage/DataHubUsageServiceImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ private ExternalAuditEventsSearchResponse mapExternalAuditEventsSearchResponse(
112112
response.count(searchHits.getHits().length);
113113
response.total((int) searchHits.getTotalHits().value);
114114
String nextScrollId = null;
115-
if (searchHits.getHits().length == analyticsSearchRequest.getSize()) {
115+
if (searchHits.getHits().length == analyticsSearchRequest.getSize()
116+
&& searchHits.getHits().length > 0) {
116117
Object[] sort = searchHits.getHits()[searchHits.getHits().length - 1].getSortValues();
117118
nextScrollId = new SearchAfterWrapper(sort, null, 0L).toScrollId();
118119
}

metadata-io/src/main/java/com/linkedin/metadata/graph/elastic/ElasticSearchGraphService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ public RelatedEntitiesScrollResult scrollRelatedEntities(
364364
SearchHit[] searchHits = response.getHits().getHits();
365365
// Only return next scroll ID if there are more results, indicated by full size results
366366
String nextScrollId = null;
367-
if (searchHits.length == count) {
367+
if (searchHits.length == count && searchHits.length > 0) {
368368
Object[] sort = searchHits[searchHits.length - 1].getSortValues();
369369
nextScrollId = new SearchAfterWrapper(sort, null, 0L).toScrollId();
370370
}

metadata-io/src/main/java/com/linkedin/metadata/search/elasticsearch/query/request/SearchRequestHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ public ScrollResult extractScrollResult(
452452
SearchHit[] searchHits = searchResponse.getHits().getHits();
453453
// Only return next scroll ID if there are more results, indicated by full size results
454454
String nextScrollId = null;
455-
if (searchHits.length == size) {
455+
if (searchHits.length == size && searchHits.length > 0) {
456456
Object[] sort = searchHits[searchHits.length - 1].getSortValues();
457457
long expirationTimeMs = 0L;
458458
if (keepAlive != null && supportsPointInTime) {

metadata-io/src/test/java/com/linkedin/metadata/graph/elastic/ElasticSearchGraphServiceTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.linkedin.metadata.graph.elastic;
22

33
import static io.datahubproject.test.search.SearchTestUtils.TEST_GRAPH_SERVICE_CONFIG;
4+
import static org.junit.Assert.assertNotNull;
45
import static org.junit.Assert.assertNull;
56
import static org.mockito.ArgumentMatchers.any;
67
import static org.mockito.ArgumentMatchers.anyInt;
@@ -254,6 +255,52 @@ public void testFindRelatedEntitiesNoResultsByType() {
254255
assertTrue(result.getEntities().isEmpty());
255256
}
256257

258+
@Test
259+
public void testScrollRelatedEntitiesWithZeroCount() {
260+
// Test the edge case where count=0 is passed, which should not cause
261+
// ArrayIndexOutOfBoundsException when accessing searchHits[searchHits.length - 1]
262+
OperationContext mockOpContext = TestOperationContexts.systemContextNoValidate();
263+
GraphFilters mockGraphFilters = GraphFilters.ALL;
264+
List<SortCriterion> mockSortCriteria = Collections.emptyList();
265+
266+
String scrollId = "test-scroll-id";
267+
String keepAlive = "1m";
268+
int count = 0; // This is the edge case we're testing
269+
270+
// Create mock search response with empty hits
271+
SearchResponse mockResponse = mock(SearchResponse.class);
272+
SearchHits mockHits = mock(SearchHits.class);
273+
when(mockResponse.getHits()).thenReturn(mockHits);
274+
275+
// Create empty search hits array (simulating no results)
276+
SearchHit[] hits = new SearchHit[0];
277+
when(mockHits.getHits()).thenReturn(hits);
278+
when(mockHits.getTotalHits()).thenReturn(new TotalHits(0L, TotalHits.Relation.EQUAL_TO));
279+
280+
when(mockReadDAO.getSearchResponse(any(), any(), any(), any(), any(), anyInt()))
281+
.thenReturn(mockResponse);
282+
when(mockReadDAO.getGraphServiceConfig()).thenReturn(TEST_GRAPH_SERVICE_CONFIG);
283+
284+
// This should not throw ArrayIndexOutOfBoundsException
285+
RelatedEntitiesScrollResult result =
286+
test.scrollRelatedEntities(
287+
mockOpContext,
288+
mockGraphFilters,
289+
mockSortCriteria,
290+
scrollId,
291+
keepAlive,
292+
count,
293+
null,
294+
null);
295+
296+
// Verify the result
297+
assertNotNull(result);
298+
assertEquals(result.getNumResults(), 0);
299+
assertEquals(result.getPageSize(), 0);
300+
assertTrue(result.getEntities().isEmpty());
301+
assertNull(result.getScrollId()); // No scroll ID since no results
302+
}
303+
257304
@Test
258305
public void testRaw() {
259306
// Create test edge tuples

metadata-io/src/test/java/com/linkedin/metadata/search/query/request/SearchRequestHandlerTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,43 @@ public void testExtractScrollResultPaginationLogic() {
13831383
assertFalse(result.hasScrollId());
13841384
}
13851385

1386+
@Test
1387+
public void testExtractScrollResultWithZeroCount() {
1388+
// Test the edge case where count=0 is passed, which should not cause
1389+
// ArrayIndexOutOfBoundsException
1390+
SearchRequestHandler handler =
1391+
SearchRequestHandler.getBuilder(
1392+
operationContext,
1393+
TestEntitySpecBuilder.getSpec(),
1394+
testQueryConfig,
1395+
null,
1396+
QueryFilterRewriteChain.EMPTY,
1397+
TEST_SEARCH_SERVICE_CONFIG);
1398+
1399+
SearchResponse mockResponse = mock(SearchResponse.class);
1400+
SearchHits mockHits = mock(SearchHits.class);
1401+
1402+
// Create empty search hits array (simulating no results)
1403+
SearchHit[] hits = new SearchHit[0];
1404+
1405+
when(mockResponse.getHits()).thenReturn(mockHits);
1406+
when(mockHits.getTotalHits()).thenReturn(new TotalHits(0L, TotalHits.Relation.EQUAL_TO));
1407+
when(mockHits.getHits()).thenReturn(hits);
1408+
when(mockResponse.getAggregations()).thenReturn(null);
1409+
when(mockResponse.getSuggest()).thenReturn(null);
1410+
when(mockResponse.pointInTimeId()).thenReturn("test-pit-id");
1411+
1412+
// This should not throw ArrayIndexOutOfBoundsException
1413+
ScrollResult result =
1414+
handler.extractScrollResult(operationContext, mockResponse, null, "5m", 0, true);
1415+
1416+
// Verify the result
1417+
assertNotNull(result);
1418+
assertEquals(result.getPageSize().intValue(), 0);
1419+
assertEquals(result.getNumEntities().intValue(), 0);
1420+
assertFalse(result.hasScrollId()); // No scroll ID since no results
1421+
}
1422+
13861423
// Helper method to create scroll results with specific sizes
13871424
private ScrollResult verifyScrollResultSize(
13881425
SearchRequestHandler handler,

0 commit comments

Comments
 (0)