diff --git a/src/Nest/CommonOptions/Scripting/InlineScript.cs b/src/Nest/CommonOptions/Scripting/InlineScript.cs index 8d5a9ce709d..571fc6475ef 100644 --- a/src/Nest/CommonOptions/Scripting/InlineScript.cs +++ b/src/Nest/CommonOptions/Scripting/InlineScript.cs @@ -8,6 +8,7 @@ namespace Nest { [InterfaceDataContract] + [ReadAs(typeof(InlineScript))] public interface IInlineScript : IScript { [DataMember(Name ="source")] 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/RuntimeFields/RuntimeField.cs b/src/Nest/Mapping/RuntimeFields/RuntimeField.cs index 863ee18ba24..f294955ea0f 100644 --- a/src/Nest/Mapping/RuntimeFields/RuntimeField.cs +++ b/src/Nest/Mapping/RuntimeFields/RuntimeField.cs @@ -2,6 +2,7 @@ // 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; using System.Runtime.Serialization; using Nest.Utf8Json; @@ -22,7 +23,7 @@ public interface IRuntimeField /// The script to be evaluated for field calculation at search time. /// [DataMember(Name = "script")] - IStoredScript Script { get; set; } + IInlineScript Script { get; set; } /// /// The datatype of the runtime field. @@ -36,7 +37,7 @@ public class RuntimeField : IRuntimeField /// public string Format { get; set; } /// - public IStoredScript Script { get; set; } + public IInlineScript Script { get; set; } /// public FieldType Type { get; set; } } @@ -47,13 +48,19 @@ public class RuntimeFieldDescriptor public RuntimeFieldDescriptor(FieldType fieldType) => Self.Type = fieldType; string IRuntimeField.Format { get; set; } - IStoredScript IRuntimeField.Script { get; set; } + IInlineScript IRuntimeField.Script { get; set; } FieldType IRuntimeField.Type { get; set; } + /// public RuntimeFieldDescriptor Format(string format) => Assign(format, (a, v) => a.Format = v); - - public RuntimeFieldDescriptor Script(IStoredScript script) => Assign(script, (a, v) => a.Script = v); - public RuntimeFieldDescriptor Script(string source) => Assign(source, (a, v) => a.Script = new PainlessScript(source)); + /// + public RuntimeFieldDescriptor Script(IInlineScript script) => Assign(script, (a, v) => a.Script = v); + + /// + public RuntimeFieldDescriptor Script(string source) => Assign(source, (a, v) => a.Script = new InlineScript(source)); + + /// + public RuntimeFieldDescriptor Script(Func selector) => Assign(selector, (a, v) => a.Script = v?.Invoke(new InlineScriptDescriptor())); } } diff --git a/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs b/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs index 6a2addb5bed..8e3caae2818 100644 --- a/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs +++ b/src/Nest/Mapping/Types/Core/Number/NumberAttribute.cs @@ -44,13 +44,27 @@ 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; + } + 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 52403855c29..9d67ca32d40 100644 --- a/src/Nest/Mapping/Types/Core/Number/NumberProperty.cs +++ b/src/Nest/Mapping/Types/Core/Number/NumberProperty.cs @@ -33,6 +33,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}")] @@ -45,10 +61,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}")] @@ -65,10 +86,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); @@ -85,6 +107,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/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs b/tests/Tests/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs index fcae1eea90f..8cfd341a28a 100644 --- a/tests/Tests/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs +++ b/tests/Tests/ClientConcepts/HighLevel/Mapping/FluentMapping.doc.cs @@ -463,7 +463,6 @@ public void MappingRuntimeFields() type = "keyword", script = new { - lang = "painless", source = "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))" } } @@ -487,6 +486,46 @@ public void MappingRuntimeFields() //hide Expect(expected).FromRequest(createIndexResponse); + + /** + * One may also include and use parameters in the script. + */ + createIndexResponse = _client.Indices.Create("myindex", c => c + .Map(m => m + .RuntimeFields(rtf => rtf + .RuntimeField("birthDayOfWeek", FieldType.Keyword, f => f + .Script(s => s + .Source("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT) + params.suffix)") + .Params(p => p.Add("suffix", " with a suffix.")) + ))) + ) + ); + + //json + var finalExpected = new + { + mappings = new + { + runtime = new + { + birthDayOfWeek = new + { + type = "keyword", + script = new + { + source = "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT) + params.suffix)", + @params = new Dictionary + { + {"suffix", " with a suffix." } + } + } + } + } + } + }; + + //hide + Expect(finalExpected).FromRequest(createIndexResponse); } } } diff --git a/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.cs b/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.cs index 11948e4854e..5907ebd9b15 100644 --- a/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.cs +++ b/tests/Tests/Indices/MappingManagement/PutMapping/PutMappingApiTest.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 Elastic.Transport; using Nest; @@ -344,7 +345,7 @@ public class PutMappingWithRuntimeFieldsTests : ApiTestBase + { + { "foo", " is a good day." } + }}}} } }; @@ -365,7 +369,7 @@ public PutMappingWithRuntimeFieldsTests(ReadOnlyCluster cluster, EndpointUsage u .Index(CallIsolatedValue) .RuntimeFields(rtf => rtf .RuntimeField("runtime_date", FieldType.Date, rf => rf.Format("yyyy-MM-dd")) - .RuntimeField("runtime_scripted", FieldType.Keyword, rf=> rf.Script(new PainlessScript(ScriptValue)))); + .RuntimeField("runtime_scripted", FieldType.Keyword, rf=> rf.Script(s => s.Source(ScriptValue).Lang(ScriptLang.Painless).Params(p => p.Add("foo", " is a good day."))))); protected override LazyResponses ClientUsage() => Calls( (client, f) => client.Indices.PutMapping(f), @@ -388,8 +392,12 @@ protected override LazyResponses ClientUsage() => Calls( type = "keyword", script = new { + source = ScriptValue, lang = "painless", - source = ScriptValue + @params = new Dictionary + { + { "foo", " is a good day." } + } } } } diff --git a/tests/Tests/Mapping/RuntimeFields/RuntimeFieldsTests.cs b/tests/Tests/Mapping/RuntimeFields/RuntimeFieldsTests.cs index 81e611f264f..33e7ecfe80b 100644 --- a/tests/Tests/Mapping/RuntimeFields/RuntimeFieldsTests.cs +++ b/tests/Tests/Mapping/RuntimeFields/RuntimeFieldsTests.cs @@ -44,7 +44,7 @@ public RuntimeFieldsTests(WritableCluster cluster, EndpointUsage usage) : base(n {RuntimeFieldNameOne, new RuntimeField { Type = FieldType.Keyword, - Script = new PainlessScript(ScriptValue) + Script = new InlineScript(ScriptValue) }}, {RuntimeFieldNameTwo, new RuntimeField { @@ -108,7 +108,7 @@ public RuntimeFieldsTests(WritableCluster cluster, EndpointUsage usage) : base(n {RuntimeFieldNameOne, new RuntimeField { Type = FieldType.Keyword, - Script = new PainlessScript(ScriptValue) + Script = new InlineScript(ScriptValue) }}, {RuntimeFieldNameTwo, new RuntimeField { diff --git a/tests/Tests/Mapping/Types/Core/Number/NumberPropertyTests.cs b/tests/Tests/Mapping/Types/Core/Number/NumberPropertyTests.cs index 5f02e06fe95..7db031750c0 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; @@ -157,4 +158,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 + } + } + }; + } } diff --git a/tests/Tests/Search/Search/SearchApiTests.cs b/tests/Tests/Search/Search/SearchApiTests.cs index 21a16b77382..8a2d4bbd07f 100644 --- a/tests/Tests/Search/Search/SearchApiTests.cs +++ b/tests/Tests/Search/Search/SearchApiTests.cs @@ -681,7 +681,6 @@ public SearchApiRuntimeFieldsTests(ReadOnlyCluster cluster, EndpointUsage usage) { script = new { - lang = "painless", source = RuntimeFieldScript }, type = "keyword" @@ -710,10 +709,11 @@ public SearchApiRuntimeFieldsTests(ReadOnlyCluster cluster, EndpointUsage usage) .And(RuntimeFieldName), RuntimeFields = new RuntimeFields { - { RuntimeFieldName, new RuntimeField + { + RuntimeFieldName, new RuntimeField { Type = FieldType.Keyword, - Script = new PainlessScript(RuntimeFieldScript) + Script = new InlineScript(RuntimeFieldScript) } } } diff --git a/tests/Tests/Search/SearchingRuntimeFields.doc.cs b/tests/Tests/Search/SearchingRuntimeFields.doc.cs index 34a4934c7d1..bfd98a9738d 100644 --- a/tests/Tests/Search/SearchingRuntimeFields.doc.cs +++ b/tests/Tests/Search/SearchingRuntimeFields.doc.cs @@ -129,7 +129,6 @@ public void SearchQueryRuntimeFields() { script = new { - lang = "painless", source = "if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}" }, type = "keyword" @@ -155,7 +154,7 @@ public void SearchQueryRuntimeFields() { "search_runtime_field", new RuntimeField { Type = FieldType.Keyword, - Script = new PainlessScript("if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}") + Script = new InlineScript("if (doc['type'].size() != 0) {emit(doc['type'].value.toUpperCase())}") } } } diff --git a/tests/Tests/XPack/Transform/TransformApiWithSettingsTests.cs b/tests/Tests/XPack/Transform/TransformApiWithSettingsTests.cs index dd358150626..bb5da854478 100644 --- a/tests/Tests/XPack/Transform/TransformApiWithSettingsTests.cs +++ b/tests/Tests/XPack/Transform/TransformApiWithSettingsTests.cs @@ -347,7 +347,6 @@ protected override LazyResponses ClientUsage() => Calls( { script = new { - lang = "painless", source = RuntimeFieldScript }, type = "keyword" @@ -410,7 +409,7 @@ protected override LazyResponses ClientUsage() => Calls( { RuntimeFieldName, new RuntimeField { Type = FieldType.Keyword, - Script = new PainlessScript(RuntimeFieldScript) + Script = new InlineScript(RuntimeFieldScript) } } }