diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/FullTextPath.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/FullTextPath.cs index d13715b0eb..d75dbab347 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/FullTextPath.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/FullTextPath.cs @@ -42,7 +42,7 @@ public class FullTextPath : IEquatable /// /// Gets or sets a string containing the language of the full text path. /// - [JsonProperty(PropertyName = "language")] + [JsonProperty(PropertyName = "language", NullValueHandling = NullValueHandling.Ignore)] public string Language { get; set; } /// @@ -62,11 +62,6 @@ public void ValidateFullTextPath() throw new ArgumentException("Argument {0} can't be null or empty.", nameof(this.Path)); } - if (string.IsNullOrEmpty(this.Language)) - { - throw new ArgumentException("Argument {0} can't be null or empty.", nameof(this.Language)); - } - if (this.Path[0] != '/') { throw new ArgumentException("The argument {0} is not a valid path.", this.Path); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs index 79283514a0..16186a5835 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs @@ -806,6 +806,58 @@ await databaseForVectorEmbedding.DefineContainer(containerName, partitionKeyPath } } + [Ignore("Marking as ignore until emulator is updated")] + [TestMethod] + public async Task TestFullTextSearchPolicyOptionalLanguage() + { + string fullTextPath1 = "/fts1"; + Database databaseForFullTextSearch = await this.GetClient().CreateDatabaseAsync("fullTextSearchDB", + cancellationToken: this.cancellationToken); + + try + { + string containerName = "fullTextContainerTest"; + string partitionKeyPath = "/pk"; + + ContainerResponse containerResponse = + await databaseForFullTextSearch.DefineContainer(containerName, partitionKeyPath) + .WithFullTextPolicy( + defaultLanguage: null, + fullTextPaths: new Collection() { new FullTextPath() + { + Path = fullTextPath1 + }}) + .Attach() + .WithIndexingPolicy() + .WithFullTextIndex() + .Path(fullTextPath1) + .Attach() + .Attach() + .CreateAsync(); + + Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode); + Assert.AreEqual(containerName, containerResponse.Resource.Id); + Assert.AreEqual(partitionKeyPath, containerResponse.Resource.PartitionKey.Paths.First()); + ContainerProperties containerSettings = containerResponse.Resource; + + // Validate FullText Paths. + Assert.IsNotNull(containerSettings.FullTextPolicy); + Assert.IsNull(containerSettings.FullTextPolicy.DefaultLanguage); + Assert.IsNotNull(containerSettings.FullTextPolicy.FullTextPaths); + Assert.AreEqual(1, containerSettings.FullTextPolicy.FullTextPaths.Count()); + Assert.IsNull(containerSettings.FullTextPolicy.FullTextPaths[0].Language); + + // Validate Full Text Indexes. + Assert.IsNotNull(containerSettings.IndexingPolicy.FullTextIndexes); + Assert.AreEqual(1, containerSettings.IndexingPolicy.FullTextIndexes.Count()); + Assert.AreEqual(fullTextPath1, containerSettings.IndexingPolicy.FullTextIndexes[0].Path); + } + finally + { + await databaseForFullTextSearch.DeleteAsync(); + } + } + [Ignore] [TestMethod] public async Task WithComputedProperties() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 1bd7fcc2f7..39a122ab05 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -5410,7 +5410,7 @@ ], "MethodInfo": "System.String get_Path();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String Language[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"language\")]": { + "System.String Language[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"language\")]": { "Type": "Property", "Attributes": [ "JsonPropertyAttribute" diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs index d0f656a8fc..65f14d567c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs @@ -292,8 +292,7 @@ public void ValidateFullTextPathsAndIndexes() }, new Cosmos.FullTextPath() { - Path = fullTextPath3, - Language = "en-US", + Path = fullTextPath3 }, }; @@ -333,6 +332,10 @@ public void ValidateFullTextPathsAndIndexes() Assert.AreEqual(fullTextPaths.Count, fullTextPolicy.FullTextPaths.Count()); Assert.AreEqual(fullTextPaths[0].Path, fullTextPolicy.FullTextPaths[0].Path); Assert.AreEqual(fullTextPaths[0].Language, fullTextPolicy.FullTextPaths[0].Language); + Assert.AreEqual(fullTextPaths[1].Path, fullTextPolicy.FullTextPaths[1].Path); + Assert.AreEqual(fullTextPaths[1].Language, fullTextPolicy.FullTextPaths[1].Language); + Assert.AreEqual(fullTextPaths[2].Path, fullTextPolicy.FullTextPaths[2].Path); + Assert.AreEqual(fullTextPaths[2].Language, fullTextPolicy.FullTextPaths[2].Language); CollectionAssert.AreEquivalent(fullTextPaths, fullTextPolicy.FullTextPaths.ToList()); @@ -340,6 +343,96 @@ public void ValidateFullTextPathsAndIndexes() Assert.AreEqual("/fts1", fullTextIndexes[0].Path); Assert.AreEqual("/fts2", fullTextIndexes[1].Path); Assert.AreEqual("/fts3", fullTextIndexes[2].Path); + Assert.AreEqual("en-US", fullTextPolicy.FullTextPaths[0].Language); + Assert.AreEqual("en-US", fullTextPolicy.FullTextPaths[1].Language); + Assert.IsNull(fullTextPolicy.FullTextPaths[2].Language); + } + + [TestMethod] + public void ValidateFullTextLanguagesOptional() + { + string fullTextPath1 = "/fts1", fullTextPath2 = "/fts2"; + + Collection fullTextPaths = new Collection() + { + new Cosmos.FullTextPath() + { + Path = fullTextPath1 + }, + new Cosmos.FullTextPath() + { + Path = fullTextPath2, + Language = "de-DE", + } + }; + + ContainerProperties containerSettings = new ContainerProperties(id: "TestContainer", partitionKeyPath: "/partitionKey") + { + FullTextPolicy = new() + { + FullTextPaths = fullTextPaths + }, + IndexingPolicy = new Cosmos.IndexingPolicy() + { + FullTextIndexes = new() + { + new Cosmos.FullTextIndexPath() + { + Path = fullTextPath1, + }, + new Cosmos.FullTextIndexPath() + { + Path = fullTextPath2, + } + }, + }, + }; + + Assert.IsNotNull(containerSettings.IndexingPolicy); + Assert.IsNotNull(containerSettings.FullTextPolicy); + Assert.IsNotNull(containerSettings.IndexingPolicy.FullTextIndexes); + + Cosmos.FullTextPolicy fullTextPolicy = containerSettings.FullTextPolicy; + Assert.IsNull(fullTextPolicy.DefaultLanguage); + Assert.IsNotNull(fullTextPolicy.FullTextPaths); + Assert.AreEqual(fullTextPaths.Count, fullTextPolicy.FullTextPaths.Count()); + Assert.AreEqual(fullTextPaths[0].Path, fullTextPolicy.FullTextPaths[0].Path); + Assert.AreEqual(fullTextPaths[0].Language, fullTextPolicy.FullTextPaths[0].Language); + Assert.AreEqual(fullTextPaths[1].Path, fullTextPolicy.FullTextPaths[1].Path); + Assert.AreEqual(fullTextPaths[1].Language, fullTextPolicy.FullTextPaths[1].Language); + + CollectionAssert.AreEquivalent(fullTextPaths, fullTextPolicy.FullTextPaths.ToList()); + + Collection fullTextIndexes = containerSettings.IndexingPolicy.FullTextIndexes; + Assert.AreEqual("/fts1", fullTextIndexes[0].Path); + Assert.AreEqual("/fts2", fullTextIndexes[1].Path); + Assert.IsNull(fullTextPolicy.FullTextPaths[0].Language); + Assert.AreEqual("de-DE", fullTextPolicy.FullTextPaths[1].Language); + + string serialized = this.Serialize(containerSettings); + Assert.AreEqual(@"{""indexingPolicy"":{""automatic"":true,""indexingMode"":""Consistent"",""includedPaths"":[],""excludedPaths"":[],""compositeIndexes"":[],""spatialIndexes"":[],""vectorIndexes"":[],""fullTextIndexes"":[{""path"":""/fts1""},{""path"":""/fts2""}]},""fullTextPolicy"":{""fullTextPaths"":[{""path"":""/fts1""},{""path"":""/fts2"",""language"":""de-DE""}]},""id"":""TestContainer"",""partitionKey"":{""paths"":[""/partitionKey""],""kind"":""Hash""}}", serialized); + } + + private string Serialize(ContainerProperties containerProperties) + { + using (MemoryStream ms = new MemoryStream()) + { + using (StreamWriter streamWriter = new StreamWriter(ms)) + { + JsonSerializer jsonSerializer = new JsonSerializer(); + using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter)) + { + jsonSerializer.Serialize(jsonWriter, containerProperties); + jsonWriter.Flush(); + + ms.Position = 0; + using (StreamReader streamReader = new StreamReader(ms)) + { + return streamReader.ReadToEnd(); + } + } + } + } } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Fluent/ContainerDefinitionForCreateTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Fluent/ContainerDefinitionForCreateTests.cs index 7433b56150..7d4c20cc71 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Fluent/ContainerDefinitionForCreateTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Fluent/ContainerDefinitionForCreateTests.cs @@ -357,8 +357,7 @@ public async Task ValidateFullTextPolicyAndIndexUsingContainerBuilder() }, new Cosmos.FullTextPath() { - Path = fullTextPath3, - Language = "en-US", + Path = fullTextPath3 }, }; @@ -412,7 +411,7 @@ public async Task ValidateFullTextPolicyAndIndexUsingContainerBuilder() && fullTextPath2.Equals(settings.FullTextPolicy.FullTextPaths[1].Path) && "en-US".Equals(settings.FullTextPolicy.FullTextPaths[1].Language) && fullTextPath3.Equals(settings.FullTextPolicy.FullTextPaths[2].Path) - && "en-US".Equals(settings.FullTextPolicy.FullTextPaths[2].Language) + && (settings.FullTextPolicy.FullTextPaths[2].Language == null) && fullTextPath1.Equals(settings.IndexingPolicy.FullTextIndexes[0].Path) && fullTextPath2.Equals(settings.IndexingPolicy.FullTextIndexes[1].Path) && fullTextPath3.Equals(settings.IndexingPolicy.FullTextIndexes[2].Path)), @@ -421,6 +420,66 @@ public async Task ValidateFullTextPolicyAndIndexUsingContainerBuilder() It.IsAny()), Times.Once); } + [TestMethod] + public async Task ValidateFullTextPolicyAndIndexUsingContainerBuilderOptionalLanguage() + { + string fullTextPath1 = "/fts1"; + + Collection fullTextPaths = new Collection() + { + new Cosmos.FullTextPath() + { + Path = fullTextPath1 + }, + }; + + Mock mockContainerResponse = new Mock(); + mockContainerResponse + .Setup(x => x.StatusCode) + .Returns(HttpStatusCode.Created); + + Mock mockContainers = new Mock(); + Mock mockClient = new Mock(); + mockContainers.Setup(m => m.Client).Returns(mockClient.Object); + mockContainers + .Setup(c => c.CreateContainerAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(mockContainerResponse.Object); + mockContainers + .Setup(c => c.Id) + .Returns(Guid.NewGuid().ToString()); + + ContainerBuilder containerFluentDefinitionForCreate = new ContainerBuilder( + mockContainers.Object, + containerName, + partitionKey); + + ContainerResponse response = await containerFluentDefinitionForCreate + .WithFullTextPolicy( + defaultLanguage: null, + fullTextPaths: fullTextPaths) + .Attach() + .WithIndexingPolicy() + .WithFullTextIndex() + .Path(fullTextPath1) + .Attach() + .Attach() + .CreateAsync(); + + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + mockContainers.Verify(c => c.CreateContainerAsync( + It.Is((settings) => settings.FullTextPolicy.FullTextPaths.Count == 1 + && (settings.FullTextPolicy.DefaultLanguage == null) + && fullTextPath1.Equals(settings.FullTextPolicy.FullTextPaths[0].Path) + && (settings.FullTextPolicy.FullTextPaths[0].Language == null)), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); + } + [TestMethod] public async Task ValidateVectorEmbeddingsAndIndexingPolicyUsingContainerBuilder() {