Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement distance_feature query #3983

Merged
merged 7 commits into from
Aug 9, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Nest/QueryDsl/Abstractions/Container/IQueryContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ public interface IQueryContainer
[DataMember(Name = "rank_feature")]
IRankFeatureQuery RankFeature { get; set; }

/// <inheritdoc cref="IDistanceFeatureQuery"/>
[DataMember(Name = "distance_feature")]
IDistanceFeatureQuery DistanceFeature { get; set; }

void Accept(IQueryVisitor visitor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public partial class QueryContainer : IQueryContainer, IDescriptor
private ICommonTermsQuery _commonTerms;
private IConstantScoreQuery _constantScore;
private IDisMaxQuery _disMax;
private IDistanceFeatureQuery _distanceFeature;
private IExistsQuery _exists;
private IFunctionScoreQuery _functionScore;
private IFuzzyQuery _fuzzy;
Expand Down Expand Up @@ -91,6 +92,12 @@ IDisMaxQuery IQueryContainer.DisMax
set => _disMax = Set(value);
}

IDistanceFeatureQuery IQueryContainer.DistanceFeature
{
get => _distanceFeature;
set => _distanceFeature = Set(value);
}

IExistsQuery IQueryContainer.Exists
{
get => _exists;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ public QueryContainer HasParent<TParent>(Func<HasParentQueryDescriptor<TParent>,
public QueryContainer DisMax(Func<DisMaxQueryDescriptor<T>, IDisMaxQuery> selector) =>
WrapInContainer(selector, (query, container) => container.DisMax = query);

/// <inheritdoc cref="IDistanceFeatureQuery"/>
public QueryContainer DistanceFeature(Func<DistanceFeatureQueryDescriptor<T>, IDistanceFeatureQuery> selector) =>
codebrain marked this conversation as resolved.
Show resolved Hide resolved
WrapInContainer(selector, (query, container) => container.DistanceFeature = query);

/// <summary>
/// A query that wraps a filter or another query and simply returns a constant score equal to the query boost
/// for every document in the filter. Maps to Lucene ConstantScoreQuery.
Expand Down
4 changes: 4 additions & 0 deletions src/Nest/QueryDsl/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public static QueryContainer DateRange(Func<DateRangeQueryDescriptor<T>, IDateRa
public static QueryContainer DisMax(Func<DisMaxQueryDescriptor<T>, IDisMaxQuery> selector) =>
new QueryContainerDescriptor<T>().DisMax(selector);

/// <inheritdoc cref="IDistanceFeatureQuery"/>
public static QueryContainer DistanceFeature(Func<DistanceFeatureQueryDescriptor<T>, IDistanceFeatureQuery> selector) =>
new QueryContainerDescriptor<T>().DistanceFeature(selector);

public static QueryContainer Exists(Func<ExistsQueryDescriptor<T>, IExistsQuery> selector) =>
new QueryContainerDescriptor<T>().Exists(selector);

Expand Down
153 changes: 153 additions & 0 deletions src/Nest/QueryDsl/Specialized/DistanceFeature/DistanceFeatureQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System;
using System.Runtime.Serialization;
using Elasticsearch.Net.Utf8Json;
using Elasticsearch.Net.Utf8Json.Internal;

namespace Nest
{
/// <summary>
/// Boosts the relevance score of documents closer to a provided origin date or point. For example, you can use this query to give
/// more weight to documents closer to a certain date or location.
/// You can use the distance_feature query to find the nearest neighbors to a location. You can also use the query in a bool
/// search’s should filter to add boosted relevance scores to the bool query’s scores.
/// </summary>
[JsonFormatter(typeof(DistanceFeatureQueryFormatter))]
[InterfaceDataContract]
public interface IDistanceFeatureQuery : IFieldNameQuery
{
/// <summary>
/// Date or point of origin used to calculate distances.
// If the field value is a date or date_nanos field, the origin value must be a date. Date Math, such as now-1h, is supported.
// If the field value is a geo_point field, the origin value must be a geopoint.
/// </summary>
[DataMember(Name = "origin")]
Union<DateMath, GeoLocation> Origin { get; set; }

/// <summary>
/// Distance from the origin at which relevance scores receive half of the boost value.
// If the field value is a date or date_nanos field, the pivot value must be a time unit, such as 1h or 10d.
// If the field value is a geo_point field, the pivot value must be a distance unit, such as 1km or 12m.
/// </summary>
[DataMember(Name = "pivot")]
Union<Time, Distance> Pivot { get; set; }
}

public class DistanceFeatureQuery : FieldNameQueryBase, IDistanceFeatureQuery
codebrain marked this conversation as resolved.
Show resolved Hide resolved
{
protected override bool Conditionless => IsConditionless(this);

internal static bool IsConditionless(IDistanceFeatureQuery q) => q.Field.IsConditionless() || q.Origin == null && q.Pivot == null;

internal override void InternalWrapInContainer(IQueryContainer container) => container.DistanceFeature = this;

public Union<DateMath, GeoLocation> Origin { get; set; }
codebrain marked this conversation as resolved.
Show resolved Hide resolved

public Union<Time, Distance> Pivot { get; set; }
codebrain marked this conversation as resolved.
Show resolved Hide resolved
}

public class DistanceFeatureQueryDescriptor<T>
: FieldNameQueryDescriptorBase<DistanceFeatureQueryDescriptor<T>, IDistanceFeatureQuery, T>
, IDistanceFeatureQuery where T : class
{
Union<DateMath, GeoLocation> IDistanceFeatureQuery.Origin { get; set; }

Union<Time, Distance> IDistanceFeatureQuery.Pivot { get; set; }

protected override bool Conditionless => DistanceFeatureQuery.IsConditionless(this);

/// <inheritdoc cref="IDistanceFeatureQuery.Origin" />
public DistanceFeatureQueryDescriptor<T> Origin(DateMath origin) =>
Assign(origin, (a, v) => a.Origin = v);

/// <inheritdoc cref="IDistanceFeatureQuery.Origin" />
public DistanceFeatureQueryDescriptor<T> Origin(GeoLocation origin) =>
Assign(origin, (a, v) => a.Origin = v);

/// <inheritdoc cref="IDistanceFeatureQuery.Pivot" />
public DistanceFeatureQueryDescriptor<T> Pivot(Time pivot) =>
Assign(pivot, (a, v) => a.Pivot = v);

/// <inheritdoc cref="IDistanceFeatureQuery.Pivot" />
public DistanceFeatureQueryDescriptor<T> Pivot(Distance pivot) =>
Assign(pivot, (a, v) => a.Pivot = v);
}

internal class DistanceFeatureQueryFormatter : IJsonFormatter<IDistanceFeatureQuery>
codebrain marked this conversation as resolved.
Show resolved Hide resolved
{
public void Serialize(ref JsonWriter writer, IDistanceFeatureQuery value, IJsonFormatterResolver formatterResolver)
{
if (value == null)
{
writer.WriteNull();
return;
}

writer.WriteBeginObject();

writer.WritePropertyName("field");
var fieldFormatter = formatterResolver.GetFormatter<Field>();
fieldFormatter.Serialize(ref writer, value.Field, formatterResolver);
writer.WriteValueSeparator();

writer.WritePropertyName("origin");
formatterResolver.GetFormatter<Union<DateMath, GeoLocation>>().Serialize(ref writer, value.Origin, formatterResolver);

writer.WriteValueSeparator();

writer.WritePropertyName("pivot");
formatterResolver.GetFormatter<Union<Time, Distance>>().Serialize(ref writer, value.Pivot, formatterResolver);

writer.WriteValueSeparator();

if (value.Boost.HasValue)
{
writer.WritePropertyName("boost");
writer.WriteDouble(value.Boost.Value);
}

writer.WriteEndObject();
}

private static readonly AutomataDictionary Fields = new AutomataDictionary
{
{ "field", 0 },
{ "origin", 1 },
{ "pivot", 2 },
{ "boost", 3 }
};

public IDistanceFeatureQuery Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
if (reader.ReadIsNull())
return null;

var query = new DistanceFeatureQuery();
var count = 0;
while (reader.ReadIsInObject(ref count))
{
if (Fields.TryGetValue(reader.ReadPropertyNameSegmentRaw(), out var value))
{
switch (value)
{
case 0:
query.Field = formatterResolver.GetFormatter<Field>().Deserialize(ref reader, formatterResolver);
break;
case 1:
query.Origin = formatterResolver.GetFormatter<Union<DateMath, GeoLocation>>().Deserialize(ref reader, formatterResolver);
break;
case 2:
query.Pivot = formatterResolver.GetFormatter<Union<Time, Distance>>().Deserialize(ref reader, formatterResolver);
break;
case 3:
query.Boost = reader.ReadDouble();
break;
}
}
else
reader.ReadNextBlock();
}

return query;
}
}
}
2 changes: 2 additions & 0 deletions src/Nest/QueryDsl/Visitor/DslPrettyPrintVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public virtual void Visit(IQuery query) { }

public virtual void Visit(IDisMaxQuery query) => Write("dis_max");

public virtual void Visit(IDistanceFeatureQuery query) => Write("distance_feature");

public virtual void Visit(ISpanContainingQuery query) => Write("span_containing");

public virtual void Visit(ISpanWithinQuery query) => Write("span_within");
Expand Down
4 changes: 4 additions & 0 deletions src/Nest/QueryDsl/Visitor/QueryVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public interface IQueryVisitor

void Visit(IDisMaxQuery query);

void Visit(IDistanceFeatureQuery query);

void Visit(IFunctionScoreQuery query);

void Visit(IFuzzyQuery query);
Expand Down Expand Up @@ -159,6 +161,8 @@ public virtual void Visit(IConstantScoreQuery query) { }

public virtual void Visit(IDisMaxQuery query) { }

public virtual void Visit(IDistanceFeatureQuery query) { }

public virtual void Visit(ISpanContainingQuery query) { }

public virtual void Visit(ISpanWithinQuery query) { }
Expand Down
4 changes: 4 additions & 0 deletions src/Nest/QueryDsl/Visitor/QueryWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public void Walk(IQueryContainer qd, IQueryVisitor visitor)
v.Visit(d);
Accept(v, d.Queries);
});
VisitQuery(qd.DistanceFeature, visitor, (v, d) =>
{
v.Visit(d);
});
VisitQuery(qd.FunctionScore, visitor, (v, d) =>
{
v.Visit(d);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Elastic.Xunit.XunitPlumbing;
using Nest;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Domain;
using Tests.Framework.EndpointTests.TestState;

namespace Tests.QueryDsl.Specialized.DistanceFeature
{
/**
* Boosts the relevance score of documents closer to a provided origin date or point. For example, you can use this query to give
* more weight to documents closer to a certain date or location.
* You can use the distance_feature query to find the nearest neighbors to a location. You can also use the query in a bool
* search’s should filter to add boosted relevance scores to the bool query’s scores.
*/
[SkipVersion("<7.2.0", "Implemented in version 7.2.0")]
public class DistanceFeatureQueryUsageTests : QueryDslUsageTestsBase
codebrain marked this conversation as resolved.
Show resolved Hide resolved
{
public DistanceFeatureQueryUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }

protected override ConditionlessWhen ConditionlessWhen => new ConditionlessWhen<IDistanceFeatureQuery>(a => a.DistanceFeature)
{
q =>
{
q.Field = null;
q.Origin = null;
q.Pivot = null;
}
};

protected override QueryContainer QueryInitializer => new DistanceFeatureQuery()
{
Boost = 1.1,
Field = Infer.Field<Project>(f => f.StartedOn),
Origin = DateMath.FromString("now"),
Pivot = new Time("7d")
};

protected override object QueryJson =>
new { distance_feature = new { boost = 1.1, field = "startedOn", origin = "now", pivot = "7d" } };

protected override QueryContainer QueryFluent(QueryContainerDescriptor<Project> q) => q
.DistanceFeature(rf => rf
.Boost(1.1)
.Field(f => f.StartedOn)
.Origin(DateMath.FromString("now"))
.Pivot(new Time("7d"))
);
}
}