diff --git a/sdk/search/Azure.Search.Documents/api/Azure.Search.Documents.netstandard2.0.cs b/sdk/search/Azure.Search.Documents/api/Azure.Search.Documents.netstandard2.0.cs index 9ddfb6c1a8eb8..9e712d977c32a 100644 --- a/sdk/search/Azure.Search.Documents/api/Azure.Search.Documents.netstandard2.0.cs +++ b/sdk/search/Azure.Search.Documents/api/Azure.Search.Documents.netstandard2.0.cs @@ -1871,15 +1871,26 @@ public partial class FacetResult : System.Collections.Generic.IEnumerable Keys { get { throw null; } } int System.Collections.Generic.IReadOnlyCollection>.Count { get { throw null; } } + public object To { get { throw null; } } + public object Value { get { throw null; } } public System.Collections.Generic.IEnumerable Values { get { throw null; } } + public Azure.Search.Documents.Models.RangeFacetResult AsRangeFacetResult() where T : struct { throw null; } + public Azure.Search.Documents.Models.ValueFacetResult AsValueFacetResult() { throw null; } public bool ContainsKey(string key) { throw null; } public System.Collections.Generic.IEnumerator> GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public bool TryGetValue(string key, out object value) { throw null; } } + public enum FacetType + { + Value = 0, + Range = 1, + } public enum IndexActionType { Upload = 0, @@ -1928,6 +1939,13 @@ internal IndexingResult() { } public int Status { get { throw null; } } public bool Succeeded { get { throw null; } } } + public partial class RangeFacetResult where T : struct + { + public RangeFacetResult(long count, T? from, T? to) { } + public long Count { get { throw null; } } + public T? From { get { throw null; } } + public T? To { get { throw null; } } + } public partial class SearchDocument : System.Dynamic.DynamicObject, System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { public SearchDocument() { } @@ -2000,4 +2018,10 @@ internal SuggestResults() { } public double? Coverage { get { throw null; } } public System.Collections.Generic.IList> Results { get { throw null; } } } + public partial class ValueFacetResult + { + public ValueFacetResult(long count, T value) { } + public long Count { get { throw null; } } + public T Value { get { throw null; } } + } } diff --git a/sdk/search/Azure.Search.Documents/src/Models/FacetResult.cs b/sdk/search/Azure.Search.Documents/src/Models/FacetResult.cs new file mode 100644 index 0000000000000..e261bb27290d3 --- /dev/null +++ b/sdk/search/Azure.Search.Documents/src/Models/FacetResult.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Search.Documents.Models +{ + public partial class FacetResult + { + /// + /// Gets the type of this facet. Value facets count documents with a + /// particular field value and Range facets count documents with a + /// field value in a particular range. + /// + public FacetType FacetType => (Value != null) ? FacetType.Value : FacetType.Range; + + /// + /// Gets the value of the facet, or the inclusive lower bound if it's + /// an interval facet. + /// + public object Value => GetValue(Constants.ValueKey); + + /// + /// Gets a value indicating the inclusive lower bound of the facet's + /// range, or null to indicate that there is no lower bound (i.e. -- + /// for the first bucket). + /// + public object From => GetValue(Constants.FromKey); + + /// + /// Gets a value indicating the exclusive upper bound of the facet's + /// range, or null to indicate that there is no upper bound (i.e. -- + /// for the last bucket). + /// + public object To => GetValue(Constants.ToKey); + + /// + /// Get the value of a key like "value" or return null if not found. + /// + /// The name of the key to lookup. + /// The value of the key or null. + private object GetValue(string key) => + AdditionalProperties.TryGetValue(key, out object value) ? value : null; + + /// + /// Attempts to convert the facet to a range facet of the given type. + /// + /// + /// A type that matches the type of the field to which the facet was + /// applied. Valid types include , + /// , and . + /// + /// A new strongly-typed range facet instance. + /// + /// This instance is not a range facet of the given type. + /// + public RangeFacetResult AsRangeFacetResult() where T : struct + { + if (FacetType != FacetType.Range) { throw new InvalidCastException(); } + return new RangeFacetResult(Count.GetValueOrDefault(), (T?)From, (T?)To); + } + + /// + /// Attempts to convert the facet to a value facet of the given type. + /// + /// + /// A type that matches the type of the field to which the facet was + /// applied. + /// + /// A new strongly-typed value facet instance. + /// + /// This instance is not a value facet of the given type. + /// + public ValueFacetResult AsValueFacetResult() + { + if (FacetType != FacetType.Value) { throw new InvalidCastException(); } + return new ValueFacetResult(Count.GetValueOrDefault(), (T)Value); + } + } +} diff --git a/sdk/search/Azure.Search.Documents/src/Models/FacetType.cs b/sdk/search/Azure.Search.Documents/src/Models/FacetType.cs new file mode 100644 index 0000000000000..ed6a6d144bc86 --- /dev/null +++ b/sdk/search/Azure.Search.Documents/src/Models/FacetType.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Search.Documents.Models +{ + /// + /// Specifies the type of a facet query result. + /// + public enum FacetType + { + /// + /// The facet counts documents with a particular field value. + /// + Value = 0, + + /// + /// The facet counts documents with a field value in a particular range. + /// + Range + } +} diff --git a/sdk/search/Azure.Search.Documents/src/Models/RangeFacetResult.cs b/sdk/search/Azure.Search.Documents/src/Models/RangeFacetResult.cs new file mode 100644 index 0000000000000..e60e670895c32 --- /dev/null +++ b/sdk/search/Azure.Search.Documents/src/Models/RangeFacetResult.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Search.Documents.Models +{ + /// + /// A single bucket of a range facet query result that reports the number + /// of documents with a field value falling within a particular range. + /// + /// + /// A type that matches the type of the field to which the facet was + /// applied. Valid types include , + /// , and . + /// + public class RangeFacetResult where T : struct + { + /// + /// Gets the approximate count of documents falling within the bucket + /// described by this facet. + /// + public long Count { get; } + + /// + /// Gets a value indicating the inclusive lower bound of the facet's + /// range, or null to indicate that there is no lower bound + /// (for the first bucket). + /// + public T? From { get; } + + /// + /// Gets a value indicating the exclusive upper bound of the facet's + /// range, or null to indicate that there is no upper bound + /// (for the last bucket). + /// + public T? To { get; } + + /// + /// Creates a new instance of the RangeFacetResult class. + /// + /// + /// The approximate count of documents falling within the bucket + /// described by this facet. + /// + /// + /// A value indicating the inclusive lower bound of the facet's range, + /// or null to indicate that there is no lower bound (for the + /// first bucket). + /// + /// + /// A value indicating the exclusive upper bound of the facet's range, + /// or null to indicate that there is no upper bound (for the + /// last bucket). + /// + public RangeFacetResult(long count, T? from, T? to) + { + From = from; + To = to; + Count = count; + } + } +} diff --git a/sdk/search/Azure.Search.Documents/src/Models/ValueFacetResult.cs b/sdk/search/Azure.Search.Documents/src/Models/ValueFacetResult.cs new file mode 100644 index 0000000000000..99140ee25dab6 --- /dev/null +++ b/sdk/search/Azure.Search.Documents/src/Models/ValueFacetResult.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Search.Documents.Models +{ + /// + /// A single bucket of a simple or interval facet query result that reports + /// the number of documents with a field falling within a particular + /// interval or having a specific value. + /// + /// + /// A type that matches the type of the field to which the facet was + /// applied. + /// + public class ValueFacetResult + { + /// + /// Gets the approximate count of documents falling within the bucket + /// described by this facet. + /// + public long Count { get; } + + /// + /// Gets the value of the facet, or the inclusive lower bound if it's + /// an interval facet. + /// + public T Value { get; } + + /// + /// Creates a new instance of the ValueFacetResult class. + /// + /// + /// The approximate count of documents falling within the bucket + /// described by this facet. + /// + /// + /// The value of the facet, or the inclusive lower bound if it's an + /// interval facet. + /// + public ValueFacetResult(long count, T value) + { + Value = value; + Count = count; + } + } +} diff --git a/sdk/search/Azure.Search.Documents/src/Utilities/Constants.cs b/sdk/search/Azure.Search.Documents/src/Utilities/Constants.cs index 7ff64ebabc4fd..e802dd2ac11be 100644 --- a/sdk/search/Azure.Search.Documents/src/Utilities/Constants.cs +++ b/sdk/search/Azure.Search.Documents/src/Utilities/Constants.cs @@ -102,6 +102,16 @@ internal static class Constants /// public const string CountKey = "count"; + /// + /// The to key. + /// + public const string FromKey = "from"; + + /// + /// The from key. + /// + public const string ToKey = "to"; + /// /// Initial ArrayPool rental size for copying unseekable streams in /// our sync method. diff --git a/sdk/search/Azure.Search.Documents/tests/DocumentOperations/SearchTests.cs b/sdk/search/Azure.Search.Documents/tests/DocumentOperations/SearchTests.cs index f1bb40ea83f4d..b6f77d1454a40 100644 --- a/sdk/search/Azure.Search.Documents/tests/DocumentOperations/SearchTests.cs +++ b/sdk/search/Azure.Search.Documents/tests/DocumentOperations/SearchTests.cs @@ -534,6 +534,20 @@ await AssertKeysContains( GetFacetsForField(response.Value.Facets, "lastRenovationDate", 2), MakeRangeFacet(count: 5, from: null, to: new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero)), MakeRangeFacet(count: 2, from: new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero), to: null)); + + // Check strongly typed range facets + ICollection facets = GetFacetsForField(response.Value.Facets, "rooms/baseRate", 4); + RangeFacetResult first = facets.ElementAt(0).AsRangeFacetResult(); + Assert.AreEqual(1, first.Count); + Assert.AreEqual(5, first.To); + RangeFacetResult second = facets.ElementAt(1).AsRangeFacetResult(); + Assert.AreEqual(1, second.Count); + Assert.AreEqual(5, second.From); + Assert.AreEqual(8, second.To); + RangeFacetResult last = facets.ElementAt(3).AsRangeFacetResult(); + Assert.AreEqual(null, first.From); + Assert.AreEqual(null, last.To); + } [Test] @@ -600,6 +614,15 @@ await AssertKeysContains( MakeValueFacet(1, "restaurant"), MakeValueFacet(1, "view"), MakeValueFacet(4, "wifi")); + + // Check strongly typed value facets + ICollection facets = GetFacetsForField(response.Value.Facets, "rating", 2); + ValueFacetResult first = facets.ElementAt(0).AsValueFacetResult(); + Assert.AreEqual(5, first.Value); + Assert.AreEqual(1, first.Count); + ValueFacetResult second = facets.ElementAt(1).AsValueFacetResult(); + Assert.AreEqual(4, second.Value); + Assert.AreEqual(4, second.Count); } [Test]