/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
using System.Runtime.Serialization;
using OpenSearch.Net.Utf8Json;
using OpenSearch.Net.Utf8Json.Internal;
namespace OpenSearch.Client
{
///
/// 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.
///
[JsonFormatter(typeof(DistanceFeatureQueryFormatter))]
[InterfaceDataContract]
public interface IDistanceFeatureQuery : IFieldNameQuery
{
///
/// 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.
///
[DataMember(Name = "origin")]
Union Origin { get; set; }
///
/// 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.
///
[DataMember(Name = "pivot")]
Union Pivot { get; set; }
}
///
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;
///
public Union Origin { get; set; }
///
public Union Pivot { get; set; }
}
public class DistanceFeatureQueryDescriptor
: FieldNameQueryDescriptorBase, IDistanceFeatureQuery, T>
, IDistanceFeatureQuery where T : class
{
Union IDistanceFeatureQuery.Origin { get; set; }
Union IDistanceFeatureQuery.Pivot { get; set; }
protected override bool Conditionless => DistanceFeatureQuery.IsConditionless(this);
///
public DistanceFeatureQueryDescriptor Origin(DateMath origin) =>
Assign(origin, (a, v) => a.Origin = v);
///
public DistanceFeatureQueryDescriptor Origin(GeoCoordinate origin) =>
Assign(origin, (a, v) => a.Origin = v);
///
public DistanceFeatureQueryDescriptor Pivot(Time pivot) =>
Assign(pivot, (a, v) => a.Pivot = v);
///
public DistanceFeatureQueryDescriptor Pivot(Distance pivot) =>
Assign(pivot, (a, v) => a.Pivot = v);
}
internal class DistanceFeatureQueryFormatter : IJsonFormatter
{
private static readonly UnionFormatter OriginUnionFormatter = new UnionFormatter(true);
private static readonly UnionFormatter PivotUnionFormatter = new UnionFormatter();
public void Serialize(ref JsonWriter writer, IDistanceFeatureQuery value, IJsonFormatterResolver formatterResolver)
{
if (value == null)
{
writer.WriteNull();
return;
}
writer.WriteBeginObject();
if (!string.IsNullOrEmpty(value.Name))
{
writer.WritePropertyName("_name");
writer.WriteString(value.Name);
writer.WriteValueSeparator();
}
if (value.Boost.HasValue)
{
writer.WritePropertyName("boost");
writer.WriteDouble(value.Boost.Value);
writer.WriteValueSeparator();
}
writer.WritePropertyName("field");
var fieldFormatter = formatterResolver.GetFormatter();
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.WriteEndObject();
}
private static readonly AutomataDictionary Fields = new AutomataDictionary
{
{ "field", 0 },
{ "origin", 1 },
{ "pivot", 2 },
{ "boost", 3 },
{ "_name", 4 }
};
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().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;
case 4:
query.Name = reader.ReadString();
break;
}
}
else
reader.ReadNextBlock();
}
return query;
}
}
}