diff --git a/src/Nest/DSL/Aggregations/AggregationDescriptor.cs b/src/Nest/DSL/Aggregations/AggregationDescriptor.cs index a95047426b5..97f615e0263 100644 --- a/src/Nest/DSL/Aggregations/AggregationDescriptor.cs +++ b/src/Nest/DSL/Aggregations/AggregationDescriptor.cs @@ -90,6 +90,9 @@ public interface IAggregationContainer [JsonProperty("top_hits")] ITopHitsAggregator TopHits { get; set; } + [JsonProperty("scripted_metric")] + IScriptedMetricAggregator ScriptedMetric { get; set; } + [JsonProperty("aggs")] [JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))] IDictionary Aggregations { get; set; } @@ -117,8 +120,9 @@ public class AggregationContainer : IAggregationContainer private ISignificantTermsAggregator _significantTerms; private IPercentileRanksAggregaor _percentileRanks; private IFiltersAggregator _filters; - private ITopHitsAggregator _topHits; + private IScriptedMetricAggregator _scriptedMetric; + public IAverageAggregator Average { get; set; } public IValueCountAggregator ValueCount { get; set; } public IMaxAggregator Max { get; set; } @@ -246,6 +250,12 @@ public ITopHitsAggregator TopHits set { _topHits = value; } } + public IScriptedMetricAggregator ScriptedMetric + { + get { return _scriptedMetric; } + set { _scriptedMetric = value; } + } + private void LiftAggregations(IBucketAggregator bucket) { if (bucket == null) return; @@ -314,6 +324,8 @@ public class AggregationDescriptor : IAggregationContainer ITopHitsAggregator IAggregationContainer.TopHits { get; set; } + IScriptedMetricAggregator IAggregationContainer.ScriptedMetric { get; set; } + public AggregationDescriptor Average(string name, Func, AverageAggregationDescriptor> selector) { return _SetInnerAggregation(name, selector, (a, d) => a.Average = d); @@ -464,6 +476,12 @@ public AggregationDescriptor TopHits(string name, return _SetInnerAggregation(name, selector, (a, d) => a.TopHits = d); } + public AggregationDescriptor ScriptedMetric(string name, + Func, ScriptedMetricAggregationDescriptor> selector) + { + return _SetInnerAggregation(name, selector, (a, d) => a.ScriptedMetric = d); + } + private AggregationDescriptor _SetInnerAggregation( string key, Func selector diff --git a/src/Nest/DSL/Aggregations/ScriptedMetricAggregationDescriptor.cs b/src/Nest/DSL/Aggregations/ScriptedMetricAggregationDescriptor.cs new file mode 100644 index 00000000000..a35f3eff220 --- /dev/null +++ b/src/Nest/DSL/Aggregations/ScriptedMetricAggregationDescriptor.cs @@ -0,0 +1,169 @@ +using Nest.Resolvers.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Nest +{ + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + [JsonConverter(typeof(ReadAsTypeConverter))] + public interface IScriptedMetricAggregator + { + [JsonProperty("init_script")] + string InitScript { get; set; } + + [JsonProperty("init_script_file")] + string InitScriptFile { get; set; } + + [JsonProperty("init_script_id")] + string InitScriptId { get; set; } + + [JsonProperty("map_script")] + string MapScript { get; set; } + + [JsonProperty("map_script_file")] + string MapScriptFile { get; set; } + + [JsonProperty("map_script_id")] + string MapScriptId { get; set; } + + [JsonProperty("combine_script")] + string CombineScript { get; set; } + + [JsonProperty("combine_script_file")] + string CombineScriptFile { get; set; } + + [JsonProperty("combine_script_id")] + string CombineScriptId { get; set; } + + [JsonProperty("reduce_script")] + string ReduceScript { get; set; } + + [JsonProperty("reduce_script_file")] + string ReduceScriptFile { get; set; } + + [JsonProperty("reduce_script_id")] + string ReduceScriptId { get; set; } + + [JsonProperty("reduce_params")] + IDictionary ReduceParams { get; set; } + } + + public class ScriptedMetricsAggregator : MetricAggregator, IScriptedMetricAggregator + { + public string InitScript { get; set; } + public string InitScriptFile { get; set; } + public string InitScriptId { get; set; } + public string MapScript { get; set; } + public string MapScriptFile { get; set; } + public string MapScriptId { get; set; } + public string CombineScript { get; set; } + public string CombineScriptFile { get; set; } + public string CombineScriptId { get; set; } + public string ReduceScript { get; set; } + public string ReduceScriptFile { get; set; } + public string ReduceScriptId { get; set; } + public IDictionary ReduceParams { get; set; } + } + + public class ScriptedMetricAggregationDescriptor + : MetricAggregationBaseDescriptor, T>, IScriptedMetricAggregator + where T : class + { + IScriptedMetricAggregator Self { get { return this; } } + + string IScriptedMetricAggregator.InitScript { get; set; } + string IScriptedMetricAggregator.InitScriptFile { get; set; } + string IScriptedMetricAggregator.InitScriptId { get; set; } + string IScriptedMetricAggregator.MapScript { get; set; } + string IScriptedMetricAggregator.MapScriptFile { get; set; } + string IScriptedMetricAggregator.MapScriptId { get; set; } + string IScriptedMetricAggregator.CombineScript { get; set; } + string IScriptedMetricAggregator.CombineScriptFile { get; set; } + string IScriptedMetricAggregator.CombineScriptId { get; set; } + string IScriptedMetricAggregator.ReduceScript { get; set; } + string IScriptedMetricAggregator.ReduceScriptFile { get; set; } + string IScriptedMetricAggregator.ReduceScriptId { get; set; } + IDictionary IScriptedMetricAggregator.ReduceParams { get; set; } + + public ScriptedMetricAggregationDescriptor InitScript(string script) + { + this.Self.InitScript = script; + return this; + } + + public ScriptedMetricAggregationDescriptor InitScriptFile(string file) + { + this.Self.InitScriptFile = file; + return this; + } + + public ScriptedMetricAggregationDescriptor InitScriptId(string id) + { + this.Self.InitScriptId = id; + return this; + } + + public ScriptedMetricAggregationDescriptor MapScript(string script) + { + this.Self.MapScript = script; + return this; + } + + public ScriptedMetricAggregationDescriptor MapScriptFile(string file) + { + this.Self.MapScriptFile = file; + return this; + } + + public ScriptedMetricAggregationDescriptor MapScriptId(string id) + { + this.Self.MapScriptId = id; + return this; + } + + public ScriptedMetricAggregationDescriptor CombineScript(string script) + { + this.Self.CombineScript = script; + return this; + } + + public ScriptedMetricAggregationDescriptor CombineScriptFile(string file) + { + this.Self.CombineScriptFile = file; + return this; + } + + public ScriptedMetricAggregationDescriptor CombineScriptId(string id) + { + this.Self.CombineScriptId = id; + return this; + } + + public ScriptedMetricAggregationDescriptor ReduceScript(string script) + { + this.Self.ReduceScript = script; + return this; + } + + public ScriptedMetricAggregationDescriptor ReduceScriptFile(string file) + { + this.Self.ReduceScriptFile = file; + return this; + } + + public ScriptedMetricAggregationDescriptor ReduceScriptId(string id) + { + this.Self.ReduceScriptId = id; + return this; + } + + public ScriptedMetricAggregationDescriptor ReduceParams(Func, FluentDictionary> paramSelector) + { + this.Self.ReduceParams = paramSelector(new FluentDictionary()); + return this; + } + } +} diff --git a/src/Nest/Domain/Aggregations/AggregationsHelper.cs b/src/Nest/Domain/Aggregations/AggregationsHelper.cs index 4e40f1e33f7..e10c64ccadc 100644 --- a/src/Nest/Domain/Aggregations/AggregationsHelper.cs +++ b/src/Nest/Domain/Aggregations/AggregationsHelper.cs @@ -55,6 +55,18 @@ public ValueMetric ValueCount(string key) return this.TryGet(key); } + public ScriptedValueMetric ScriptedMetric(string key) + { + var valueMetric = this.TryGet(key); + + if (valueMetric != null) + { + return new ScriptedValueMetric { _Value = valueMetric.Value }; + } + + return this.TryGet(key); + } + public StatsMetric Stats(string key) { return this.TryGet(key); diff --git a/src/Nest/Domain/Aggregations/ScriptedValueMetric.cs b/src/Nest/Domain/Aggregations/ScriptedValueMetric.cs new file mode 100644 index 00000000000..38154316a13 --- /dev/null +++ b/src/Nest/Domain/Aggregations/ScriptedValueMetric.cs @@ -0,0 +1,28 @@ + +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Nest +{ + public class ScriptedValueMetric : IMetricAggregation + { + internal object _Value { get; set; } + + /// + /// Get the resut of the scripted metric aggregation as T + /// + /// The type that best represents the result of your scripted metric aggrgation + public T Value() + { + var jToken = this._Value as JToken; + + if (jToken != null) + return jToken.ToObject(); + + return (T)this._Value; + } + } +} diff --git a/src/Nest/Nest.csproj b/src/Nest/Nest.csproj index 126db80cfde..9be3a93c1c0 100644 --- a/src/Nest/Nest.csproj +++ b/src/Nest/Nest.csproj @@ -109,6 +109,7 @@ + @@ -233,6 +234,7 @@ + diff --git a/src/Nest/Resolvers/Converters/Aggregations/AggregationConverter.cs b/src/Nest/Resolvers/Converters/Aggregations/AggregationConverter.cs index 8b7f18a8fc6..0ce294feacd 100644 --- a/src/Nest/Resolvers/Converters/Aggregations/AggregationConverter.cs +++ b/src/Nest/Resolvers/Converters/Aggregations/AggregationConverter.cs @@ -351,14 +351,25 @@ private IAggregation GetBucketAggregation(JsonReader reader, JsonSerializer seri private IAggregation GetValueMetricOrAggregation(JsonReader reader, JsonSerializer serializer) { reader.Read(); - var metric = new ValueMetric() + var valueMetric = new ValueMetric() { Value = (reader.Value as double?) }; - if (metric.Value == null && reader.ValueType == typeof(long)) - metric.Value = reader.Value as long?; - reader.Read(); - return metric; + if (valueMetric.Value == null && reader.ValueType == typeof(long)) + valueMetric.Value = reader.Value as long?; + + if (valueMetric.Value != null) + { + reader.Read(); + return valueMetric; + } + + var scriptedMetric = serializer.Deserialize(reader); + + if (scriptedMetric != null) + return new ScriptedValueMetric { _Value = scriptedMetric }; + + return valueMetric; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) diff --git a/src/Tests/Nest.Tests.Integration/Aggregations/MetricAggregationTests.cs b/src/Tests/Nest.Tests.Integration/Aggregations/MetricAggregationTests.cs index 72f063d3f00..c66f7f49481 100644 --- a/src/Tests/Nest.Tests.Integration/Aggregations/MetricAggregationTests.cs +++ b/src/Tests/Nest.Tests.Integration/Aggregations/MetricAggregationTests.cs @@ -3,6 +3,7 @@ using FluentAssertions; using Nest.Tests.MockData.Domain; using NUnit.Framework; +using System.Collections.Generic; namespace Nest.Tests.Integration.Aggregations { @@ -203,9 +204,111 @@ public void TopHits() var hits = topHits.Hits(); hits.Should().NotBeEmpty().And.NotContain(h=> h.Id.IsNullOrEmpty() || h.Index.IsNullOrEmpty()); topHits.Documents().Should().NotBeEmpty(); - } + } + + [Test] + public void ScriptedMetric_SingleNumericValue() + { + var results = this.Client.Search(s => s + .Size(0) + .Aggregations(a => a + .ScriptedMetric("project_count", sm => sm + .InitScript("_agg['count'] = []") + .MapScript("_agg.count.add(1)") + .CombineScript("total = 0; for (c in _agg.count) { total += c }; return total") + .ReduceScript("total = 0; for (a in _aggs) { total += a }; return total") + ) + ) + ); + + results.IsValid.Should().BeTrue(); + var count = results.Aggs.ScriptedMetric("project_count"); + var value = count.Value(); + value.Should().BeGreaterThan(0); + } + + [Test] + public void ScriptedMetric_MultiNumericValue() + { + var results = this.Client.Search(s => s + .Size(0) + .Aggregations(a => a + .ScriptedMetric("project_count_per_shard", sm => sm + .InitScript("_agg['count'] = []") + .MapScript("_agg.count.add(1)") + .CombineScript("total = 0; for (c in _agg.count) { total += c }; return total") + .ReduceScript("return _aggs") + ) + ) + ); + + results.IsValid.Should().BeTrue(); + var countsPerShard = results.Aggs.ScriptedMetric("project_count_per_shard"); + var value = countsPerShard.Value>(); + value.Should().NotBeNull(); + value.Count().Should().BeGreaterThan(0); + } + + [Test] + public void ScriptedMetric_SingleStringValue() + { + var results = this.Client.Search(s => s + .Size(0) + .Aggregations(a => a + .ScriptedMetric("first_name", sm => sm + .InitScript("_agg['names'] = []") + .MapScript("_agg.names.add(doc['name'].value)") + .ReduceScript("return _aggs['names'][0][0]") + ) + ) + ); + + results.IsValid.Should().BeTrue(); + var firstName = results.Aggs.ScriptedMetric("first_name"); + var value = firstName.Value(); + value.Should().NotBeNullOrEmpty(); + } + + [Test] + public void ScriptedMetric_MultiStringValue() + { + var results = this.Client.Search(s => s + .Size(0) + .Aggregations(a => a + .ScriptedMetric("names_on_first_shard", sm => sm + .InitScript("_agg['names'] = []") + .MapScript("_agg.names.add(doc['name'].value)") + .ReduceScript("return _aggs['names'][0]") + ) + ) + ); + + results.IsValid.Should().BeTrue(); + var namesOnFirstShard = results.Aggs.ScriptedMetric("names_on_first_shard"); + var value = namesOnFirstShard.Value>(); + value.Should().NotBeNull(); + value.Count().Should().BeGreaterThan(0); + } + [Test] + public void ScriptedMetric_MultiArrayValue() + { + var results = this.Client.Search(s => s + .Size(0) + .Aggregations(a => a + .ScriptedMetric("names_per_shard", sm => sm + .InitScript("_agg['names'] = []") + .MapScript("_agg.names.add(doc['name'].value)") + .ReduceScript("return _aggs['names']") + ) + ) + ); + + results.IsValid.Should().BeTrue(); + var namesPerShard = results.Aggs.ScriptedMetric("names_per_shard"); + var value = namesPerShard.Value>>(); + value.Should().NotBeNull(); } } } \ No newline at end of file diff --git a/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj b/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj index fcf56d8c53f..e1c70f9a392 100644 --- a/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj +++ b/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj @@ -404,6 +404,7 @@ + diff --git a/src/Tests/Nest.Tests.Unit/Search/Aggregations/ScriptedMetricJson.cs b/src/Tests/Nest.Tests.Unit/Search/Aggregations/ScriptedMetricJson.cs new file mode 100644 index 00000000000..31362dee32e --- /dev/null +++ b/src/Tests/Nest.Tests.Unit/Search/Aggregations/ScriptedMetricJson.cs @@ -0,0 +1,60 @@ + +using Nest.Tests.MockData.Domain; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nest.Tests.Unit.Search.Aggregations +{ + [TestFixture] + public class ScriptedMetricJson + { + [Test] + public void ScriptedMetric() + { + var s = new SearchDescriptor() + .From(0) + .Size(10) + .Aggregations(a => a + .ScriptedMetric("profit", sm => sm + .Language("groovy") + .InitScript("init script") + .MapScript("map script") + .CombineScript("combine script") + .ReduceScript("reduce script") + .ReduceParams(rp => rp + .Add("param1", "value1") + .Add("param2", "value2") + ) + ) + ); + + var json = TestElasticClient.Serialize(s); + var expected = @" + { + from: 0, + size: 10, + aggs : { + ""profit"" : { + scripted_metric : { + lang: ""groovy"", + init_script : ""init script"", + map_script : ""map script"", + combine_script: ""combine script"", + reduce_script: ""reduce script"", + reduce_params: { + param1: ""value1"", + param2: ""value2"" + } + } + } + } + }"; + + Assert.True(json.JsonEquals(expected), json); + } + } +}