diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/HybridSearchQueryTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/HybridSearchQueryTest.java index 2bb99c6906e8..f2ee33c38169 100644 --- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/HybridSearchQueryTest.java +++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/rx/HybridSearchQueryTest.java @@ -14,11 +14,13 @@ import com.azure.cosmos.models.CosmosFullTextIndex; import com.azure.cosmos.models.CosmosFullTextPath; import com.azure.cosmos.models.CosmosFullTextPolicy; +import com.azure.cosmos.models.CosmosFullTextScoreScope; import com.azure.cosmos.models.CosmosQueryRequestOptions; import com.azure.cosmos.models.IncludedPath; import com.azure.cosmos.models.IndexingMode; import com.azure.cosmos.models.IndexingPolicy; import com.azure.cosmos.models.PartitionKeyDefinition; +import com.azure.cosmos.models.PartitionKey; import com.azure.cosmos.models.SqlParameter; import com.azure.cosmos.models.SqlQuerySpec; import com.azure.cosmos.models.ThroughputProperties; @@ -49,7 +51,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -@Ignore("TODO: Ignore these test cases until the public emulator is released.") +//@Ignore("TODO: Ignore these test cases until the public emulator is released.") public class HybridSearchQueryTest { protected static final int TIMEOUT = 30000; protected static final int SETUP_TIMEOUT = 80000; @@ -74,7 +76,7 @@ public void before_HybridSearchQueryTest() { PartitionKeyDefinition partitionKeyDef = new PartitionKeyDefinition(); ArrayList paths = new ArrayList(); - paths.add("/id"); + paths.add("/pk"); partitionKeyDef.setPaths(paths); CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerId, partitionKeyDef); @@ -83,8 +85,11 @@ public void before_HybridSearchQueryTest() { database.createContainer(containerProperties, ThroughputProperties.createManualThroughput(10000)).block(); container = database.getContainer(containerId); + // Insert documents with pk field based on (index % 2) + 1, so even ids → pk="1", odd ids → pk="2" List documents = loadProductsFromJson(); for (Document doc : documents) { + int index = Integer.parseInt(doc.getId()); + doc.pk = String.valueOf((index % 2) + 1); container.createItem(doc).block(); } } @@ -131,7 +136,7 @@ public void hybridQueryTest() { .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) .collectList().block(); assertThat(resultDocs).hasSize(15); - validateResults(Arrays.asList("51", "49", "24", "61", "54", "22", "2", "25", "75", "77", "57", "76", "66", "80", "85"), resultDocs); + validateResults(Arrays.asList("61", "54", "51", "49", "24", "2", "57", "22", "75", "25", "77", "76", "66", "80", "85"), resultDocs); query = "SELECT c.id, c.title FROM c WHERE FullTextContains(c.title, @term1) " + "OR FullTextContains(c.text, @term1) OR FullTextContains(c.text, @term2) ORDER BY " + @@ -144,7 +149,7 @@ public void hybridQueryTest() { .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) .collectList().block(); assertThat(resultDocs).hasSize(10); - validateResults(Arrays.asList("22", "2", "25", "75", "77", "57", "76", "66", "80", "85"), resultDocs); + validateResults(Arrays.asList("2", "57", "22", "75", "25", "77", "76", "66", "80", "85"), resultDocs); List vector = getQueryVector(); query = "SELECT TOP 10 c.id, c.text, c.title FROM c " + @@ -159,7 +164,7 @@ public void hybridQueryTest() { .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) .collectList().block(); assertThat(resultDocs).hasSize(10); - validateResults(Arrays.asList("4", "24", "6", "9", "2", "3", "21", "5", "49", "13"), resultDocs); + validateResults(Arrays.asList("55", "61", "57", "24", "2", "54", "63", "9", "62", "75"), resultDocs); } @Test(groups = {"query", "split"}, timeOut = TIMEOUT) @@ -179,7 +184,7 @@ public void hybridQueryWeightedRRFTest(){ assertThat(results).isNotNull(); validateResults( - Arrays.asList("51", "49", "24", "61", "54", "22", "2", "25", "75", "77", "57", "76", "66", "80", "85"), + Arrays.asList("61", "54", "51", "49", "24", "2", "57", "22", "75", "25", "77", "76", "66", "80", "85"), results ); @@ -198,7 +203,7 @@ public void hybridQueryWeightedRRFTest(){ assertThat(results).hasSize(15); assertThat(results).isNotNull(); validateResults( - Arrays.asList("51", "49", "24", "61", "54", "22", "2", "25", "75", "77", "57", "76", "66", "80", "85"), + Arrays.asList("61", "54", "51", "49", "24", "2", "57", "22", "75", "25", "77", "76", "66", "80", "85"), results ); @@ -216,7 +221,7 @@ public void hybridQueryWeightedRRFTest(){ assertThat(results).hasSize(10); assertThat(results).isNotNull(); validateResults( - Arrays.asList("51", "49", "24", "61", "54", "22", "2", "25", "75", "77"), + Arrays.asList("61", "54", "51", "49", "24", "2", "57", "22", "75", "25"), results ); @@ -234,7 +239,7 @@ public void hybridQueryWeightedRRFTest(){ assertThat(results).hasSize(10); assertThat(results).isNotNull(); validateResults( - Arrays.asList("22", "57", "25", "24", "66", "2", "85", "49", "51", "54"), + Arrays.asList("57", "22", "25", "54", "66", "24", "2", "85", "61", "76"), results ); @@ -254,11 +259,106 @@ public void hybridQueryWeightedRRFTest(){ assertThat(results).hasSize(10); assertThat(results).isNotNull(); validateResults( - Arrays.asList("75", "24", "49", "61", "21", "9", "26", "4", "6", "37"), + Arrays.asList("75", "24", "49", "55", "61", "21", "9", "26", "37", "57"), results ); } + @Test(groups = {"query", "split"}, timeOut = TIMEOUT) + public void hybridQueryWithLocalStatisticsTest() { + // Documents with 'John': id=2 (pk="1"), id=57 (pk="2"), id=85 (pk="2") + + String query = "SELECT TOP 10 c.id, c.title, c.pk FROM c WHERE FullTextContains(c.title, @term1) OR " + + "FullTextContains(c.text, @term1) ORDER BY RANK FullTextScore(c.title, @term1)"; + SqlQuerySpec querySpec = new SqlQuerySpec(query, Arrays.asList( + new SqlParameter("@term1", "John") + )); + + // Test 1: GLOBAL scope (default) cross-partition — should return all 3 'John' matches + List globalResultDocs = container.queryItems(querySpec, new CosmosQueryRequestOptions(), Document.class).byPage() + .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) + .collectList().block(); + assertThat(globalResultDocs).isNotNull(); + assertThat(globalResultDocs).hasSize(3); + + // Test 2: Explicit GLOBAL scope cross-partition — same as default + CosmosQueryRequestOptions globalScopeOptions = new CosmosQueryRequestOptions(); + globalScopeOptions.setFullTextScoreScope(CosmosFullTextScoreScope.GLOBAL); + + List explicitGlobalResultDocs = container.queryItems(querySpec, globalScopeOptions, Document.class).byPage() + .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) + .collectList().block(); + assertThat(explicitGlobalResultDocs).isNotNull(); + assertThat(explicitGlobalResultDocs).hasSize(3); + + List defaultIds = globalResultDocs.stream().map(Document::getId).collect(Collectors.toList()); + List explicitGlobalIds = explicitGlobalResultDocs.stream().map(Document::getId).collect(Collectors.toList()); + assertThat(explicitGlobalIds).isEqualTo(defaultIds); + + // Test 3: LOCAL scope with pk="2" — only id=57 and id=85 have 'John' in pk="2" + // Stats are computed only over pk="2" partition + CosmosQueryRequestOptions localScopeOptionsPk2 = new CosmosQueryRequestOptions(); + localScopeOptionsPk2.setFullTextScoreScope(CosmosFullTextScoreScope.LOCAL); + localScopeOptionsPk2.setPartitionKey(new PartitionKey("2")); + + List localPk2ResultDocs = container.queryItems(querySpec, localScopeOptionsPk2, Document.class).byPage() + .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) + .collectList().block(); + assertThat(localPk2ResultDocs).isNotNull(); + assertThat(localPk2ResultDocs).hasSize(2); + for (Document doc : localPk2ResultDocs) { + assertThat(doc.getPk()).isEqualTo("2"); + } + + // Test 4: LOCAL scope with pk="1" — only id=2 has 'John' in pk="1" + // Stats are computed only over pk="1" partition + CosmosQueryRequestOptions localScopeOptionsPk1 = new CosmosQueryRequestOptions(); + localScopeOptionsPk1.setFullTextScoreScope(CosmosFullTextScoreScope.LOCAL); + localScopeOptionsPk1.setPartitionKey(new PartitionKey("1")); + + List localPk1ResultDocs = container.queryItems(querySpec, localScopeOptionsPk1, Document.class).byPage() + .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) + .collectList().block(); + assertThat(localPk1ResultDocs).isNotNull(); + assertThat(localPk1ResultDocs).hasSize(1); + assertThat(localPk1ResultDocs.get(0).getId()).isEqualTo("2"); + assertThat(localPk1ResultDocs.get(0).getPk()).isEqualTo("1"); + } + + @Test(groups = {"query", "split"}, timeOut = TIMEOUT) + public void hybridQueryRRFWithLocalStatisticsTest() { + // Test LOCAL scope with RRF (multiple component queries) + String query = "SELECT TOP 10 c.id, c.title, c.pk FROM c WHERE " + + "FullTextContains(c.title, @term1) OR FullTextContains(c.text, @term1) OR " + + "FullTextContains(c.text, @term2) " + + "ORDER BY RANK RRF(FullTextScore(c.title, @term1), FullTextScore(c.text, @term2))"; + SqlQuerySpec querySpec = new SqlQuerySpec(query, Arrays.asList( + new SqlParameter("@term1", "John"), + new SqlParameter("@term2", "United") + )); + + // GLOBAL scope (default) cross-partition + List globalResultDocs = container.queryItems(querySpec, new CosmosQueryRequestOptions(), Document.class).byPage() + .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) + .collectList().block(); + assertThat(globalResultDocs).isNotNull(); + assertThat(globalResultDocs).isNotEmpty(); + + // LOCAL scope with pk="2" + CosmosQueryRequestOptions localScopeOptions = new CosmosQueryRequestOptions(); + localScopeOptions.setFullTextScoreScope(CosmosFullTextScoreScope.LOCAL); + localScopeOptions.setPartitionKey(new PartitionKey("2")); + + List localResultDocs = container.queryItems(querySpec, localScopeOptions, Document.class).byPage() + .flatMap(feedResponse -> Flux.fromIterable(feedResponse.getResults())) + .collectList().block(); + assertThat(localResultDocs).isNotNull(); + assertThat(localResultDocs).isNotEmpty(); + for (Document doc : localResultDocs) { + assertThat(doc.getPk()).isEqualTo("2"); + } + } + @Test(groups = {"query", "split"}, timeOut = TIMEOUT) public void wrongHybridQueryTest() { String query = ""; @@ -410,6 +510,8 @@ public static List getQueryVector() { static class Document { @JsonProperty("id") String id; + @JsonProperty("pk") + String pk; @JsonProperty("text") String text; @JsonProperty("title") @@ -422,8 +524,9 @@ static class Document { public Document() { } - public Document(String id, String text, String title, double[] vector, double score) { + public Document(String id, String pk, String text, String title, double[] vector, double score) { this.id = id; + this.pk = pk; this.text = text; this.title = title; this.vector = vector; @@ -433,5 +536,9 @@ public Document(String id, String text, String title, double[] vector, double sc public String getId() { return id; } + + public String getPk() { + return pk; + } } } diff --git a/sdk/cosmos/azure-cosmos/CHANGELOG.md b/sdk/cosmos/azure-cosmos/CHANGELOG.md index cd85fb9aaafe..7761e57d4f35 100644 --- a/sdk/cosmos/azure-cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure-cosmos/CHANGELOG.md @@ -5,11 +5,14 @@ #### Features Added * Added support for N-Region synchronous commit feature - See [PR 47757](https://github.com/Azure/azure-sdk-for-java/pull/47757) * Added support for Query Advisor feature - See [48160](https://github.com/Azure/azure-sdk-for-java/pull/48160) +* Added `CosmosFullTextScoreScope` enum and `setFullTextScoreScope()` on `CosmosQueryRequestOptions` for controlling BM25 statistics scope in hybrid search queries. Supports `LOCAL` (scoped to target partitions) and `GLOBAL` (default, all partitions) scopes. See [PR 48431](https://github.com/Azure/azure-sdk-for-java/pull/48431) #### Breaking Changes #### Bugs Fixed * Fixed Remote Code Execution (RCE) vulnerability (CWE-502) by replacing Java deserialization with JSON-based serialization in `CosmosClientMetadataCachesSnapshot`, `AsyncCache`, and `DocumentCollection`. The metadata cache snapshot now uses Jackson for serialization/deserialization, eliminating the entire class of Java deserialization attacks. - [PR 47971](https://github.com/Azure/azure-sdk-for-java/pull/47971) +* Fixed `NullPointerException` in `DocumentQueryExecutionContextFactory.tryCacheQueryPlan` when executing hybrid search queries with a partition key filter. See [PR 48431](https://github.com/Azure/azure-sdk-for-java/pull/48431) +* Fixed `ConcurrentModificationException` in hybrid search component query execution caused by concurrent access to shared mutable state. See [PR 48431](https://github.com/Azure/azure-sdk-for-java/pull/48431) #### Other Changes * Added aggressive HTTP timeout policies for document operations routed to Gateway V2. - [PR 47879](https://github.com/Azure/azure-sdk-for-java/pull/47879) diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosQueryRequestOptionsImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosQueryRequestOptionsImpl.java index 3e0839a8eb41..dbdbcbc1ceea 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosQueryRequestOptionsImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/CosmosQueryRequestOptionsImpl.java @@ -3,6 +3,7 @@ package com.azure.cosmos.implementation; import com.azure.cosmos.CosmosDiagnostics; +import com.azure.cosmos.models.CosmosFullTextScoreScope; import com.azure.cosmos.models.CosmosRequestOptions; import com.azure.cosmos.models.FeedRange; import com.azure.cosmos.models.PartitionKey; @@ -32,6 +33,7 @@ public final class CosmosQueryRequestOptionsImpl extends CosmosQueryRequestOptio private Integer maxItemCountForHybridSearch; private List cancelledRequestDiagnosticsTracker = new ArrayList<>(); private String collectionRid; + private CosmosFullTextScoreScope fullTextScoreScope; /** * Instantiates a new query request options. @@ -72,6 +74,7 @@ public CosmosQueryRequestOptionsImpl(CosmosQueryRequestOptionsImpl options) { this.maxItemCountForVectorSearch = options.maxItemCountForVectorSearch; this.maxItemCountForHybridSearch = options.maxItemCountForHybridSearch; this.collectionRid = options.collectionRid; + this.fullTextScoreScope = options.fullTextScoreScope; } /** @@ -408,4 +411,24 @@ public String getCollectionRid() { public void setCollectionRid(String collectionRid) { this.collectionRid = collectionRid; } + + /** + * Gets the scope for computing BM25 statistics used by FullTextScore in hybrid search queries. + * + * @return the full text score scope, or null if not set (defaults to GLOBAL behavior). + */ + public CosmosFullTextScoreScope getFullTextScoreScope() { + return this.fullTextScoreScope; + } + + /** + * Sets the scope for computing BM25 statistics used by FullTextScore in hybrid search queries. + * + * @param fullTextScoreScope the full text score scope. + * @return the CosmosQueryRequestOptionsImpl. + */ + public CosmosQueryRequestOptionsImpl setFullTextScoreScope(CosmosFullTextScoreScope fullTextScoreScope) { + this.fullTextScoreScope = fullTextScoreScope; + return this; + } } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/DocumentQueryExecutionContextFactory.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/DocumentQueryExecutionContextFactory.java index 1149e15f43bd..7ccb8a926279 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/DocumentQueryExecutionContextFactory.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/DocumentQueryExecutionContextFactory.java @@ -262,7 +262,8 @@ synchronized private static void tryCacheQueryPlan( SqlQuerySpec query, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo, Map queryPlanCache) { - if (canCacheQuery(partitionedQueryExecutionInfo.getQueryInfo()) && !queryPlanCache.containsKey(query.getQueryText())) { + QueryInfo queryInfo = partitionedQueryExecutionInfo.getQueryInfo(); + if (queryInfo != null && canCacheQuery(queryInfo) && !queryPlanCache.containsKey(query.getQueryText())) { if (queryPlanCache.size() >= Constants.QUERYPLAN_CACHE_SIZE) { logger.warn("Clearing query plan cache as it has reached the maximum size : {}", queryPlanCache.size()); queryPlanCache.clear(); diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/HybridSearchDocumentQueryExecutionContext.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/HybridSearchDocumentQueryExecutionContext.java index b1969f1d412a..0ef9659fb7d2 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/HybridSearchDocumentQueryExecutionContext.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/query/HybridSearchDocumentQueryExecutionContext.java @@ -24,6 +24,7 @@ import com.azure.cosmos.implementation.query.hybridsearch.FullTextQueryStatistics; import com.azure.cosmos.implementation.query.hybridsearch.GlobalFullTextSearchQueryStatistics; import com.azure.cosmos.implementation.query.hybridsearch.HybridSearchQueryInfo; +import com.azure.cosmos.models.CosmosFullTextScoreScope; import com.azure.cosmos.models.CosmosQueryRequestOptions; import com.azure.cosmos.models.FeedResponse; import com.azure.cosmos.models.ModelBridgeInternal; @@ -141,8 +142,15 @@ private void initialize( this.hybridSearchSchedulingStopwatch.start(); if (hybridSearchQueryInfo.getRequiresGlobalStatistics()) { + // When FullTextScoreScope is GLOBAL (default), use allFeedRanges for statistics. + // When LOCAL, use only targetFeedRanges for scoped statistics. + List statisticsTargetRanges = + this.cosmosQueryRequestOptions.getFullTextScoreScope() == CosmosFullTextScoreScope.LOCAL + ? targetFeedRanges + : allFeedRanges; + Map partitionKeyRangeToContinuationToken = new HashMap<>(); - for (FeedRangeEpkImpl feedRangeEpk : allFeedRanges) { + for (FeedRangeEpkImpl feedRangeEpk : statisticsTargetRanges) { partitionKeyRangeToContinuationToken.put(feedRangeEpk, null); } super.initialize(collection, @@ -389,7 +397,10 @@ private static Mono>> retrieveComponentScores(Mono getComponentQueryResults(List targetFeedRanges, int initialPageSize, DocumentCollection collection, Flux rewrittenQueryInfos) { - return rewrittenQueryInfos.flatMap(queryInfo -> { + // Use concatMap to serialize component query initialization. The parent class has shared mutable + // state (documentProducers, metrics trackers) that is not thread-safe for concurrent access. + // Each component query still executes its partition queries in parallel via the inner flatMap. + return rewrittenQueryInfos.concatMap(queryInfo -> { Map partitionKeyRangeToContinuationToken = new HashMap<>(); for (FeedRangeEpkImpl feedRangeEpk : targetFeedRanges) { partitionKeyRangeToContinuationToken.put(feedRangeEpk, diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosFullTextScoreScope.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosFullTextScoreScope.java new file mode 100644 index 000000000000..2d59206d1e9d --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosFullTextScoreScope.java @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.models; + +/** + * Specifies the scope for computing BM25 statistics used by FullTextScore in hybrid search queries. + */ +public enum CosmosFullTextScoreScope { + /** + * Compute BM25 statistics (term frequency, inverse document frequency, and document length) + * across all documents in the container, including all physical and logical partitions. + * This is the default behavior. + */ + GLOBAL, + + /** + * Compute BM25 statistics only over the subset of documents within the partition key values + * specified in the query. This is useful for multi-tenant scenarios where scoring should + * reflect statistics that are accurate for a specific tenant's dataset. + */ + LOCAL +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosQueryRequestOptions.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosQueryRequestOptions.java index 722f66bf65d0..cfd6a17749f7 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosQueryRequestOptions.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CosmosQueryRequestOptions.java @@ -532,6 +532,38 @@ public CosmosQueryRequestOptions setQueryAdviceEnabled(boolean queryAdviceEnable return this; } + /** + * Gets the scope for computing BM25 statistics used by FullTextScore in hybrid search queries. + * + * @return the scope for computing BM25 statistics. Defaults to {@link CosmosFullTextScoreScope#GLOBAL}. + */ + public CosmosFullTextScoreScope getFullTextScoreScope() { + CosmosFullTextScoreScope scope = this.actualRequestOptions.getFullTextScoreScope(); + return scope != null ? scope : CosmosFullTextScoreScope.GLOBAL; + } + + /** + * Sets the scope for computing BM25 statistics used by FullTextScore in hybrid search queries. + * + *

+ * When set to {@link CosmosFullTextScoreScope#GLOBAL}, BM25 statistics (term frequency, inverse document frequency, + * and document length) are computed across all documents in the container, including all physical and logical + * partitions. + *

+ *

+ * When set to {@link CosmosFullTextScoreScope#LOCAL}, statistics are computed only over the subset of documents + * within the partition key values specified in the query request. This is useful for multi-tenant scenarios where + * scoring should reflect statistics that are accurate for a specific tenant's dataset. + *

+ * + * @param fullTextScoreScope the scope for computing BM25 statistics. + * @return the CosmosQueryRequestOptions. + */ + public CosmosQueryRequestOptions setFullTextScoreScope(CosmosFullTextScoreScope fullTextScoreScope) { + this.actualRequestOptions.setFullTextScoreScope(fullTextScoreScope); + return this; + } + /** * Sets the logical query name - this identifier is only used for metrics and logs