diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 95d0b6b8..1a96326a 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -761,3 +761,18 @@ update_proximity_precision_settings_1: |- await client.Index("books").UpdateProximityPrecisionAsync("byAttribute"); reset_proximity_precision_settings_1: |- await client.Index("books").ResetProximityPrecisionAsync(); +search_parameter_reference_distinct_1: |- + var params = new SearchQuery() + { + Distinct = "ATTRIBUTE_A" + }; + await client.Index("INDEX_NAME").SearchAsync("QUERY TERMS", params); +distinct_attribute_guide_filterable_1: |- + List attributes = new() { "product_id", "sku", "url" }; + TaskInfo result = await client.Index("products").UpdateFilterableAttributesAsync(attributes); +distinct_attribute_guide_distinct_parameter_1: |- + var params = new SearchQuery() + { + Distinct = "sku" + }; + await client.Index("products").SearchAsync("white shirt", params); diff --git a/.env b/.env index 79e2e8bc..767307bf 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -MEILISEARCH_VERSION=v1.6.0 +MEILISEARCH_VERSION=v1.9.0 PROXIED_MEILISEARCH=http://nginx/api/ MEILISEARCH_URL=http://meilisearch:7700 diff --git a/src/Meilisearch/SearchQuery.cs b/src/Meilisearch/SearchQuery.cs index 4efa4ea1..3bcf45ad 100644 --- a/src/Meilisearch/SearchQuery.cs +++ b/src/Meilisearch/SearchQuery.cs @@ -130,5 +130,11 @@ public class SearchQuery /// [JsonPropertyName("page")] public int? Page { get; set; } + + /// + /// Sets distinct attribute at search time. + /// + [JsonPropertyName("distinct")] + public string Distinct { get; set; } } } diff --git a/tests/Meilisearch.Tests/Datasets.cs b/tests/Meilisearch.Tests/Datasets.cs index 3302dcc2..610454a2 100644 --- a/tests/Meilisearch.Tests/Datasets.cs +++ b/tests/Meilisearch.Tests/Datasets.cs @@ -17,6 +17,8 @@ internal static class Datasets public static readonly string MoviesForFacetingJsonPath = Path.Combine(BasePath, "movies_for_faceting.json"); public static readonly string MoviesWithIntIdJsonPath = Path.Combine(BasePath, "movies_with_int_id.json"); public static readonly string MoviesWithInfoJsonPath = Path.Combine(BasePath, "movies_with_info.json"); + + public static readonly string ProductsForDistinctJsonPath = Path.Combine(BasePath, "products_for_distinct_search.json"); } public class DatasetSmallMovie diff --git a/tests/Meilisearch.Tests/Datasets/products_for_distinct_search.json b/tests/Meilisearch.Tests/Datasets/products_for_distinct_search.json new file mode 100644 index 00000000..56ef9b70 --- /dev/null +++ b/tests/Meilisearch.Tests/Datasets/products_for_distinct_search.json @@ -0,0 +1,100 @@ +[ + { + "id": 1, + "description": "Leather Jacket", + "brand": "Lee Jeans", + "product_id": "123456", + "color": "Brown" + }, + { + "id": 2, + "description": "Leather Jacket", + "brand": "Lee Jeans", + "product_id": "123456", + "color": "Black" + }, + { + "id": 3, + "description": "Leather Jacket", + "brand": "Lee Jeans", + "product_id": "123456", + "color": "Blue" + }, + { + "id": 4, + "description": "T-Shirt", + "brand": "Nike", + "product_id": "789012", + "color": "Red" + }, + { + "id": 5, + "description": "T-Shirt", + "brand": "Nike", + "product_id": "789012", + "color": "Blue" + }, + { + "id": 6, + "description": "Running Shoes", + "brand": "Adidas", + "product_id": "456789", + "color": "Black" + }, + { + "id": 7, + "description": "Running Shoes", + "brand": "Adidas", + "product_id": "456789", + "color": "White" + }, + { + "id": 8, + "description": "Hoodie", + "brand": "Puma", + "product_id": "987654", + "color": "Gray" + }, + { + "id": 9, + "description": "Sweater", + "brand": "Gap", + "product_id": "234567", + "color": "Green" + }, + { + "id": 10, + "description": "Sweater", + "brand": "Gap", + "product_id": "234567", + "color": "Red" + }, + { + "id": 11, + "description": "Sweater", + "brand": "Gap", + "product_id": "234567", + "color": "Blue" + }, + { + "id": 12, + "description": "Jeans", + "brand": "Levi's", + "product_id": "345678", + "color": "Indigo" + }, + { + "id": 13, + "description": "Jeans", + "brand": "Levi's", + "product_id": "345678", + "color": "Black" + }, + { + "id": 14, + "description": "Jeans", + "brand": "Levi's", + "product_id": "345678", + "color": "Stone Wash" + } +] diff --git a/tests/Meilisearch.Tests/IndexFixture.cs b/tests/Meilisearch.Tests/IndexFixture.cs index de3e02a6..f0499c17 100644 --- a/tests/Meilisearch.Tests/IndexFixture.cs +++ b/tests/Meilisearch.Tests/IndexFixture.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Text.Json; using System.Threading.Tasks; using Xunit; @@ -123,6 +124,37 @@ public async Task SetUpIndexForNestedSearch(string indexUid) return index; } + public async Task SetUpIndexForDistinctProductsSearch(string indexUid) + { + var index = DefaultClient.Index(indexUid); + var products = await JsonFileReader.ReadAsync>(Datasets.ProductsForDistinctJsonPath); + var task = await index.AddDocumentsAsync(products, primaryKey: "id"); + // Check the documents have been added + var finishedTask = await index.WaitForTaskAsync(task.TaskUid); + if (finishedTask.Status != TaskInfoStatus.Succeeded) + { + throw new Exception($"The documents were not added during SetUpIndexForDistinctProductsSearch.\n" + + $"Impossible to run the tests.\n" + + $"{JsonSerializer.Serialize(finishedTask.Error)}"); + } + + var settings = new Settings + { + FilterableAttributes = new string[] { "product_id" }, + }; + task = await index.UpdateSettingsAsync(settings); + + // Check the settings have been added + finishedTask = await index.WaitForTaskAsync(task.TaskUid); + if (finishedTask.Status != TaskInfoStatus.Succeeded) + { + throw new Exception($"The documents were not added during SetUpIndexForDistinctProductsSearch.\n" + + $"Impossible to run the tests.\n" + + $"{JsonSerializer.Serialize(finishedTask.Error)}"); + } + + return index; + } public async Task DeleteAllIndexes() { diff --git a/tests/Meilisearch.Tests/Product.cs b/tests/Meilisearch.Tests/Product.cs new file mode 100644 index 00000000..c06a4ae5 --- /dev/null +++ b/tests/Meilisearch.Tests/Product.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Meilisearch.Tests +{ + public class Product + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("description")] + public string Description { get; set; } + + [JsonPropertyName("brand")] + public string Brand { get; set; } + + [JsonPropertyName("product_id")] + public string ProductId { get; set; } + + [JsonPropertyName("color")] + public string Color { get; set; } + } +} diff --git a/tests/Meilisearch.Tests/SearchTests.cs b/tests/Meilisearch.Tests/SearchTests.cs index 79d081f5..776958e2 100644 --- a/tests/Meilisearch.Tests/SearchTests.cs +++ b/tests/Meilisearch.Tests/SearchTests.cs @@ -13,6 +13,7 @@ public abstract class SearchTests : IAsyncLifetime where TFixture : In private Index _nestedIndex; private Index _indexForFaceting; private Index _indexWithIntId; + private Index _productIndexForDistinct; private readonly TFixture _fixture; @@ -28,6 +29,7 @@ public async Task InitializeAsync() _indexForFaceting = await _fixture.SetUpIndexForFaceting("IndexForFaceting-SearchTests"); _indexWithIntId = await _fixture.SetUpBasicIndexWithIntId("IndexWithIntId-SearchTests"); _nestedIndex = await _fixture.SetUpIndexForNestedSearch("IndexForNestedDocs-SearchTests"); + _productIndexForDistinct = await _fixture.SetUpIndexForDistinctProductsSearch("IndexForDistinctProducts-SearchTests"); } public Task DisposeAsync() => Task.CompletedTask; @@ -503,5 +505,25 @@ public async Task CustomSearchWithShowRankingScore() var movies = await _basicIndex.SearchAsync("iron man", searchQuery); Assert.NotNull(movies.Hits.First()._RankingScore); } + [Fact] + public async Task CustomSearchProductsWithoutDistinct() + { + var searchQuery = new SearchQuery() + { + + }; + var products = await _productIndexForDistinct.SearchAsync("", searchQuery); + products.Hits.Count.Should().Be(14); + } + [Fact] + public async Task CustomSearchProductsWithDistinct() + { + var searchQuery = new SearchQuery() + { + Distinct = "product_id" + }; + var products = await _productIndexForDistinct.SearchAsync("", searchQuery); + products.Hits.Count.Should().Be(6); + } } }