From 646fbcc3b99e1ddc84da84d9865f56cbb3a562f8 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:26:16 -0400 Subject: [PATCH] Fix issue with SearchPagedFlux and SearchPagedIterable --- eng/lintingconfigs/revapi/track2/revapi.json | 18 ++ .../azure-search-documents/CHANGELOG.md | 3 + .../search/documents/SearchAsyncClient.java | 21 +- .../azure/search/documents/SearchClient.java | 13 +- .../util/SearchPagedResponseAccessHelper.java | 193 ----------------- .../SemanticSearchResultsAccessHelper.java | 18 +- .../CoreToCodegenBridgeUtils.java | 196 ------------------ .../models/SemanticSearchResults.java | 15 +- .../documents/util/SearchPagedFlux.java | 14 +- .../documents/util/SearchPagedIterable.java | 21 +- .../documents/util/SearchPagedResponse.java | 144 ++++--------- .../azure/search/documents/SearchTests.java | 43 ++-- .../VectorSearchWithSharedIndexTests.java | 21 +- 13 files changed, 150 insertions(+), 570 deletions(-) delete mode 100644 sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SearchPagedResponseAccessHelper.java delete mode 100644 sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/indexes/implementation/CoreToCodegenBridgeUtils.java diff --git a/eng/lintingconfigs/revapi/track2/revapi.json b/eng/lintingconfigs/revapi/track2/revapi.json index d70aed935d08..a71891bc6794 100644 --- a/eng/lintingconfigs/revapi/track2/revapi.json +++ b/eng/lintingconfigs/revapi/track2/revapi.json @@ -636,6 +636,24 @@ "old": "method com.azure.search.documents.util.SearchPagedFlux com.azure.search.documents.SearchAsyncClient::search(com.azure.search.documents.implementation.models.SearchRequest, java.lang.String, com.azure.core.util.Context)", "justification": "Non-breaking change in Search 05-01-2025 preview to package-private api." }, + { + "ignore": true, + "code": "java.method.visibilityIncreased", + "old": "method java.lang.Long com.azure.search.documents.util.SearchPagedResponse::getCount()", + "justification": "Non-breaking change as class is final." + }, + { + "ignore": true, + "code": "java.method.visibilityIncreased", + "old": "method java.lang.Double com.azure.search.documents.util.SearchPagedResponse::getCoverage()", + "justification": "Non-breaking change as class is final." + }, + { + "ignore": true, + "code": "java.method.visibilityIncreased", + "old": "method java.util.Map> com.azure.search.documents.util.SearchPagedResponse::getFacets()", + "justification": "Non-breaking change as class is final." + }, { "code": "java.field.enumConstantOrderChanged", "old": { diff --git a/sdk/search/azure-search-documents/CHANGELOG.md b/sdk/search/azure-search-documents/CHANGELOG.md index 9078cf580779..1400e3d79d00 100644 --- a/sdk/search/azure-search-documents/CHANGELOG.md +++ b/sdk/search/azure-search-documents/CHANGELOG.md @@ -17,6 +17,9 @@ ### Bugs Fixed +- Fixed a bug where multiple iterations / subscriptions of `SearchPagedFlux` and `SearchPagedIterable` would return the + same first page result of the initial iteration / subscription. + ## 11.8.0 (2025-10-10) ### Features Added diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchAsyncClient.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchAsyncClient.java index 63c810cbe94f..ec8e269b3dee 100644 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchAsyncClient.java +++ b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchAsyncClient.java @@ -1277,9 +1277,6 @@ SearchPagedFlux search(String searchText, SearchOptions searchOptions, String qu private Mono search(SearchRequest request, String continuationToken, SearchFirstPageResponseWrapper firstPageResponseWrapper, String querySourceAuthorization, Context context) { - if (continuationToken == null && firstPageResponseWrapper.getFirstPageResponse() != null) { - return Mono.just(firstPageResponseWrapper.getFirstPageResponse()); - } SearchRequest requestToUse = (continuationToken == null) ? request : SearchContinuationToken.deserializeToken(serviceVersion.getVersion(), continuationToken); @@ -1288,13 +1285,7 @@ private Mono search(SearchRequest request, String continuat .searchPostWithResponseAsync(requestToUse, querySourceAuthorization, null, context) .onErrorMap(MappingUtils::exceptionMapper) .map(response -> { - SearchDocumentsResult result = response.getValue(); - - SearchPagedResponse page - = new SearchPagedResponse(new SimpleResponse<>(response, getSearchResults(result, serializer)), - createContinuationToken(result, serviceVersion), result.getFacets(), result.getCount(), - result.getCoverage(), result.getAnswers(), result.getSemanticPartialResponseReason(), - result.getSemanticPartialResponseType()); + SearchPagedResponse page = mapToSearchPagedResponse(response, serializer, serviceVersion); if (continuationToken == null) { firstPageResponseWrapper.setFirstPageResponse(page); } @@ -1314,6 +1305,16 @@ static String createContinuationToken(SearchDocumentsResult result, ServiceVersi result.getNextPageParameters()); } + static SearchPagedResponse mapToSearchPagedResponse(Response response, + JsonSerializer serializer, SearchServiceVersion serviceVersion) { + SearchDocumentsResult result = response.getValue(); + return new SearchPagedResponse(new SimpleResponse<>(response, getSearchResults(result, serializer)), + createContinuationToken(result, serviceVersion), result.getFacets(), result.getCount(), + result.getCoverage(), result.getAnswers(), result.getSemanticPartialResponseReason(), + result.getSemanticPartialResponseType(), result.getDebugInfo(), + result.getSemanticQueryRewritesResultType()); + } + /** * Suggests documents in the index that match the given partial query. * diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchClient.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchClient.java index c2d2aa7b3bd3..e523f443a73e 100644 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchClient.java +++ b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/SearchClient.java @@ -49,10 +49,8 @@ import static com.azure.core.util.serializer.TypeReference.createInstance; import static com.azure.search.documents.SearchAsyncClient.buildIndexBatch; import static com.azure.search.documents.SearchAsyncClient.createAutoCompleteRequest; -import static com.azure.search.documents.SearchAsyncClient.createContinuationToken; import static com.azure.search.documents.SearchAsyncClient.createSearchRequest; import static com.azure.search.documents.SearchAsyncClient.createSuggestRequest; -import static com.azure.search.documents.SearchAsyncClient.getSearchResults; import static com.azure.search.documents.SearchAsyncClient.getSuggestResults; /** @@ -1111,9 +1109,6 @@ public SearchPagedIterable search(String searchText, SearchOptions searchOptions private SearchPagedResponse search(SearchRequest request, String continuationToken, SearchFirstPageResponseWrapper firstPageResponseWrapper, String querySourceAuthorization, Context context) { - if (continuationToken == null && firstPageResponseWrapper.getFirstPageResponse() != null) { - return firstPageResponseWrapper.getFirstPageResponse(); - } SearchRequest requestToUse = (continuationToken == null) ? request : SearchContinuationToken.deserializeToken(serviceVersion.getVersion(), continuationToken); @@ -1121,13 +1116,7 @@ private SearchPagedResponse search(SearchRequest request, String continuationTok return Utility.executeRestCallWithExceptionHandling(() -> { Response response = restClient.getDocuments() .searchPostWithResponse(requestToUse, querySourceAuthorization, null, context); - SearchDocumentsResult result = response.getValue(); - SearchPagedResponse page - = new SearchPagedResponse(new SimpleResponse<>(response, getSearchResults(result, serializer)), - createContinuationToken(result, serviceVersion), result.getFacets(), result.getCount(), - result.getCoverage(), result.getAnswers(), result.getSemanticPartialResponseReason(), - result.getSemanticPartialResponseType(), result.getDebugInfo(), - result.getSemanticQueryRewritesResultType()); + SearchPagedResponse page = SearchAsyncClient.mapToSearchPagedResponse(response, serializer, serviceVersion); if (continuationToken == null) { firstPageResponseWrapper.setFirstPageResponse(page); } diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SearchPagedResponseAccessHelper.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SearchPagedResponseAccessHelper.java deleted file mode 100644 index 4dc45d01252f..000000000000 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SearchPagedResponseAccessHelper.java +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.search.documents.implementation.util; - -import com.azure.search.documents.models.DebugInfo; -import com.azure.search.documents.models.FacetResult; -import com.azure.search.documents.models.QueryAnswerResult; -import com.azure.search.documents.models.SemanticErrorReason; -import com.azure.search.documents.models.SemanticQueryRewritesResultType; -import com.azure.search.documents.models.SemanticSearchResultsType; -import com.azure.search.documents.util.SearchPagedResponse; - -import java.util.List; -import java.util.Map; - -/** - * Helper class to access internal values of {@link SearchPagedResponse}. - */ -public final class SearchPagedResponseAccessHelper { - private SearchPagedResponseAccessHelper() { - } - - private static SearchPagedResponseAccessor accessor; - - public interface SearchPagedResponseAccessor { - /** - * The percentage of the index covered in the search request. - *

- * If {@code minimumCoverage} wasn't supplied in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The percentage of the index covered in the search request if {@code minimumCoverage} was set in the - * request, otherwise null. - */ - Double getCoverage(SearchPagedResponse response); - - /** - * The facet query results based on the search request. - *

- * If {@code facets} weren't supplied in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The facet query results if {@code facets} were supplied in the request, otherwise null. - */ - Map> getFacets(SearchPagedResponse response); - - /** - * The approximate number of documents that matched the search and filter parameters in the request. - *

- * If {@code count} is set to {@code false} in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The approximate number of documents that match the request if {@code count} is {@code true}, otherwise - * null. - */ - Long getCount(SearchPagedResponse response); - - /** - * The answer results based on the search request. - *

- * If {@code answers} wasn't supplied in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The answer results if {@code answers} were supplied in the request, otherwise null. - */ - List getQueryAnswers(SearchPagedResponse response); - - /** - * The reason that a partial response was returned for a semantic search request. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The reason that a partial response was returned for a semantic search request. - */ - SemanticErrorReason getSemanticErrorReason(SearchPagedResponse response); - - /** - * Get the semanticPartialResponseType property: Type of partial response that was returned for a semantic search - * request. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return the semanticPartialResponseType value. - */ - SemanticSearchResultsType getSemanticSearchResultsType(SearchPagedResponse response); - - /** - * Get the debugInfo property: Contains debugging information that can be used to further explore your search - * results. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return the debugInfo value. - */ - DebugInfo getDebugInfo(SearchPagedResponse response); - - /** - * Get the semanticQueryRewritesResultType property: Type of query rewrite that was used to retrieve documents. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return the semanticQueryRewritesResultType value. - */ - SemanticQueryRewritesResultType getSemanticQueryRewritesResultType(SearchPagedResponse response); - } - - /** - * The percentage of the index covered in the search request. - *

- * If {@code minimumCoverage} wasn't supplied in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The percentage of the index covered in the search request if {@code minimumCoverage} was set in the - * request, otherwise null. - */ - public static Double getCoverage(SearchPagedResponse response) { - return accessor.getCoverage(response); - } - - /** - * The facet query results based on the search request. - *

- * If {@code facets} weren't supplied in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The facet query results if {@code facets} were supplied in the request, otherwise null. - */ - public static Map> getFacets(SearchPagedResponse response) { - return accessor.getFacets(response); - } - - /** - * The approximate number of documents that matched the search and filter parameters in the request. - *

- * If {@code count} is set to {@code false} in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The approximate number of documents that match the request if {@code count} is {@code true}, otherwise - * null. - */ - public static Long getCount(SearchPagedResponse response) { - return accessor.getCount(response); - } - - /** - * The answer results based on the search request. - *

- * If {@code answers} wasn't supplied in the request this will be null. - * - * @param response The {@link SearchPagedResponse} being accessed. - * @return The answer results if {@code answers} were supplied in the request, otherwise null. - */ - public static List getQueryAnswers(SearchPagedResponse response) { - return accessor.getQueryAnswers(response); - } - - /** - * The reason that a partial response was returned for a semantic search request. - * @param response The {@link SearchPagedResponse} being accessed. - * @return The reason that a partial response was returned for a semantic search request. - */ - public static SemanticErrorReason getSemanticErrorReason(SearchPagedResponse response) { - return accessor.getSemanticErrorReason(response); - } - - /** - * The type of partial response that was returned for a semantic search request. - * @param response The {@link SearchPagedResponse} being accessed. - * @return The type of partial response that was returned for a semantic search request. - */ - public static SemanticSearchResultsType getSemanticSearchResultsType(SearchPagedResponse response) { - return accessor.getSemanticSearchResultsType(response); - } - - /** - * Contains debugging information that can be used to further explore your search results. - * @param response The {@link SearchPagedResponse} being accessed. - * @return The debugging information that can be used to further explore your search results. - */ - public static DebugInfo getDebugInfo(SearchPagedResponse response) { - return accessor.getDebugInfo(response); - } - - /** - * Type of query rewrite that was used to retrieve documents. - * @param response The {@link SearchPagedResponse} being accessed. - * @return The type of query rewrite that was used to retrieve documents. - */ - public static SemanticQueryRewritesResultType getSemanticQueryRewritesResultType(SearchPagedResponse response) { - return accessor.getSemanticQueryRewritesResultType(response); - } - - public static void setAccessor(SearchPagedResponseAccessor searchPagedResponseAccessor) { - accessor = searchPagedResponseAccessor; - } -} diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SemanticSearchResultsAccessHelper.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SemanticSearchResultsAccessHelper.java index 4a4651ddb23c..c99d29f4e2a9 100644 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SemanticSearchResultsAccessHelper.java +++ b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/implementation/util/SemanticSearchResultsAccessHelper.java @@ -2,8 +2,13 @@ // Licensed under the MIT License. package com.azure.search.documents.implementation.util; +import com.azure.search.documents.models.QueryAnswerResult; +import com.azure.search.documents.models.SemanticErrorReason; +import com.azure.search.documents.models.SemanticQueryRewritesResultType; import com.azure.search.documents.models.SemanticSearchResults; -import com.azure.search.documents.util.SearchPagedResponse; +import com.azure.search.documents.models.SemanticSearchResultsType; + +import java.util.List; /** * Helper class to access internals of {@link SemanticSearchResults}. @@ -15,14 +20,18 @@ private SemanticSearchResultsAccessHelper() { private static SemanticSearchResultsAccessor accessor; public interface SemanticSearchResultsAccessor { - SemanticSearchResults create(SearchPagedResponse pagedResponse); + SemanticSearchResults create(List queryAnswers, SemanticErrorReason semanticErrorReason, + SemanticSearchResultsType semanticSearchResultsType, + SemanticQueryRewritesResultType semanticQueryRewritesResultType); } public static void setAccessor(final SemanticSearchResultsAccessor newAccessor) { accessor = newAccessor; } - public static SemanticSearchResults create(SearchPagedResponse pagedResponse) { + public static SemanticSearchResults create(List queryAnswers, + SemanticErrorReason semanticErrorReason, SemanticSearchResultsType semanticSearchResultsType, + SemanticQueryRewritesResultType semanticQueryRewritesResultType) { if (accessor == null) { try { Class.forName(SemanticSearchResults.class.getName(), true, @@ -33,6 +42,7 @@ public static SemanticSearchResults create(SearchPagedResponse pagedResponse) { } assert accessor != null; - return accessor.create(pagedResponse); + return accessor.create(queryAnswers, semanticErrorReason, semanticSearchResultsType, + semanticQueryRewritesResultType); } } diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/indexes/implementation/CoreToCodegenBridgeUtils.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/indexes/implementation/CoreToCodegenBridgeUtils.java deleted file mode 100644 index de981e670711..000000000000 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/indexes/implementation/CoreToCodegenBridgeUtils.java +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// -// Code generated by Microsoft (R) AutoRest Code Generator. -// Changes may cause incorrect behavior and will be lost if the code is regenerated. - -package com.azure.search.documents.indexes.implementation; - -import com.azure.core.models.ResponseError; -import com.azure.json.JsonReader; -import com.azure.json.JsonToken; -import com.azure.json.JsonWriter; -import java.io.IOException; -import java.time.Duration; - -/** - * Utility class that handles functionality not yet available in azure-core that generated code requires. - */ -public final class CoreToCodegenBridgeUtils { - /** - * Writes the object to the passed {@link ResponseError}. - * - * @param jsonWriter Where the {@link ResponseError} JSON will be written. - * @return The {@link JsonWriter} where the JSON was written. - * @throws IOException If the {@link ResponseError} fails to be written to the {@code jsonWriter}. - */ - public static JsonWriter responseErrorToJson(JsonWriter jsonWriter, ResponseError responseError) - throws IOException { - return jsonWriter.writeStartObject() - .writeStringField("code", responseError.getCode()) - .writeStringField("message", responseError.getMessage()) - .writeEndObject(); - } - - /** - * Reads a JSON stream into a {@link ResponseError}. - * - * @param jsonReader The {@link JsonReader} being read. - * @return The {@link ResponseError} that the JSON stream represented, or null if it pointed to JSON null. - * @throws IllegalStateException If the deserialized JSON object was missing any required properties. - * @throws IOException If a {@link ResponseError} fails to be read from the {@code jsonReader}. - */ - public static ResponseError responseErrorFromJson(JsonReader jsonReader) throws IOException { - return jsonReader.readObject(reader -> { - // Buffer the next JSON object as ResponseError can take two forms: - // - // - A ResponseError object - // - A ResponseError object wrapped in an "error" node. - JsonReader bufferedReader = reader.bufferObject(); - bufferedReader.nextToken(); // Get to the START_OBJECT token. - while (bufferedReader.nextToken() != JsonToken.END_OBJECT) { - String fieldName = bufferedReader.getFieldName(); - bufferedReader.nextToken(); - - if ("error".equals(fieldName)) { - // If the ResponseError was wrapped in the "error" node begin reading it now. - return readResponseError(bufferedReader); - } else { - bufferedReader.skipChildren(); - } - } - - // Otherwise reset the JsonReader and read the whole JSON object. - return readResponseError(bufferedReader.reset()); - }); - } - - private static ResponseError readResponseError(JsonReader jsonReader) throws IOException { - return jsonReader.readObject(reader -> { - String code = null; - boolean codeFound = false; - String message = null; - boolean messageFound = false; - - while (reader.nextToken() != JsonToken.END_OBJECT) { - String fieldName = reader.getFieldName(); - reader.nextToken(); - - if ("code".equals(fieldName)) { - code = reader.getString(); - codeFound = true; - } else if ("message".equals(fieldName)) { - message = reader.getString(); - messageFound = true; - } else { - reader.skipChildren(); - } - } - - if (!codeFound && !messageFound) { - throw new IllegalStateException("Missing required properties: code, message"); - } else if (!codeFound) { - throw new IllegalStateException("Missing required property: code"); - } else if (!messageFound) { - throw new IllegalStateException("Missing required property: message"); - } - - return new ResponseError(code, message); - }); - } - - /** - * Converts a {@link Duration} to a string in ISO-8601 format with support for a day component. - * - * @param duration The {@link Duration} to convert. - * @return The {@link Duration} as a string in ISO-8601 format with support for a day component, or null if the - * provided {@link Duration} was null. - */ - public static String durationToStringWithDays(Duration duration) { - if (duration == null) { - return null; - } - - if (duration.isZero()) { - return "PT0S"; - } - - StringBuilder builder = new StringBuilder(); - - if (duration.isNegative()) { - builder.append("-P"); - duration = duration.negated(); - } else { - builder.append('P'); - } - - long days = duration.toDays(); - if (days > 0) { - builder.append(days); - builder.append('D'); - duration = duration.minusDays(days); - } - - long hours = duration.toHours(); - if (hours > 0) { - builder.append('T'); - builder.append(hours); - builder.append('H'); - duration = duration.minusHours(hours); - } - - final long minutes = duration.toMinutes(); - if (minutes > 0) { - if (hours == 0) { - builder.append('T'); - } - - builder.append(minutes); - builder.append('M'); - duration = duration.minusMinutes(minutes); - } - - final long seconds = duration.getSeconds(); - if (seconds > 0) { - if (hours == 0 && minutes == 0) { - builder.append('T'); - } - - builder.append(seconds); - duration = duration.minusSeconds(seconds); - } - - long milliseconds = duration.toMillis(); - if (milliseconds > 0) { - if (hours == 0 && minutes == 0 && seconds == 0) { - builder.append("T"); - } - - if (seconds == 0) { - builder.append("0"); - } - - builder.append('.'); - - if (milliseconds <= 99) { - builder.append('0'); - - if (milliseconds <= 9) { - builder.append('0'); - } - } - - // Remove trailing zeros. - while (milliseconds % 10 == 0) { - milliseconds /= 10; - } - builder.append(milliseconds); - } - - if (seconds > 0 || milliseconds > 0) { - builder.append('S'); - } - - return builder.toString(); - } -} diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/models/SemanticSearchResults.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/models/SemanticSearchResults.java index 8295e56aaff0..8ac26496fc2f 100644 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/models/SemanticSearchResults.java +++ b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/models/SemanticSearchResults.java @@ -2,9 +2,7 @@ // Licensed under the MIT License. package com.azure.search.documents.models; -import com.azure.search.documents.implementation.util.SearchPagedResponseAccessHelper; import com.azure.search.documents.implementation.util.SemanticSearchResultsAccessHelper; -import com.azure.search.documents.util.SearchPagedResponse; import java.util.List; @@ -21,12 +19,13 @@ public final class SemanticSearchResults { SemanticSearchResultsAccessHelper.setAccessor(SemanticSearchResults::new); } - private SemanticSearchResults(SearchPagedResponse pagedResponse) { - this.queryAnswers = SearchPagedResponseAccessHelper.getQueryAnswers(pagedResponse); - this.errorReason = SearchPagedResponseAccessHelper.getSemanticErrorReason(pagedResponse); - this.resultsType = SearchPagedResponseAccessHelper.getSemanticSearchResultsType(pagedResponse); - this.semanticQueryRewritesResultType - = SearchPagedResponseAccessHelper.getSemanticQueryRewritesResultType(pagedResponse); + private SemanticSearchResults(List queryAnswers, SemanticErrorReason semanticErrorReason, + SemanticSearchResultsType semanticSearchResultsType, + SemanticQueryRewritesResultType semanticQueryRewritesResultType) { + this.queryAnswers = queryAnswers; + this.errorReason = semanticErrorReason; + this.resultsType = semanticSearchResultsType; + this.semanticQueryRewritesResultType = semanticQueryRewritesResultType; } /** diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedFlux.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedFlux.java index d6e7964c1a3a..977e8eb5f1c0 100644 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedFlux.java +++ b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedFlux.java @@ -7,7 +7,6 @@ import com.azure.core.util.paging.ContinuablePagedFlux; import com.azure.search.documents.implementation.models.SearchFirstPageResponseWrapper; import com.azure.search.documents.implementation.models.SearchRequest; -import com.azure.search.documents.implementation.util.SemanticSearchResultsAccessHelper; import com.azure.search.documents.models.DebugInfo; import com.azure.search.documents.models.FacetResult; import com.azure.search.documents.models.SearchResult; @@ -58,7 +57,9 @@ public SearchPagedFlux(Supplier> firstPageRetriever, * * @return The approximate number of documents that match the request if {@code count} is {@code true}, otherwise * {@code null}. + * @deprecated Use {@link SearchPagedResponse#getCount()} when consuming {@link #byPage()}. */ + @Deprecated public Mono getTotalCount() { return metadataSupplier.get().flatMap(metaData -> { if (metaData.getFirstPageResponse().getCount() == null) { @@ -75,7 +76,9 @@ public Mono getTotalCount() { * * @return The percentage of the index covered in the search request if {@code minimumCoverage} was set in the * request, otherwise {@code null}. + * @deprecated Use {@link SearchPagedResponse#getCoverage()} when consuming {@link #byPage()}. */ + @Deprecated public Mono getCoverage() { return metadataSupplier.get().flatMap(metaData -> { if (metaData.getFirstPageResponse().getCoverage() == null) { @@ -91,7 +94,9 @@ public Mono getCoverage() { * If {@code facets} weren't supplied in the request this will be {@code null}. * * @return The facet query results if {@code facets} were supplied in the request, otherwise {@code null}. + * @deprecated Use {@link SearchPagedResponse#getFacets()} when consuming {@link #byPage()}. */ + @Deprecated public Mono>> getFacets() { return metadataSupplier.get().flatMap(metaData -> { if (metaData.getFirstPageResponse().getFacets() == null) { @@ -108,17 +113,20 @@ public Mono>> getFacets() { * * @return The semantic search results if semantic search was requested, otherwise an empty * {@link SemanticSearchResults}. + * @deprecated Use {@link SearchPagedResponse#getSemanticResults()} when consuming {@link #byPage()}. */ + @Deprecated public Mono getSemanticResults() { - return metadataSupplier.get() - .map(metadata -> SemanticSearchResultsAccessHelper.create(metadata.getFirstPageResponse())); + return metadataSupplier.get().map(metadata -> metadata.getFirstPageResponse().getSemanticResults()); } /** * The debug information that can be used to further explore your search results. * * @return The debug information that can be used to further explore your search results. + * @deprecated Use {@link SearchPagedResponse#getDebugInfo()} when consuming {@link #byPage()}. */ + @Deprecated public Mono getDebugInfo() { return metadataSupplier.get().flatMap(metaData -> { if (metaData.getFirstPageResponse().getDebugInfo() == null) { diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedIterable.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedIterable.java index e94b97d60374..eedda34fdbce 100644 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedIterable.java +++ b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedIterable.java @@ -8,7 +8,6 @@ import com.azure.core.util.paging.PageRetrieverSync; import com.azure.search.documents.implementation.models.SearchFirstPageResponseWrapper; import com.azure.search.documents.implementation.models.SearchRequest; -import com.azure.search.documents.implementation.util.SemanticSearchResultsAccessHelper; import com.azure.search.documents.models.DebugInfo; import com.azure.search.documents.models.FacetResult; import com.azure.search.documents.models.SearchResult; @@ -31,7 +30,10 @@ public final class SearchPagedIterable extends PagedIterableBase> getFacets() { return metadataSupplier != null ? metadataSupplier.get().getFirstPageResponse().getFacets() @@ -113,7 +121,10 @@ public Map> getFacets() { * * @return The approximate number of documents that match the request if {@code count} is {@code true}, otherwise * {@code null}. + * @deprecated Use {@link SearchPagedResponse#getCount()} when consuming {@link #streamByPage()} or + * {@link #iterableByPage()}. */ + @Deprecated public Long getTotalCount() { return metadataSupplier != null ? metadataSupplier.get().getFirstPageResponse().getCount() @@ -127,10 +138,13 @@ public Long getTotalCount() { * * @return The semantic search results if semantic search was requested, otherwise an empty * {@link SemanticSearchResults}. + * @deprecated Use {@link SearchPagedResponse#getSemanticResults()} when consuming {@link #streamByPage()} or + * {@link #iterableByPage()}. */ + @Deprecated public SemanticSearchResults getSemanticResults() { return metadataSupplier != null - ? SemanticSearchResultsAccessHelper.create(metadataSupplier.get().getFirstPageResponse()) + ? metadataSupplier.get().getFirstPageResponse().getSemanticResults() : pagedFlux.getSemanticResults().block(); } @@ -138,7 +152,10 @@ public SemanticSearchResults getSemanticResults() { * The debug information that can be used to further explore your search results. * * @return The debug information that can be used to further explore your search results. + * @deprecated Use {@link SearchPagedResponse#getDebugInfo()} when consuming {@link #streamByPage()} or + * {@link #iterableByPage()}. */ + @Deprecated public DebugInfo getDebugInfo() { return metadataSupplier != null ? metadataSupplier.get().getFirstPageResponse().getDebugInfo() diff --git a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedResponse.java b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedResponse.java index 068c66f6655d..59a67eaeb5d5 100644 --- a/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedResponse.java +++ b/sdk/search/azure-search-documents/src/main/java/com/azure/search/documents/util/SearchPagedResponse.java @@ -7,13 +7,14 @@ import com.azure.core.http.rest.Page; import com.azure.core.http.rest.PagedResponseBase; import com.azure.core.http.rest.Response; -import com.azure.search.documents.implementation.util.SearchPagedResponseAccessHelper; +import com.azure.search.documents.implementation.util.SemanticSearchResultsAccessHelper; import com.azure.search.documents.models.DebugInfo; import com.azure.search.documents.models.FacetResult; import com.azure.search.documents.models.QueryAnswerResult; import com.azure.search.documents.models.SearchResult; import com.azure.search.documents.models.SemanticErrorReason; import com.azure.search.documents.models.SemanticQueryRewritesResultType; +import com.azure.search.documents.models.SemanticSearchResults; import com.azure.search.documents.models.SemanticSearchResultsType; import java.util.List; @@ -32,56 +33,8 @@ public final class SearchPagedResponse extends PagedResponseBase> facets; - private final List queryAnswers; + private final SemanticSearchResults semanticSearchResults; private final DebugInfo debugInfo; - private final SemanticErrorReason semanticErrorReason; - private final SemanticSearchResultsType semanticSearchResultsType; - private final SemanticQueryRewritesResultType semanticQueryRewritesResultType; - - static { - SearchPagedResponseAccessHelper.setAccessor(new SearchPagedResponseAccessHelper.SearchPagedResponseAccessor() { - @Override - public Double getCoverage(SearchPagedResponse response) { - return response.getCoverage(); - } - - @Override - public Map> getFacets(SearchPagedResponse response) { - return response.getFacets(); - } - - @Override - public Long getCount(SearchPagedResponse response) { - return response.getCount(); - } - - @Override - public List getQueryAnswers(SearchPagedResponse response) { - return response.getQueryAnswers(); - } - - @Override - public SemanticErrorReason getSemanticErrorReason(SearchPagedResponse response) { - return response.getSemanticErrorReason(); - } - - @Override - public SemanticSearchResultsType getSemanticSearchResultsType(SearchPagedResponse response) { - return response.getSemanticSearchResultsType(); - } - - @Override - public DebugInfo getDebugInfo(SearchPagedResponse response) { - return response.getDebugInfo(); - } - - @Override - public SemanticQueryRewritesResultType getSemanticQueryRewritesResultType(SearchPagedResponse response) { - return response.getSemanticQueryRewritesResultType(); - } - - }); - } /** * Constructor @@ -91,7 +44,9 @@ public SemanticQueryRewritesResultType getSemanticQueryRewritesResultType(Search * @param facets Facets contained in the search. * @param count Total number of documents available as a result for the search. * @param coverage Percent of the index used in the search operation. + * @deprecated Use {@link SearchPagedResponse#SearchPagedResponse(Response, String, Map, Long, Double, List, SemanticErrorReason, SemanticSearchResultsType, DebugInfo, SemanticQueryRewritesResultType)} */ + @Deprecated public SearchPagedResponse(Response> response, String continuationToken, Map> facets, Long count, Double coverage) { this(response, continuationToken, facets, count, coverage, null, null, null); @@ -108,7 +63,9 @@ public SearchPagedResponse(Response> response, String continu * @param queryAnswers Answers contained in the search. * @param semanticErrorReason Reason that a partial response was returned for a semantic search request. * @param semanticSearchResultsType Type of the partial response returned for a semantic search request. + * @deprecated Use {@link SearchPagedResponse#SearchPagedResponse(Response, String, Map, Long, Double, List, SemanticErrorReason, SemanticSearchResultsType, DebugInfo, SemanticQueryRewritesResultType)} */ + @Deprecated public SearchPagedResponse(Response> response, String continuationToken, Map> facets, Long count, Double coverage, List queryAnswers, SemanticErrorReason semanticErrorReason, SemanticSearchResultsType semanticSearchResultsType) { @@ -141,95 +98,64 @@ public SearchPagedResponse(Response> response, String continu this.facets = facets; this.count = count; this.coverage = coverage; - this.queryAnswers = queryAnswers; - this.semanticErrorReason = semanticErrorReason; - this.semanticSearchResultsType = semanticSearchResultsType; + this.semanticSearchResults = SemanticSearchResultsAccessHelper.create(queryAnswers, semanticErrorReason, + semanticSearchResultsType, semanticQueryRewritesResultType); this.debugInfo = debugInfo; - this.semanticQueryRewritesResultType = semanticQueryRewritesResultType; } /** - * The percentage of the index covered in the search request. - *

- * If {@code minimumCoverage} wasn't supplied in the request this will be null. + * Get the count property: The total count of results found by the search operation, or null if the count was not + * requested. If present, the count may be greater than the number of results in this response. This can happen if + * you use the $top or $skip parameters, or if the query can't return all the requested documents in a single + * response. * - * @return The percentage of the index covered in the search request if {@code minimumCoverage} was set in the - * request, otherwise null. + * @return the count value. */ - Double getCoverage() { - return coverage; + public Long getCount() { + return this.count; } /** - * The facet query results based on the search request. - *

- * If {@code facets} weren't supplied in the request this will be null. + * Get the coverage property: A value indicating the percentage of the index that was included in the query, or null + * if minimumCoverage was not specified in the request. * - * @return The facet query results if {@code facets} were supplied in the request, otherwise null. + * @return the coverage value. */ - Map> getFacets() { - return facets; + public Double getCoverage() { + return this.coverage; } /** - * The approximate number of documents that matched the search and filter parameters in the request. - *

- * If {@code count} is set to {@code false} in the request this will be null. + * Get the facets property: The facet query results for the search operation, organized as a collection of buckets + * for each faceted field; null if the query did not include any facet expressions. * - * @return The approximate number of documents that match the request if {@code count} is {@code true}, otherwise - * null. + * @return the facets value. */ - Long getCount() { - return count; + public Map> getFacets() { + return this.facets; } /** - * The answer results based on the search request. + * The semantic search results based on the search request. *

- * If {@code answers} wasn't supplied in the request this will be null. - * - * @return The answer results if {@code answers} were supplied in the request, otherwise null. - */ - List getQueryAnswers() { - return queryAnswers; - } - - /** - * The reason that a partial response was returned for a semantic search request. - * - * @return Reason that a partial response was returned for a semantic search request if response was partial. - */ - SemanticErrorReason getSemanticErrorReason() { - return semanticErrorReason; - } - - /** - * The type of the partial response returned for a semantic search request. + * If semantic search wasn't requested this will return a {@link SemanticSearchResults} with no values. * - * @return Type of the partial response returned for a semantic search request if response was partial. + * @return The semantic search results if semantic search was requested, otherwise an empty + * {@link SemanticSearchResults}. */ - SemanticSearchResultsType getSemanticSearchResultsType() { - return semanticSearchResultsType; + public SemanticSearchResults getSemanticResults() { + return semanticSearchResults; } /** - * The debug information that can be used to further explore your search results. + * Get the debugInfo property: Debug information that applies to the search results as a whole. * - * @return The debug information that can be used to further explore your search results. + * @return the debugInfo value. */ - DebugInfo getDebugInfo() { + public DebugInfo getDebugInfo() { return debugInfo; } - /** - * The type of the partial response returned for a semantic query rewrites request. - * - * @return Type of the partial response returned for a semantic query rewrites request if response was partial. - */ - SemanticQueryRewritesResultType getSemanticQueryRewritesResultType() { - return semanticQueryRewritesResultType; - } - @Override public List getValue() { return value; diff --git a/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/SearchTests.java b/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/SearchTests.java index bd961cfd7f35..50f9194f3436 100644 --- a/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/SearchTests.java +++ b/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/SearchTests.java @@ -9,7 +9,6 @@ import com.azure.core.test.TestProxyTestBase; import com.azure.core.test.annotation.LiveOnly; import com.azure.core.util.Context; -import com.azure.search.documents.implementation.util.SearchPagedResponseAccessHelper; import com.azure.search.documents.indexes.SearchIndexClient; import com.azure.search.documents.indexes.models.SearchField; import com.azure.search.documents.indexes.models.SearchFieldDataType; @@ -190,9 +189,9 @@ public void canSearchDynamicDocumentsSync() { = hotels.stream().collect(Collectors.toMap(h -> h.get("HotelId").toString(), Function.identity())); for (SearchPagedResponse response : client.search("*").iterableByPage()) { - assertNull(SearchPagedResponseAccessHelper.getCount(response)); - assertNull(SearchPagedResponseAccessHelper.getCoverage(response)); - assertNull(SearchPagedResponseAccessHelper.getFacets(response)); + assertNull(response.getCount()); + assertNull(response.getCoverage()); + assertNull(response.getFacets()); response.getElements().forEach(item -> { assertEquals(1, item.getScore(), 0); @@ -218,9 +217,9 @@ public void canSearchDynamicDocumentsAsync() { = hotels.stream().collect(Collectors.toMap(h -> h.get("HotelId").toString(), Function.identity())); StepVerifier.create(asyncClient.search("*").byPage()).thenConsumeWhile(response -> { - assertNull(SearchPagedResponseAccessHelper.getCount(response)); - assertNull(SearchPagedResponseAccessHelper.getCoverage(response)); - assertNull(SearchPagedResponseAccessHelper.getFacets(response)); + assertNull(response.getCount()); + assertNull(response.getCoverage()); + assertNull(response.getFacets()); response.getElements().forEach(item -> { assertEquals(1, item.getScore(), 0); @@ -357,9 +356,9 @@ public void canSearchStaticallyTypedDocumentsSync() { .collect(Collectors.toMap(Hotel::hotelId, Function.identity())); for (SearchPagedResponse response : client.search("*", new SearchOptions(), Context.NONE).iterableByPage()) { - assertNull(SearchPagedResponseAccessHelper.getCount(response)); - assertNull(SearchPagedResponseAccessHelper.getCoverage(response)); - assertNull(SearchPagedResponseAccessHelper.getFacets(response)); + assertNull(response.getCount()); + assertNull(response.getCoverage()); + assertNull(response.getFacets()); response.getElements().forEach(sr -> { assertEquals(1, sr.getScore(), 0); @@ -385,9 +384,9 @@ public void canSearchStaticallyTypedDocumentsAsync() { .collect(Collectors.toMap(Hotel::hotelId, Function.identity())); StepVerifier.create(asyncClient.search("*", new SearchOptions()).byPage()).thenConsumeWhile(response -> { - assertNull(SearchPagedResponseAccessHelper.getCount(response)); - assertNull(SearchPagedResponseAccessHelper.getCoverage(response)); - assertNull(SearchPagedResponseAccessHelper.getFacets(response)); + assertNull(response.getCount()); + assertNull(response.getCoverage()); + assertNull(response.getFacets()); response.getElements().forEach(sr -> { assertEquals(1, sr.getScore(), 0); @@ -722,7 +721,7 @@ public void canSearchWithRangeFacetsSync() { for (SearchPagedResponse response : client.search("*", getSearchOptionsForRangeFacets(), Context.NONE) .iterableByPage()) { - Map> facets = SearchPagedResponseAccessHelper.getFacets(response); + Map> facets = response.getFacets(); assertNotNull(facets); List> baseRateFacets = getRangeFacetsForField(facets, "Rooms/BaseRate", 4); @@ -745,7 +744,7 @@ public void canSearchWithRangeFacetsAsync() { StepVerifier.create(asyncClient.search("*", getSearchOptionsForRangeFacets()).byPage()) .thenConsumeWhile(response -> { - Map> facets = SearchPagedResponseAccessHelper.getFacets(response); + Map> facets = response.getFacets(); assertNotNull(facets); List> baseRateFacets = getRangeFacetsForField(facets, "Rooms/BaseRate", 4); @@ -768,7 +767,7 @@ public void canSearchWithValueFacetsSync() { for (SearchPagedResponse response : client.search("*", getSearchOptionsForValueFacets(), Context.NONE) .iterableByPage()) { - Map> facets = SearchPagedResponseAccessHelper.getFacets(response); + Map> facets = response.getFacets(); assertNotNull(facets); canSearchWithValueFacetsValidateResponse(response, hotels, facets); @@ -783,7 +782,7 @@ public void canSearchWithValueFacetsAsync() { StepVerifier.create(asyncClient.search("*", getSearchOptionsForValueFacets()).byPage()) .thenConsumeWhile(response -> { - Map> facets = SearchPagedResponseAccessHelper.getFacets(response); + Map> facets = response.getFacets(); assertNotNull(facets); canSearchWithValueFacetsValidateResponse(response, hotels, facets); @@ -928,11 +927,11 @@ public void canGetResultCountInSearchSync() { SearchPagedIterable results = client.search("*", new SearchOptions().setIncludeTotalCount(true), Context.NONE); assertNotNull(results); - assertEquals(hotels.size(), results.getTotalCount().intValue()); Iterator iterator = results.iterableByPage().iterator(); + SearchPagedResponse page = iterator.next(); + assertEquals(hotels.size(), page.getCount().intValue()); - assertNotNull(iterator.next()); assertFalse(iterator.hasNext()); } @@ -942,7 +941,7 @@ public void canGetResultCountInSearchAsync() { List> hotels = readJsonFileToList(HOTELS_DATA_JSON); StepVerifier.create(asyncClient.search("*", new SearchOptions().setIncludeTotalCount(true)).byPage()) - .assertNext(response -> assertEquals(hotels.size(), SearchPagedResponseAccessHelper.getCount(response))) + .assertNext(response -> assertEquals(hotels.size(), response.getCount())) .verifyComplete(); } @@ -1108,7 +1107,7 @@ public void canSearchWithMinimumCoverageSync() { .iterator() .next(); - assertEquals(100.0, SearchPagedResponseAccessHelper.getCoverage(response), 0); + assertEquals(100.0, response.getCoverage(), 0); } @Test @@ -1116,7 +1115,7 @@ public void canSearchWithMinimumCoverageAsync() { SearchAsyncClient asyncClient = getAsyncClient(HOTEL_INDEX_NAME); StepVerifier.create(asyncClient.search("*", new SearchOptions().setMinimumCoverage(50.0)).byPage()) - .assertNext(response -> assertEquals(100.0, SearchPagedResponseAccessHelper.getCoverage(response))) + .assertNext(response -> assertEquals(100.0, response.getCoverage())) .verifyComplete(); } diff --git a/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/VectorSearchWithSharedIndexTests.java b/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/VectorSearchWithSharedIndexTests.java index 07dceef708e3..eb51fe929639 100644 --- a/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/VectorSearchWithSharedIndexTests.java +++ b/sdk/search/azure-search-documents/src/test/java/com/azure/search/documents/VectorSearchWithSharedIndexTests.java @@ -6,7 +6,6 @@ import com.azure.core.test.TestMode; import com.azure.core.test.TestProxyTestBase; import com.azure.core.util.Context; -import com.azure.search.documents.implementation.util.SearchPagedResponseAccessHelper; import com.azure.search.documents.indexes.SearchIndexClient; import com.azure.search.documents.indexes.SearchIndexClientBuilder; import com.azure.search.documents.indexes.models.DistanceScoringFunction; @@ -205,11 +204,11 @@ public void semanticHybridSearchAsync() { .byPage() .collectList()).assertNext(pages -> { SearchPagedResponse page1 = pages.get(0); - assertNotNull(SearchPagedResponseAccessHelper.getQueryAnswers(page1)); - assertEquals(1, SearchPagedResponseAccessHelper.getQueryAnswers(page1).size()); - assertEquals("9", SearchPagedResponseAccessHelper.getQueryAnswers(page1).get(0).getKey()); - assertNotNull(SearchPagedResponseAccessHelper.getQueryAnswers(page1).get(0).getHighlights()); - assertNotNull(SearchPagedResponseAccessHelper.getQueryAnswers(page1).get(0).getText()); + assertNotNull(page1.getSemanticResults().getQueryAnswers()); + assertEquals(1, page1.getSemanticResults().getQueryAnswers().size()); + assertEquals("9", page1.getSemanticResults().getQueryAnswers().get(0).getKey()); + assertNotNull(page1.getSemanticResults().getQueryAnswers().get(0).getHighlights()); + assertNotNull(page1.getSemanticResults().getQueryAnswers().get(0).getText()); List results = new ArrayList<>(); for (SearchPagedResponse page : pages) { @@ -246,11 +245,11 @@ public void semanticHybridSearchSync() { .collect(Collectors.toList()); SearchPagedResponse page1 = pages.get(0); - assertNotNull(SearchPagedResponseAccessHelper.getQueryAnswers(page1)); - assertEquals(1, SearchPagedResponseAccessHelper.getQueryAnswers(page1).size()); - assertEquals("9", SearchPagedResponseAccessHelper.getQueryAnswers(page1).get(0).getKey()); - assertNotNull(SearchPagedResponseAccessHelper.getQueryAnswers(page1).get(0).getHighlights()); - assertNotNull(SearchPagedResponseAccessHelper.getQueryAnswers(page1).get(0).getText()); + assertNotNull(page1.getSemanticResults().getQueryAnswers()); + assertEquals(1, page1.getSemanticResults().getQueryAnswers().size()); + assertEquals("9", page1.getSemanticResults().getQueryAnswers().get(0).getKey()); + assertNotNull(page1.getSemanticResults().getQueryAnswers().get(0).getHighlights()); + assertNotNull(page1.getSemanticResults().getQueryAnswers().get(0).getText()); List results = new ArrayList<>(); for (SearchPagedResponse page : pages) {