Skip to content

Commit

Permalink
Add strongly typed FacetResult helpers for Search (#12620)
Browse files Browse the repository at this point in the history
* Add strongly typed FacetResult helpers for Search

Fixes #10613.  We kept this open for a long time hoping we'd be able to do
something nifty using type info, but there's just not a great answer so we're
stealing the Track 1 experience.
  • Loading branch information
tg-msft authored Jun 9, 2020
1 parent a9cd95d commit 423fa77
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1871,15 +1871,26 @@ public partial class FacetResult : System.Collections.Generic.IEnumerable<System
{
internal FacetResult() { }
public long? Count { get { throw null; } }
public Azure.Search.Documents.Models.FacetType FacetType { get { throw null; } }
public object From { get { throw null; } }
public object this[string key] { get { throw null; } }
public System.Collections.Generic.IEnumerable<string> Keys { get { throw null; } }
int System.Collections.Generic.IReadOnlyCollection<System.Collections.Generic.KeyValuePair<System.String,System.Object>>.Count { get { throw null; } }
public object To { get { throw null; } }
public object Value { get { throw null; } }
public System.Collections.Generic.IEnumerable<object> Values { get { throw null; } }
public Azure.Search.Documents.Models.RangeFacetResult<T> AsRangeFacetResult<T>() where T : struct { throw null; }
public Azure.Search.Documents.Models.ValueFacetResult<T> AsValueFacetResult<T>() { throw null; }
public bool ContainsKey(string key) { throw null; }
public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object>> 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,
Expand Down Expand Up @@ -1928,6 +1939,13 @@ internal IndexingResult() { }
public int Status { get { throw null; } }
public bool Succeeded { get { throw null; } }
}
public partial class RangeFacetResult<T> 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.KeyValuePair<string, object>>, System.Collections.Generic.IDictionary<string, object>, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>>, System.Collections.IEnumerable
{
public SearchDocument() { }
Expand Down Expand Up @@ -2000,4 +2018,10 @@ internal SuggestResults() { }
public double? Coverage { get { throw null; } }
public System.Collections.Generic.IList<Azure.Search.Documents.Models.SearchSuggestion<T>> Results { get { throw null; } }
}
public partial class ValueFacetResult<T>
{
public ValueFacetResult(long count, T value) { }
public long Count { get { throw null; } }
public T Value { get { throw null; } }
}
}
80 changes: 80 additions & 0 deletions sdk/search/Azure.Search.Documents/src/Models/FacetResult.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public FacetType FacetType => (Value != null) ? FacetType.Value : FacetType.Range;

/// <summary>
/// Gets the value of the facet, or the inclusive lower bound if it's
/// an interval facet.
/// </summary>
public object Value => GetValue(Constants.ValueKey);

/// <summary>
/// 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).
/// </summary>
public object From => GetValue(Constants.FromKey);

/// <summary>
/// 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).
/// </summary>
public object To => GetValue(Constants.ToKey);

/// <summary>
/// Get the value of a key like "value" or return null if not found.
/// </summary>
/// <param name="key">The name of the key to lookup.</param>
/// <returns>The value of the key or null.</returns>
private object GetValue(string key) =>
AdditionalProperties.TryGetValue(key, out object value) ? value : null;

/// <summary>
/// Attempts to convert the facet to a range facet of the given type.
/// </summary>
/// <typeparam name="T">
/// A type that matches the type of the field to which the facet was
/// applied. Valid types include <see cref="DateTimeOffset"/>,
/// <see cref="Double"/>, and <see cref="Int64"/>.
/// </typeparam>
/// <returns>A new strongly-typed range facet instance.</returns>
/// <exception cref="InvalidCastException">
/// This instance is not a range facet of the given type.
/// </exception>
public RangeFacetResult<T> AsRangeFacetResult<T>() where T : struct
{
if (FacetType != FacetType.Range) { throw new InvalidCastException(); }
return new RangeFacetResult<T>(Count.GetValueOrDefault(), (T?)From, (T?)To);
}

/// <summary>
/// Attempts to convert the facet to a value facet of the given type.
/// </summary>
/// <typeparam name="T">
/// A type that matches the type of the field to which the facet was
/// applied.
/// </typeparam>
/// <returns>A new strongly-typed value facet instance.</returns>
/// <exception cref="InvalidCastException">
/// This instance is not a value facet of the given type.
/// </exception>
public ValueFacetResult<T> AsValueFacetResult<T>()
{
if (FacetType != FacetType.Value) { throw new InvalidCastException(); }
return new ValueFacetResult<T>(Count.GetValueOrDefault(), (T)Value);
}
}
}
21 changes: 21 additions & 0 deletions sdk/search/Azure.Search.Documents/src/Models/FacetType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Search.Documents.Models
{
/// <summary>
/// Specifies the type of a facet query result.
/// </summary>
public enum FacetType
{
/// <summary>
/// The facet counts documents with a particular field value.
/// </summary>
Value = 0,

/// <summary>
/// The facet counts documents with a field value in a particular range.
/// </summary>
Range
}
}
63 changes: 63 additions & 0 deletions sdk/search/Azure.Search.Documents/src/Models/RangeFacetResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Azure.Search.Documents.Models
{
/// <summary>
/// A single bucket of a range facet query result that reports the number
/// of documents with a field value falling within a particular range.
/// </summary>
/// <typeparam name="T">
/// A type that matches the type of the field to which the facet was
/// applied. Valid types include <see cref="DateTimeOffset"/>,
/// <see cref="Double"/>, and <see cref="Int64"/>.
/// </typeparam>
public class RangeFacetResult<T> where T : struct
{
/// <summary>
/// Gets the approximate count of documents falling within the bucket
/// described by this facet.
/// </summary>
public long Count { get; }

/// <summary>
/// Gets a value indicating the inclusive lower bound of the facet's
/// range, or <c>null</c> to indicate that there is no lower bound
/// (for the first bucket).
/// </summary>
public T? From { get; }

/// <summary>
/// Gets a value indicating the exclusive upper bound of the facet's
/// range, or <c>null</c> to indicate that there is no upper bound
/// (for the last bucket).
/// </summary>
public T? To { get; }

/// <summary>
/// Creates a new instance of the RangeFacetResult class.
/// </summary>
/// <param name="count">
/// The approximate count of documents falling within the bucket
/// described by this facet.
/// </param>
/// <param name="from">
/// A value indicating the inclusive lower bound of the facet's range,
/// or <c>null</c> to indicate that there is no lower bound (for the
/// first bucket).
/// </param>
/// <param name="to">
/// A value indicating the exclusive upper bound of the facet's range,
/// or <c>null</c> to indicate that there is no upper bound (for the
/// last bucket).
/// </param>
public RangeFacetResult(long count, T? from, T? to)
{
From = from;
To = to;
Count = count;
}
}
}
48 changes: 48 additions & 0 deletions sdk/search/Azure.Search.Documents/src/Models/ValueFacetResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Azure.Search.Documents.Models
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">
/// A type that matches the type of the field to which the facet was
/// applied.
/// </typeparam>
public class ValueFacetResult<T>
{
/// <summary>
/// Gets the approximate count of documents falling within the bucket
/// described by this facet.
/// </summary>
public long Count { get; }

/// <summary>
/// Gets the value of the facet, or the inclusive lower bound if it's
/// an interval facet.
/// </summary>
public T Value { get; }

/// <summary>
/// Creates a new instance of the ValueFacetResult class.
/// </summary>
/// <param name="count">
/// The approximate count of documents falling within the bucket
/// described by this facet.
/// </param>
/// <param name="value">
/// The value of the facet, or the inclusive lower bound if it's an
/// interval facet.
/// </param>
public ValueFacetResult(long count, T value)
{
Value = value;
Count = count;
}
}
}
10 changes: 10 additions & 0 deletions sdk/search/Azure.Search.Documents/src/Utilities/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ internal static class Constants
/// </summary>
public const string CountKey = "count";

/// <summary>
/// The to key.
/// </summary>
public const string FromKey = "from";

/// <summary>
/// The from key.
/// </summary>
public const string ToKey = "to";

/// <summary>
/// Initial ArrayPool rental size for copying unseekable streams in
/// our sync <see cref="JsonExtensions.Deserialize{T}"/> method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<FacetResult> facets = GetFacetsForField(response.Value.Facets, "rooms/baseRate", 4);
RangeFacetResult<double> first = facets.ElementAt(0).AsRangeFacetResult<double>();
Assert.AreEqual(1, first.Count);
Assert.AreEqual(5, first.To);
RangeFacetResult<double> second = facets.ElementAt(1).AsRangeFacetResult<double>();
Assert.AreEqual(1, second.Count);
Assert.AreEqual(5, second.From);
Assert.AreEqual(8, second.To);
RangeFacetResult<double> last = facets.ElementAt(3).AsRangeFacetResult<double>();
Assert.AreEqual(null, first.From);
Assert.AreEqual(null, last.To);

}

[Test]
Expand Down Expand Up @@ -600,6 +614,15 @@ await AssertKeysContains(
MakeValueFacet(1, "restaurant"),
MakeValueFacet(1, "view"),
MakeValueFacet(4, "wifi"));

// Check strongly typed value facets
ICollection<FacetResult> facets = GetFacetsForField(response.Value.Facets, "rating", 2);
ValueFacetResult<int> first = facets.ElementAt(0).AsValueFacetResult<int>();
Assert.AreEqual(5, first.Value);
Assert.AreEqual(1, first.Count);
ValueFacetResult<int> second = facets.ElementAt(1).AsValueFacetResult<int>();
Assert.AreEqual(4, second.Value);
Assert.AreEqual(4, second.Count);
}

[Test]
Expand Down

0 comments on commit 423fa77

Please sign in to comment.