diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/HybridSearch/HybridSearchQueryResult.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/HybridSearch/HybridSearchQueryResult.cs index bfd19db547..c7b9f4df34 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/HybridSearch/HybridSearchQueryResult.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/HybridSearch/HybridSearchQueryResult.cs @@ -54,9 +54,9 @@ public static HybridSearchQueryResult Create(CosmosElement document) throw new ArgumentException($"{FieldNames.Payload} must exist."); } - if (!outerPayload.TryGetValue(FieldNames.Payload, out CosmosObject innerPayload)) + if (!outerPayload.TryGetValue(FieldNames.Payload, out CosmosElement innerPayload)) { - throw new ArgumentException($"{FieldNames.Payload} must exist nested within the outer payload field."); + innerPayload = CosmosUndefined.Create(); } if (!outerPayload.TryGetValue(FieldNames.ComponentScores, out CosmosArray componentScores)) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/CosmosUndefinedQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/CosmosUndefinedQueryTests.cs index 5fe7f28c87..618810f735 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/CosmosUndefinedQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/CosmosUndefinedQueryTests.cs @@ -23,10 +23,10 @@ public sealed class CosmosUndefinedQueryTests : QueryTestsBase private const int IntegerValue = 42; - private const string StringValue = "string"; - - private const string ArrayValue = "[10, 20]"; - + private const string StringValue = "string"; + + private const string ArrayValue = "[10, 20]"; + private const string ObjectValue = "{\"type\":\"object\"}"; private static readonly int[] PageSizes = new[] { 5, 10, -1 }; @@ -51,6 +51,18 @@ await this.CreateIngestQueryDeleteAsync( indexingPolicy: CompositeIndexPolicy); } + [TestMethod] + public async Task HybridSearchTests() + { + // Removing the await causes the test framework to not run this test + await this.CreateIngestQueryDeleteAsync( + connectionModes: ConnectionModes.Direct | ConnectionModes.Gateway, + collectionTypes: CollectionTypes.MultiPartition | CollectionTypes.SinglePartition, + documents: Documents, + query: HybridSearchTests, + indexingPolicy: CompositeIndexPolicy); + } + private static async Task RunTests(Container container, IReadOnlyList _) { await OrderByTests(container); @@ -58,7 +70,57 @@ private static async Task RunTests(Container container, IReadOnlyList _) + { + UndefinedProjectionTestCase[] testCases = new[] + { + MakeUndefinedProjectionTest( + query: "SELECT c.AlwaysUndefinedField " + + "FROM c " + + "ORDER BY RANK FullTextScore(c.AlwaysUndefinedField, ['needle'])", + expectedCount: DocumentCount), + MakeUndefinedProjectionTest( + query: "SELECT VALUE c.AlwaysUndefinedField " + + "FROM c " + + "ORDER BY RANK FullTextScore(c.AlwaysUndefinedField, ['needle'])", + expectedCount: 0), + MakeUndefinedProjectionTest( + query: "SELECT c.AlwaysUndefinedField " + + "FROM c " + + "ORDER BY RANK RRF(FullTextScore(c.AlwaysUndefinedField, ['needle']), FullTextScore(c.AnotherUndefinedField, ['needle']))", + expectedCount: DocumentCount), + MakeUndefinedProjectionTest( + query: "SELECT VALUE c.AlwaysUndefinedField " + + "FROM c " + + "ORDER BY RANK RRF(FullTextScore(c.AlwaysUndefinedField, ['needle']), FullTextScore(c.AnotherUndefinedField, ['needle']))", + expectedCount: 0), + MakeUndefinedProjectionTest( + query: $"SELECT c.AlwaysUndefinedField " + + $"FROM c " + + $"ORDER BY RANK FullTextScore(c.{nameof(MixedTypeDocument.MixedTypeField)}, ['needle'])", + expectedCount: DocumentCount), + MakeUndefinedProjectionTest( + query: $"SELECT VALUE c.AlwaysUndefinedField " + + $"FROM c " + + $"ORDER BY RANK FullTextScore(c.{nameof(MixedTypeDocument.MixedTypeField)}, ['needle'])", + expectedCount: 0), + MakeUndefinedProjectionTest( + query: $"SELECT c.AlwaysUndefinedField " + + $"FROM c " + + $"ORDER BY RANK RRF(FullTextScore(c.{nameof(MixedTypeDocument.MixedTypeField)}, ['needle']),FullTextScore(c.{nameof(MixedTypeDocument.Index)}, ['needle']))", + expectedCount: DocumentCount), + MakeUndefinedProjectionTest( + query: $"SELECT VALUE c.AlwaysUndefinedField " + + $"FROM c " + + $"ORDER BY RANK RRF(FullTextScore(c.{nameof(MixedTypeDocument.MixedTypeField)}, ['needle']),FullTextScore(c.{nameof(MixedTypeDocument.Index)}, ['needle']))", + expectedCount: 0), + }; + + await RunUndefinedProjectionTests(container, testCases); + await RunUntypedTestsAsync(container, testCases); + } + + private static Task UntypedTests(Container container) { UndefinedProjectionTestCase[] undefinedProjectionTestCases = new[] { @@ -79,13 +141,18 @@ private static async Task UntypedTests(Container container) expectedCount: 0), MakeUndefinedProjectionTest( query: $"SELECT VALUE AVG(c.{nameof(MixedTypeDocument.MixedTypeField)}) FROM c", - expectedCount: 0), - MakeUndefinedProjectionTest( + expectedCount: 0), + MakeUndefinedProjectionTest( query: $"SELECT DISTINCT VALUE SUM(c.{nameof(MixedTypeDocument.MixedTypeField)}) FROM c", expectedCount: 0) }; - foreach (UndefinedProjectionTestCase testCase in undefinedProjectionTestCases) + return RunUntypedTestsAsync(container, undefinedProjectionTestCases); + } + + private static async Task RunUntypedTestsAsync(Container container, IEnumerable testCases) + { + foreach (UndefinedProjectionTestCase testCase in testCases) { foreach (int pageSize in PageSizes) { @@ -122,6 +189,24 @@ private static async Task UntypedTests(Container container) Assert.AreEqual(testCase.ExpectedResultCount, actualCount); } } + } + + private static async Task RunUndefinedProjectionTests(Container container, IEnumerable testCases) + { + foreach (UndefinedProjectionTestCase testCase in testCases) + { + foreach (int pageSize in PageSizes) + { + List results = await RunQueryCombinationsAsync( + container, + testCase.Query, + new QueryRequestOptions { MaxItemCount = pageSize }, + QueryDrainingMode.HoldState); + + Assert.AreEqual(testCase.ExpectedResultCount, results.Count); + Assert.IsTrue(results.All(x => x is UndefinedProjection)); + } + } } private static async Task OrderByTests(Container container) @@ -237,10 +322,10 @@ private static async Task GroupByTests(Container container) value: DocumentsPerTypeCount), MakeGrouping( key: CosmosArray.Parse(ArrayValue), - value: DocumentsPerTypeCount), + value: DocumentsPerTypeCount), MakeGrouping( key: CosmosObject.Parse(ObjectValue), - value: DocumentsPerTypeCount), + value: DocumentsPerTypeCount), }), MakeGroupByTest( query: $"SELECT SUM(c.{nameof(MixedTypeDocument.MixedTypeField)}) as {nameof(GroupByProjection.MixedTypeField)}, " + @@ -361,9 +446,9 @@ private static async Task GroupByTests(Container container) }; foreach (GroupByUndefinedTestCase testCase in mixedTypeTestCases) - { + { foreach (int pageSize in PageSizes) - { + { List actual = await QueryWithoutContinuationTokensAsync( container, testCase.Query, @@ -457,14 +542,14 @@ private static List CreateDocuments(int count) case 4: mixedTypeElement = CosmosString.Create(StringValue); break; - - case 5: - mixedTypeElement = CosmosArray.Parse(ArrayValue); - break; - - case 6: - mixedTypeElement = CosmosObject.Parse(ObjectValue); - break; + + case 5: + mixedTypeElement = CosmosArray.Parse(ArrayValue); + break; + + case 6: + mixedTypeElement = CosmosObject.Parse(ObjectValue); + break; default: mixedTypeElement = null;