From ec4beac710f2e5d82ddf502135d2c95f38f5f747 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Fri, 23 Apr 2021 15:25:17 +0100 Subject: [PATCH] Support for calculated numeric fields (#5622) --- .../CommonOptions/Scripting/OnScriptError.cs | 18 +++++++ .../Types/Core/Number/NumberAttribute.cs | 16 +++++- .../Types/Core/Number/NumberProperty.cs | 38 +++++++++++++- .../Types/Core/Number/NumberPropertyTests.cs | 52 +++++++++++++++++++ 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/Nest/CommonOptions/Scripting/OnScriptError.cs diff --git a/src/Nest/CommonOptions/Scripting/OnScriptError.cs b/src/Nest/CommonOptions/Scripting/OnScriptError.cs new file mode 100644 index 00000000000..f2eac950d9c --- /dev/null +++ b/src/Nest/CommonOptions/Scripting/OnScriptError.cs @@ -0,0 +1,18 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Runtime.Serialization; +using Elasticsearch.Net; + +namespace Nest +{ + [StringEnum] + public enum OnScriptError + { + [EnumMember(Value = "fail")] + Fail, + [EnumMember(Value = "continue")] + Continue + } +} diff --git a/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs b/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs index b1e021e96ca..56d24d34f4e 100644 --- a/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs +++ b/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs @@ -52,14 +52,28 @@ public double ScalingFactor set => Self.ScalingFactor = value; } + public IInlineScript Script + { + get => Self.Script; + set => Self.Script = value; + } + + public OnScriptError OnScriptError + { + get => Self.OnScriptError.GetValueOrDefault(); + set => Self.OnScriptError = value; + } + double? INumberProperty.Boost { get; set; } bool? INumberProperty.Coerce { get; set; } INumericFielddata INumberProperty.Fielddata { get; set; } bool? INumberProperty.IgnoreMalformed { get; set; } - bool? INumberProperty.Index { get; set; } double? INumberProperty.NullValue { get; set; } double? INumberProperty.ScalingFactor { get; set; } + IInlineScript INumberProperty.Script { get; set; } + OnScriptError? INumberProperty.OnScriptError { get; set; } + private INumberProperty Self => this; } } diff --git a/src/Nest/Mapping/Types/Core/Number/NumberProperty.cs b/src/Nest/Mapping/Types/Core/Number/NumberProperty.cs index ec8d0958d5f..5368b949106 100644 --- a/src/Nest/Mapping/Types/Core/Number/NumberProperty.cs +++ b/src/Nest/Mapping/Types/Core/Number/NumberProperty.cs @@ -37,6 +37,22 @@ public interface INumberProperty : IDocValuesProperty [DataMember(Name = "scaling_factor")] double? ScalingFactor { get; set; } + + /// + /// If this parameter is set, then the field will index values generated by this script, rather than reading the values directly from the source. + /// If a value is set for this field on the input document, then the document will be rejected with an error. + /// Scripts are in the same format as their runtime equivalent. Scripts can only be configured on long and double field types. + /// + [DataMember(Name = "script")] + IInlineScript Script { get; set; } + + /// + /// Defines what to do if the script defined by the `script` parameter throws an error at indexing time.Accepts `reject` (default), which + /// will cause the entire document to be rejected, and `ignore`, which will register the field in the document's ignored metadata field and + /// continue indexing.This parameter can only be set if the `script` field is also set. + /// + [DataMember(Name = "on_script_error")] + OnScriptError? OnScriptError { get; set; } } [DebuggerDisplay("{DebugDisplay}")] @@ -50,10 +66,15 @@ public NumberProperty(NumberType type) : base(type.ToFieldType()) { } public bool? Coerce { get; set; } public INumericFielddata Fielddata { get; set; } public bool? IgnoreMalformed { get; set; } - public bool? Index { get; set; } public double? NullValue { get; set; } public double? ScalingFactor { get; set; } + + /// + public IInlineScript Script { get; set; } + + /// + public OnScriptError? OnScriptError { get; set; } } [DebuggerDisplay("{DebugDisplay}")] @@ -71,10 +92,11 @@ protected NumberPropertyDescriptorBase(FieldType type) : base(type) { } bool? INumberProperty.Coerce { get; set; } INumericFielddata INumberProperty.Fielddata { get; set; } bool? INumberProperty.IgnoreMalformed { get; set; } - bool? INumberProperty.Index { get; set; } double? INumberProperty.NullValue { get; set; } double? INumberProperty.ScalingFactor { get; set; } + IInlineScript INumberProperty.Script { get; set; } + OnScriptError? INumberProperty.OnScriptError { get; set; } public TDescriptor Type(NumberType? type) => Assign(type?.GetStringValue(), (a, v) => a.Type = v); @@ -93,6 +115,18 @@ public TDescriptor Fielddata(Func Assign(selector(new NumericFielddataDescriptor()), (a, v) => a.Fielddata = v); public TDescriptor ScalingFactor(double? scalingFactor) => Assign(scalingFactor, (a, v) => a.ScalingFactor = v); + + /// + public TDescriptor Script(IInlineScript inlineScript) => Assign(inlineScript, (a, v) => a.Script = v); + + /// + public TDescriptor Script(string source) => Assign(source, (a, v) => a.Script = new InlineScript(source)); + + /// + public TDescriptor Script(Func selector) => Assign(selector, (a, v) => a.Script = v?.Invoke(new InlineScriptDescriptor())); + + /// + public TDescriptor OnScriptError(OnScriptError? onScriptError) => Assign(onScriptError, (a, v) => a.OnScriptError = v); } public class NumberPropertyDescriptor : NumberPropertyDescriptorBase, INumberProperty, T> diff --git a/tests/Tests/Mapping/Types/Core/Number/NumberPropertyTests.cs b/tests/Tests/Mapping/Types/Core/Number/NumberPropertyTests.cs index 9fd71acc8e6..7d98e5e479c 100644 --- a/tests/Tests/Mapping/Types/Core/Number/NumberPropertyTests.cs +++ b/tests/Tests/Mapping/Types/Core/Number/NumberPropertyTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System; +using System.Collections.Generic; using Elastic.Elasticsearch.Xunit.XunitPlumbing; using Nest; using Tests.Core.ManagedElasticsearch.Clusters; @@ -166,4 +167,55 @@ public UnsignedLongNumberPropertyTests(WritableCluster cluster, EndpointUsage us } }; } + + [SkipVersion("<7.13.0", "Script support added in 7.13.0")] + public class ScriptedNumberPropertyTests : PropertyTestsBase + { + public ScriptedNumberPropertyTests(WritableCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override object ExpectJson => new + { + properties = new + { + doublesCommits = new + { + type = "long", + script = new + { + source = "emit((long)(doc['numberOfCommits'].value * params.multiplier))", + @params = new Dictionary + { + {"multiplier", 2 } + } + }, + on_script_error = "continue" + } + } + }; + + protected override Func, IPromise> FluentProperties => f => f + .Number(n => n + .Name("doublesCommits") + .Type(NumberType.Long) + .Script(s => s.Source("emit((long)(doc['numberOfCommits'].value * params.multiplier))").Params(p => p.Add("multiplier", 2))) + .OnScriptError(OnScriptError.Continue) + ); + + protected override IProperties InitializerProperties => new Properties + { + { + "doublesCommits", new NumberProperty(NumberType.Long) + { + Script = new InlineScript("emit((long)(doc['numberOfCommits'].value * params.multiplier))") + { + Params = new Dictionary + { + { "multiplier", 2 } + } + }, + OnScriptError = OnScriptError.Continue + } + } + }; + } }