diff --git a/src/Nest.Tests.Unit/Nest.Tests.Unit.csproj b/src/Nest.Tests.Unit/Nest.Tests.Unit.csproj index 9e2c9899e43..8f99619c5e5 100644 --- a/src/Nest.Tests.Unit/Nest.Tests.Unit.csproj +++ b/src/Nest.Tests.Unit/Nest.Tests.Unit.csproj @@ -108,6 +108,7 @@ + diff --git a/src/Nest.Tests.Unit/Search/Query/Singles/FunctionScoreQueryJson.cs b/src/Nest.Tests.Unit/Search/Query/Singles/FunctionScoreQueryJson.cs new file mode 100644 index 00000000000..b377c6491ff --- /dev/null +++ b/src/Nest.Tests.Unit/Search/Query/Singles/FunctionScoreQueryJson.cs @@ -0,0 +1,47 @@ +using NUnit.Framework; +using Nest.DSL.Query; +using Nest.Tests.MockData.Domain; + +namespace Nest.Tests.Unit.Search.Query.Singles +{ + [TestFixture] + public class FunctionScoreQueryTests + { + [Test] + public void FunctionScoreQuery() + { + var s = new SearchDescriptor().From(0).Size(10) + .Query(q => q + .FunctionScore(fs => fs + .Query(qq => qq.MatchAll()) + .Functions( + f => f.Gauss(x=>x.StartedOn, d=>d.Scale("42w")), + f => f.Linear(x => x.FloatValue, d => d.Scale("0.3")), + f => f.Exp(x => x.DoubleValue, d => d.Scale("0.5")), + f => f.BoostFactor(2) + ) + .ScoreMode(FunctionScoreMode.sum) + ) + ).Fields(x=>x.Content); + + var json = TestElasticClient.Serialize(s); + var expected = @"{ + from: 0, size: 10, + query : { + function_score : { + functions: [ + {gauss: { startedOn : { scale: '42w'}}}, + {linear: { floatValue : { scale: '0.3'}}}, + {exp: { doubleValue: { scale: '0.5'}}}, + {boost_factor: 2.0 } + ], + query : { match_all : {} }, + score_mode: 'sum' + } + }, + fields: [""content""] + }"; + Assert.True(json.JsonEquals(expected), json); + } + } +} \ No newline at end of file diff --git a/src/Nest/DSL/Query.cs b/src/Nest/DSL/Query.cs index 94b96775075..95c99baa124 100644 --- a/src/Nest/DSL/Query.cs +++ b/src/Nest/DSL/Query.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Nest.DSL.Query; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Nest.Resolvers.Converters; @@ -237,6 +238,11 @@ public static BaseQuery Wildcard(string field, string value, double? Boost = nul { return new QueryDescriptor().Wildcard(field, value, Boost); } + + public static BaseQuery FunctionScore(Action> functionScoreQuery) + { + return new QueryDescriptor().FunctionScore(functionScoreQuery); + } } diff --git a/src/Nest/DSL/Query/FunctionScoreQueryDescriptor.cs b/src/Nest/DSL/Query/FunctionScoreQueryDescriptor.cs new file mode 100644 index 00000000000..9e5170315bd --- /dev/null +++ b/src/Nest/DSL/Query/FunctionScoreQueryDescriptor.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Nest.Resolvers; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Nest.DSL.Query +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class FunctionScoreQueryDescriptor : IQuery where T : class + { + [JsonProperty(PropertyName = "functions")] + internal IEnumerable> _Functions { get; set; } + + [JsonProperty(PropertyName = "query")] + internal BaseQuery _Query { get; set; } + + [JsonProperty(PropertyName = "score_mode")] + [JsonConverter(typeof(StringEnumConverter))] + FunctionScoreMode _ScoreMode { get; set; } + + + internal bool IsConditionless + { + get + { + return this._Query == null || this._Query.IsConditionless; + } + } + + public FunctionScoreQueryDescriptor Query(Func, BaseQuery> querySelector) + { + querySelector.ThrowIfNull("querySelector"); + var query = new QueryDescriptor(); + var q = querySelector(query); + + this._Query = q; + return this; + } + + public FunctionScoreQueryDescriptor Functions(params Func, FunctionScoreFunction>[] functions) + { + var descriptor = new FunctionScoreFunctionsDescriptor(); + + foreach (var f in functions) + { + f(descriptor); + } + + _Functions = descriptor; + + return this; + } + + public FunctionScoreQueryDescriptor ScoreMode(FunctionScoreMode mode) + { + this._ScoreMode = mode; + return this; + } + } + + public enum FunctionScoreMode + { + multiply, + sum, + avg, + first, + max, + min + } + + public class FunctionScoreFunctionsDescriptor : IEnumerable> + { + internal List> _Functions { get; set; } + + public FunctionScoreFunctionsDescriptor() + { + this._Functions = new List>(); + } + + public FunctionScoreFunction Gauss(Expression> objectPath, Action db) + { + var fn = new GaussFunction(objectPath, db); + this._Functions.Add(fn); + return fn; + } + + public FunctionScoreFunction Linear(Expression> objectPath, Action db) + { + var fn = new LinearFunction(objectPath, db); + this._Functions.Add(fn); + return fn; + } + + public FunctionScoreFunction Exp(Expression> objectPath, Action db) + { + var fn = new ExpFunction(objectPath, db); + this._Functions.Add(fn); + return fn; + } + + public BoostFactorFunction BoostFactor(double value) + { + var fn = new BoostFactorFunction(value); + this._Functions.Add(fn); + return fn; + } + + + public IEnumerator> GetEnumerator() + { + return _Functions.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _Functions.GetEnumerator(); + } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class FunctionScoreFunction + { + } + + public class FunctionScoreDecayFieldDescriptor + { + [JsonProperty(PropertyName = "origin")] + internal string _Origin { get; set; } + + [JsonProperty(PropertyName = "scale")] + internal string _Scale { get; set; } + + [JsonProperty(PropertyName = "offset")] + internal string _Offset { get; set; } + + [JsonProperty(PropertyName = "decay")] + internal double? _Decay { get; set; } + + public FunctionScoreDecayFieldDescriptor Origin(string origin) + { + this._Origin = origin; + return this; + } + + public FunctionScoreDecayFieldDescriptor Scale(string scale) + { + this._Scale = scale; + return this; + } + + public FunctionScoreDecayFieldDescriptor Offset(string offset) + { + this._Offset = offset; + return this; + } + + public FunctionScoreDecayFieldDescriptor Decay(double? decay) + { + this._Decay = decay; + return this; + } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class FunctionScoreDecayFunction : FunctionScoreFunction + { + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class GaussFunction : FunctionScoreDecayFunction + { + [JsonProperty(PropertyName = "gauss")] + [JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))] + internal IDictionary _GaussDescriptor { get; set; } + + public GaussFunction(Expression> objectPath, Action descriptorBuilder) + { + _GaussDescriptor = new Dictionary(); + + var resolver = new PropertyNameResolver(); + var fieldName = resolver.Resolve(objectPath); + var descriptor = new FunctionScoreDecayFieldDescriptor(); + descriptorBuilder(descriptor); + _GaussDescriptor[fieldName] = descriptor; + } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class LinearFunction : FunctionScoreDecayFunction + { + [JsonProperty(PropertyName = "linear")] + [JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))] + internal IDictionary _LinearDescriptor { get; set; } + + public LinearFunction(Expression> objectPath, Action descriptorBuilder) + { + _LinearDescriptor = new Dictionary(); + + var resolver = new PropertyNameResolver(); + var fieldName = resolver.Resolve(objectPath); + var descriptor = new FunctionScoreDecayFieldDescriptor(); + descriptorBuilder(descriptor); + _LinearDescriptor[fieldName] = descriptor; + } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class ExpFunction : FunctionScoreDecayFunction + { + [JsonProperty(PropertyName = "exp")] + [JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))] + internal IDictionary _ExpDescriptor { get; set; } + + public ExpFunction(Expression> objectPath, Action descriptorBuilder) + { + _ExpDescriptor = new Dictionary(); + + var resolver = new PropertyNameResolver(); + var fieldName = resolver.Resolve(objectPath); + var descriptor = new FunctionScoreDecayFieldDescriptor(); + descriptorBuilder(descriptor); + _ExpDescriptor[fieldName] = descriptor; + } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class BoostFactorFunction : FunctionScoreFunction + { + [JsonProperty(PropertyName = "boost_factor")] + internal double _BoostFactor { get; set; } + + public BoostFactorFunction(double boostFactor) + { + _BoostFactor = boostFactor; + } + } +} diff --git a/src/Nest/DSL/QueryDescriptor.cs b/src/Nest/DSL/QueryDescriptor.cs index 4165e46542c..96a9128527d 100644 --- a/src/Nest/DSL/QueryDescriptor.cs +++ b/src/Nest/DSL/QueryDescriptor.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Nest.DSL.Query; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Nest.Resolvers.Converters; @@ -35,6 +36,9 @@ public QueryDescriptor() internal BoostingQueryDescriptor BoostingQueryDescriptor { get; set; } [JsonProperty(PropertyName = "ids")] internal IdsQuery IdsQuery { get; set; } + + [JsonProperty(PropertyName = "function_score")] + internal FunctionScoreQueryDescriptor FunctionScoreQueryDescriptor { get; set; } [JsonProperty(PropertyName = "custom_score")] internal CustomScoreQueryDescriptor CustomScoreQueryDescriptor { get; set; } [JsonProperty(PropertyName = "custom_filters_score")] @@ -128,6 +132,7 @@ internal QueryDescriptor Clone() BoostingQueryDescriptor = BoostingQueryDescriptor, IdsQuery = IdsQuery, + FunctionScoreQueryDescriptor = FunctionScoreQueryDescriptor, CustomScoreQueryDescriptor = CustomScoreQueryDescriptor, CustomBoostFactorQueryDescriptor = CustomBoostFactorQueryDescriptor, ConstantScoreQueryDescriptor = ConstantScoreQueryDescriptor, @@ -939,5 +944,20 @@ public BaseQuery SpanNot(Action> selector) return new QueryDescriptor { SpanNotQueryDescriptor = this.SpanNotQueryDescriptor }; } - } + /// + /// Function score query + /// + /// + public BaseQuery FunctionScore(Action> functionScoreQuery) + { + var query = new FunctionScoreQueryDescriptor(); + functionScoreQuery(query); + + if (query.IsConditionless) + return CreateConditionlessQueryDescriptor(query); + + this.FunctionScoreQueryDescriptor = query; + return new QueryDescriptor { FunctionScoreQueryDescriptor = this.FunctionScoreQueryDescriptor }; + } + } } diff --git a/src/Nest/Nest.csproj b/src/Nest/Nest.csproj index b0b1fc518a8..402847edfe3 100644 --- a/src/Nest/Nest.csproj +++ b/src/Nest/Nest.csproj @@ -70,6 +70,7 @@ +