Skip to content

Commit

Permalink
Implement distance_feature query (#3983)
Browse files Browse the repository at this point in the history
Implement distance_feature query

(cherry picked from commit e58db5b)
  • Loading branch information
codebrain authored and russcam committed Sep 3, 2019
1 parent b779cb3 commit 1420c82
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 0 deletions.
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 @@ -167,6 +167,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 @@ -13,6 +13,7 @@ public partial class QueryContainer : IQueryContainer, IDescriptor
#pragma warning restore 618
private IConstantScoreQuery _constantScore;
private IDisMaxQuery _disMax;
private IDistanceFeatureQuery _distanceFeature;
private IExistsQuery _exists;
private IFunctionScoreQuery _functionScore;
private IFuzzyQuery _fuzzy;
Expand Down Expand Up @@ -96,6 +97,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 @@ -216,6 +216,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) =>
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 @@ -27,6 +27,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
158 changes: 158 additions & 0 deletions src/Nest/QueryDsl/Specialized/DistanceFeature/DistanceFeatureQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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<GeoLocation, DateMath> 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<Distance, Time> Pivot { get; set; }
}

/// <inheritdoc cref="IDistanceFeatureQuery" />
public class DistanceFeatureQuery : FieldNameQueryBase, IDistanceFeatureQuery
{
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;

/// <inheritdoc />
public Union<GeoLocation, DateMath> Origin { get; set; }

/// <inheritdoc />
public Union<Distance, Time> Pivot { get; set; }
}

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

Union<Distance, Time> 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>
{
private static readonly UnionFormatter<GeoLocation, DateMath> OriginUnionFormatter = new UnionFormatter<GeoLocation, DateMath> ();
private static readonly UnionFormatter<Distance, Time> PivotUnionFormatter = new UnionFormatter<Distance, Time>();

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");
OriginUnionFormatter.Serialize(ref writer, value.Origin, formatterResolver);
writer.WriteValueSeparator();

writer.WritePropertyName("pivot");
PivotUnionFormatter.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 = OriginUnionFormatter.Deserialize(ref reader, formatterResolver);
break;
case 2:
query.Pivot = PivotUnionFormatter.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 @@ -57,6 +57,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 @@ -36,6 +36,8 @@ public interface IQueryVisitor

void Visit(IDisMaxQuery query);

void Visit(IDistanceFeatureQuery query);

void Visit(IFunctionScoreQuery query);

void Visit(IFuzzyQuery query);
Expand Down Expand Up @@ -163,6 +165,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,86 @@
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.
*/
[SkipVersion("<7.2.0", "Implemented in version 7.2.0")]
public class DistanceFeatureTimeQueryUsageTests : QueryDslUsageTestsBase
{
public DistanceFeatureTimeQueryUsageTests(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"))
);
}

/**
* 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 DistanceFeatureDistanceQueryUsageTests : QueryDslUsageTestsBase
{
public DistanceFeatureDistanceQueryUsageTests(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 = new GeoLocation(70, -70),
Pivot = new Distance(100, DistanceUnit.Miles)
};

protected override object QueryJson =>
new { distance_feature = new { boost = 1.1, field = "startedOn", origin = new { lat = 70.0, lon = -70.0 }, pivot = "100mi" } };

protected override QueryContainer QueryFluent(QueryContainerDescriptor<Project> q) => q
.DistanceFeature(rf => rf
.Boost(1.1)
.Field(f => f.StartedOn)
.Origin(new GeoLocation(70, -70))
.Pivot(new Distance(100, DistanceUnit.Miles))
);
}
}

0 comments on commit 1420c82

Please sign in to comment.