fix(sort): fix out of bounds exception with count 0 (#15085)

This commit is contained in:
david-leifker 2025-10-22 21:09:38 -05:00 committed by GitHub
parent b9c263b0f6
commit a7f1c6b32f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 88 additions and 3 deletions

View File

@ -112,7 +112,8 @@ public class DataHubUsageServiceImpl implements DataHubUsageService {
response.count(searchHits.getHits().length); response.count(searchHits.getHits().length);
response.total((int) searchHits.getTotalHits().value); response.total((int) searchHits.getTotalHits().value);
String nextScrollId = null; String nextScrollId = null;
if (searchHits.getHits().length == analyticsSearchRequest.getSize()) { if (searchHits.getHits().length == analyticsSearchRequest.getSize()
&& searchHits.getHits().length > 0) {
Object[] sort = searchHits.getHits()[searchHits.getHits().length - 1].getSortValues(); Object[] sort = searchHits.getHits()[searchHits.getHits().length - 1].getSortValues();
nextScrollId = new SearchAfterWrapper(sort, null, 0L).toScrollId(); nextScrollId = new SearchAfterWrapper(sort, null, 0L).toScrollId();
} }

View File

@ -364,7 +364,7 @@ public class ElasticSearchGraphService implements GraphService, ElasticSearchInd
SearchHit[] searchHits = response.getHits().getHits(); SearchHit[] searchHits = response.getHits().getHits();
// Only return next scroll ID if there are more results, indicated by full size results // Only return next scroll ID if there are more results, indicated by full size results
String nextScrollId = null; String nextScrollId = null;
if (searchHits.length == count) { if (searchHits.length == count && searchHits.length > 0) {
Object[] sort = searchHits[searchHits.length - 1].getSortValues(); Object[] sort = searchHits[searchHits.length - 1].getSortValues();
nextScrollId = new SearchAfterWrapper(sort, null, 0L).toScrollId(); nextScrollId = new SearchAfterWrapper(sort, null, 0L).toScrollId();
} }

View File

@ -452,7 +452,7 @@ public class SearchRequestHandler extends BaseRequestHandler {
SearchHit[] searchHits = searchResponse.getHits().getHits(); SearchHit[] searchHits = searchResponse.getHits().getHits();
// Only return next scroll ID if there are more results, indicated by full size results // Only return next scroll ID if there are more results, indicated by full size results
String nextScrollId = null; String nextScrollId = null;
if (searchHits.length == size) { if (searchHits.length == size && searchHits.length > 0) {
Object[] sort = searchHits[searchHits.length - 1].getSortValues(); Object[] sort = searchHits[searchHits.length - 1].getSortValues();
long expirationTimeMs = 0L; long expirationTimeMs = 0L;
if (keepAlive != null && supportsPointInTime) { if (keepAlive != null && supportsPointInTime) {

View File

@ -1,6 +1,7 @@
package com.linkedin.metadata.graph.elastic; package com.linkedin.metadata.graph.elastic;
import static io.datahubproject.test.search.SearchTestUtils.TEST_GRAPH_SERVICE_CONFIG; import static io.datahubproject.test.search.SearchTestUtils.TEST_GRAPH_SERVICE_CONFIG;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
@ -254,6 +255,52 @@ public class ElasticSearchGraphServiceTest {
assertTrue(result.getEntities().isEmpty()); assertTrue(result.getEntities().isEmpty());
} }
@Test
public void testScrollRelatedEntitiesWithZeroCount() {
// Test the edge case where count=0 is passed, which should not cause
// ArrayIndexOutOfBoundsException when accessing searchHits[searchHits.length - 1]
OperationContext mockOpContext = TestOperationContexts.systemContextNoValidate();
GraphFilters mockGraphFilters = GraphFilters.ALL;
List<SortCriterion> mockSortCriteria = Collections.emptyList();
String scrollId = "test-scroll-id";
String keepAlive = "1m";
int count = 0; // This is the edge case we're testing
// Create mock search response with empty hits
SearchResponse mockResponse = mock(SearchResponse.class);
SearchHits mockHits = mock(SearchHits.class);
when(mockResponse.getHits()).thenReturn(mockHits);
// Create empty search hits array (simulating no results)
SearchHit[] hits = new SearchHit[0];
when(mockHits.getHits()).thenReturn(hits);
when(mockHits.getTotalHits()).thenReturn(new TotalHits(0L, TotalHits.Relation.EQUAL_TO));
when(mockReadDAO.getSearchResponse(any(), any(), any(), any(), any(), anyInt()))
.thenReturn(mockResponse);
when(mockReadDAO.getGraphServiceConfig()).thenReturn(TEST_GRAPH_SERVICE_CONFIG);
// This should not throw ArrayIndexOutOfBoundsException
RelatedEntitiesScrollResult result =
test.scrollRelatedEntities(
mockOpContext,
mockGraphFilters,
mockSortCriteria,
scrollId,
keepAlive,
count,
null,
null);
// Verify the result
assertNotNull(result);
assertEquals(result.getNumResults(), 0);
assertEquals(result.getPageSize(), 0);
assertTrue(result.getEntities().isEmpty());
assertNull(result.getScrollId()); // No scroll ID since no results
}
@Test @Test
public void testRaw() { public void testRaw() {
// Create test edge tuples // Create test edge tuples

View File

@ -1383,6 +1383,43 @@ public class SearchRequestHandlerTest extends AbstractTestNGSpringContextTests {
assertFalse(result.hasScrollId()); assertFalse(result.hasScrollId());
} }
@Test
public void testExtractScrollResultWithZeroCount() {
// Test the edge case where count=0 is passed, which should not cause
// ArrayIndexOutOfBoundsException
SearchRequestHandler handler =
SearchRequestHandler.getBuilder(
operationContext,
TestEntitySpecBuilder.getSpec(),
testQueryConfig,
null,
QueryFilterRewriteChain.EMPTY,
TEST_SEARCH_SERVICE_CONFIG);
SearchResponse mockResponse = mock(SearchResponse.class);
SearchHits mockHits = mock(SearchHits.class);
// Create empty search hits array (simulating no results)
SearchHit[] hits = new SearchHit[0];
when(mockResponse.getHits()).thenReturn(mockHits);
when(mockHits.getTotalHits()).thenReturn(new TotalHits(0L, TotalHits.Relation.EQUAL_TO));
when(mockHits.getHits()).thenReturn(hits);
when(mockResponse.getAggregations()).thenReturn(null);
when(mockResponse.getSuggest()).thenReturn(null);
when(mockResponse.pointInTimeId()).thenReturn("test-pit-id");
// This should not throw ArrayIndexOutOfBoundsException
ScrollResult result =
handler.extractScrollResult(operationContext, mockResponse, null, "5m", 0, true);
// Verify the result
assertNotNull(result);
assertEquals(result.getPageSize().intValue(), 0);
assertEquals(result.getNumEntities().intValue(), 0);
assertFalse(result.hasScrollId()); // No scroll ID since no results
}
// Helper method to create scroll results with specific sizes // Helper method to create scroll results with specific sizes
private ScrollResult verifyScrollResultSize( private ScrollResult verifyScrollResultSize(
SearchRequestHandler handler, SearchRequestHandler handler,