Browse Source

Runtime fields to optionally ignore script errors (#92380)

Currently Elasticsearch always returns a shard failure once a runtime error arises from using a runtime field, the exception being script-less runtime fields. This also means that execution of the query for that shard stops, which is okay for development and exploration. In a production scenario, however, it is often desirable to ignore runtime errors and continue with the query execution.

This change adds a new a new on_script_error parameter to runtime field definitions similar to the already existing
parameter for index-time scripted fields. When `on_script_error` is set to `continue`, errors from script execution are effectively ignored. This means affected documents don't show up in query results, but also don't prevent other matches from the same shard. Runtime fields accessed through the fields API don't return values on errors, aggregations will ignore documents that throw errors.

Note that this change affects scripted runtime fields only, while leaving default behaviour untouched. Also, ignored errors are not reported back to users for now.

Relates to #72143
Christoph Büscher 2 years ago
parent
commit
8067f01d48
78 changed files with 2180 additions and 299 deletions
  1. 5 0
      docs/changelog/92380.yaml
  2. 26 16
      docs/reference/mapping/runtime.asciidoc
  3. 17 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java
  4. 124 0
      modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/114_composite_errors.yml
  5. 216 0
      modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/14_keyword_errors.yml
  6. 176 0
      modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/24_long_errors.yml
  7. 176 0
      modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/34_double_errors.yml
  8. 184 0
      modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/44_date_errors.yml
  9. 177 0
      modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/54_ip_errors.yml
  10. 184 0
      modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/64_boolean_errors.yml
  11. 26 7
      server/src/main/java/org/elasticsearch/index/mapper/AbstractScriptFieldType.java
  12. 1 1
      server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java
  13. 11 4
      server/src/main/java/org/elasticsearch/index/mapper/BooleanScriptFieldType.java
  14. 14 6
      server/src/main/java/org/elasticsearch/index/mapper/CompositeRuntimeField.java
  15. 7 3
      server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java
  16. 14 7
      server/src/main/java/org/elasticsearch/index/mapper/DateScriptFieldType.java
  17. 11 4
      server/src/main/java/org/elasticsearch/index/mapper/DoubleScriptFieldType.java
  18. 1 1
      server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java
  19. 12 5
      server/src/main/java/org/elasticsearch/index/mapper/GeoPointScriptFieldType.java
  20. 1 1
      server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java
  21. 16 4
      server/src/main/java/org/elasticsearch/index/mapper/IpScriptFieldType.java
  22. 1 1
      server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java
  23. 11 4
      server/src/main/java/org/elasticsearch/index/mapper/KeywordScriptFieldType.java
  24. 16 4
      server/src/main/java/org/elasticsearch/index/mapper/LongScriptFieldType.java
  25. 2 1
      server/src/main/java/org/elasticsearch/index/mapper/LookupRuntimeFieldType.java
  26. 2 2
      server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java
  27. 33 0
      server/src/main/java/org/elasticsearch/index/mapper/OnScriptError.java
  28. 2 1
      server/src/main/java/org/elasticsearch/index/mapper/RuntimeField.java
  29. 20 4
      server/src/main/java/org/elasticsearch/script/AbstractFieldScript.java
  30. 9 2
      server/src/main/java/org/elasticsearch/script/AbstractLongFieldScript.java
  31. 14 7
      server/src/main/java/org/elasticsearch/script/BooleanFieldScript.java
  32. 15 3
      server/src/main/java/org/elasticsearch/script/CompositeFieldScript.java
  33. 20 6
      server/src/main/java/org/elasticsearch/script/DateFieldScript.java
  34. 14 7
      server/src/main/java/org/elasticsearch/script/DoubleFieldScript.java
  35. 14 7
      server/src/main/java/org/elasticsearch/script/GeoPointFieldScript.java
  36. 14 7
      server/src/main/java/org/elasticsearch/script/IpFieldScript.java
  37. 14 7
      server/src/main/java/org/elasticsearch/script/LongFieldScript.java
  38. 2 1
      server/src/main/java/org/elasticsearch/script/SortedNumericDocValuesLongFieldScript.java
  39. 2 1
      server/src/main/java/org/elasticsearch/script/SortedSetDocValuesStringFieldScript.java
  40. 14 7
      server/src/main/java/org/elasticsearch/script/StringFieldScript.java
  41. 7 1
      server/src/test/java/org/elasticsearch/index/TimeSeriesModeTests.java
  42. 48 0
      server/src/test/java/org/elasticsearch/index/mapper/AbstractScriptFieldTypeTestCase.java
  43. 14 2
      server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldMapperTests.java
  44. 3 1
      server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldScriptTests.java
  45. 40 13
      server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptFieldTypeTests.java
  46. 7 2
      server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptMapperTests.java
  47. 9 2
      server/src/test/java/org/elasticsearch/index/mapper/CompositeRuntimeFieldTests.java
  48. 4 2
      server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java
  49. 5 2
      server/src/test/java/org/elasticsearch/index/mapper/DateFieldScriptTests.java
  50. 39 14
      server/src/test/java/org/elasticsearch/index/mapper/DateScriptFieldTypeTests.java
  51. 3 2
      server/src/test/java/org/elasticsearch/index/mapper/DateScriptMapperTests.java
  52. 2 1
      server/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java
  53. 14 2
      server/src/test/java/org/elasticsearch/index/mapper/DoubleFieldMapperTests.java
  54. 5 2
      server/src/test/java/org/elasticsearch/index/mapper/DoubleFieldScriptTests.java
  55. 38 14
      server/src/test/java/org/elasticsearch/index/mapper/DoubleScriptFieldTypeTests.java
  56. 7 2
      server/src/test/java/org/elasticsearch/index/mapper/DoubleScriptMapperTests.java
  57. 3 1
      server/src/test/java/org/elasticsearch/index/mapper/GeoPointFieldScriptTests.java
  58. 27 12
      server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptFieldTypeTests.java
  59. 7 2
      server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptMapperTests.java
  60. 4 4
      server/src/test/java/org/elasticsearch/index/mapper/IndexTimeScriptTests.java
  61. 14 2
      server/src/test/java/org/elasticsearch/index/mapper/IpFieldMapperTests.java
  62. 5 2
      server/src/test/java/org/elasticsearch/index/mapper/IpFieldScriptTests.java
  63. 35 14
      server/src/test/java/org/elasticsearch/index/mapper/IpScriptFieldTypeTests.java
  64. 7 2
      server/src/test/java/org/elasticsearch/index/mapper/IpScriptMapperTests.java
  65. 14 2
      server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldMapperTests.java
  66. 37 16
      server/src/test/java/org/elasticsearch/index/mapper/KeywordScriptFieldTypeTests.java
  67. 7 2
      server/src/test/java/org/elasticsearch/index/mapper/KeywordScriptMapperTests.java
  68. 14 2
      server/src/test/java/org/elasticsearch/index/mapper/LongFieldMapperTests.java
  69. 5 2
      server/src/test/java/org/elasticsearch/index/mapper/LongFieldScriptTests.java
  70. 48 16
      server/src/test/java/org/elasticsearch/index/mapper/LongScriptFieldTypeTests.java
  71. 7 2
      server/src/test/java/org/elasticsearch/index/mapper/LongScriptMapperTests.java
  72. 8 3
      server/src/test/java/org/elasticsearch/index/mapper/StringFieldScriptTests.java
  73. 4 0
      server/src/test/java/org/elasticsearch/script/CompositeFieldScriptTests.java
  74. 10 3
      server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java
  75. 5 3
      server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java
  76. 2 0
      server/src/test/java/org/elasticsearch/search/runtime/GeoPointScriptFieldDistanceFeatureQueryTests.java
  77. 2 0
      server/src/test/java/org/elasticsearch/search/runtime/LongScriptFieldDistanceFeatureQueryTests.java
  78. 52 8
      test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java

+ 5 - 0
docs/changelog/92380.yaml

@@ -0,0 +1,5 @@
+pr: 92380
+summary: Add option to allow script errors on runtime fields
+area: Search
+type: enhancement
+issues: []

+ 26 - 16
docs/reference/mapping/runtime.asciidoc

@@ -53,7 +53,7 @@ combined use less resources and reduce your operating costs.
 Runtime fields can replace many of the ways you can use scripting with the
 `_search` API. How you use a runtime field is impacted by the number of
 documents that the included script runs against. For example, if you're using
-the `fields` parameter on the `_search` API to 
+the `fields` parameter on the `_search` API to
 <<runtime-retrieving-fields,retrieve the values of a runtime field>>, the script
 runs only against the top hits just like script fields do.
 
@@ -77,7 +77,7 @@ If you move a script from any of these sections in a search request to a
 runtime field that is computing values from the same number of documents, the
 performance should be about the same. The performance for these features is
 largely dependent upon the calculations that the included script is running and
-how many documents the script runs against. 
+how many documents the script runs against.
 
 [discrete]
 [[runtime-compromises]]
@@ -88,9 +88,9 @@ the runtime script.
 
 To balance search performance and flexibility, index fields that you'll
 frequently search for and filter on, such as a timestamp. {es} automatically
-uses these indexed fields first when running a query, resulting in a fast 
-response time. You can then use runtime fields to limit the number of fields 
-that {es} needs to calculate values for. Using indexed fields in tandem with 
+uses these indexed fields first when running a query, resulting in a fast
+response time. You can then use runtime fields to limit the number of fields
+that {es} needs to calculate values for. Using indexed fields in tandem with
 runtime fields provides flexibility in the data that you index and how you
 define queries for other fields.
 
@@ -111,7 +111,7 @@ You map runtime fields by adding a `runtime` section under the mapping
 definition and defining
 <<modules-scripting-using,a Painless script>>. This script has access to the
 entire context of a document, including the original `_source` via `params._source`
-and any mapped fields plus their values. At query time, the script runs and 
+and any mapped fields plus their values. At query time, the script runs and
 generates values for each scripted field that is required for the query.
 
 .Emitting runtime field values
@@ -227,6 +227,16 @@ with `params._source` (such as `params._source.day_of_week`). For simplicity,
 defining a runtime field in the mapping definition without a script is the
 recommended option, whenever possible.
 
+[[runtime-errorhandling]]
+==== Ignoring script errors on runtime fields
+
+Scripts can throw errors at runtime, e.g. on accessing missing or invalid values
+in documents or because of performing invalid operations. The `on_script_error`
+parameter can be used to control error behaviour when this happens. Setting this
+parameter to `continue` will have the effect of silently ignoring all errors on
+this runtime field. The default `fail` value will cause a shard failure which
+gets reported in the search response.
+
 [[runtime-updating-scripts]]
 ==== Updating and removing runtime fields
 
@@ -932,14 +942,14 @@ can define runtime fields in the
 decide to index a runtime field for greater performance, just move the full
 runtime field definition (including the script) to the context of an index
 mapping. {es} automatically uses these indexed fields to drive queries,
-resulting in a fast response time. This capability means you can write a 
+resulting in a fast response time. This capability means you can write a
 script only once, and apply it to any context that supports runtime fields.
 
 NOTE: Indexing a `composite` runtime field is currently not supported.
 
-You can then use runtime fields to limit the number of fields that {es} needs 
-to calculate values for. Using indexed fields in tandem with runtime fields 
-provides flexibility in the data that you index and how you define queries for 
+You can then use runtime fields to limit the number of fields that {es} needs
+to calculate values for. Using indexed fields in tandem with runtime fields
+provides flexibility in the data that you index and how you define queries for
 other fields.
 
 IMPORTANT: After indexing a runtime field, you cannot update the included
@@ -1417,9 +1427,9 @@ GET my-index-000001/_search
 
 [[runtime-examples-grok-composite]]
 ==== Define a composite runtime field
-You can also define a _composite_ runtime field to emit multiple fields from a 
-single script. You can define a set of typed subfields and emit a map of 
-values. At search time, each subfield retrieves the value associated with 
+You can also define a _composite_ runtime field to emit multiple fields from a
+single script. You can define a set of typed subfields and emit a map of
+values. At search time, each subfield retrieves the value associated with
 their name in the map. This means that you only need to specify your grok
 pattern one time and can return multiple values:
 
@@ -1467,11 +1477,11 @@ GET my-index-000001/_search
 ----
 // TEST[continued]
 
-The API returns the following result. Because `http` is a `composite` runtime 
+The API returns the following result. Because `http` is a `composite` runtime
 field, the response includes each of the sub-fields under `fields`, including
-any associated values that match the query. Without building your data structure 
+any associated values that match the query. Without building your data structure
 in advance, you can search and explore your data in meaningful ways to
-experiment and determine which fields to index. 
+experiment and determine which fields to index.
 
 [source,console-result]
 ----

+ 17 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java

@@ -49,6 +49,7 @@ import org.elasticsearch.index.Index;
 import org.elasticsearch.index.IndexService;
 import org.elasticsearch.index.mapper.DateFieldMapper;
 import org.elasticsearch.index.mapper.DocumentMapper;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.index.mapper.ParsedDocument;
 import org.elasticsearch.index.mapper.SourceToParse;
 import org.elasticsearch.index.query.AbstractQueryBuilder;
@@ -557,7 +558,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                     BooleanFieldScript.LeafFactory leafFactory = factory.newFactory(
                         BooleanFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
-                        context.lookup()
+                        context.lookup(),
+                        OnScriptError.FAIL
                     );
                     BooleanFieldScript booleanFieldScript = leafFactory.newInstance(leafReaderContext);
                     List<Boolean> booleans = new ArrayList<>();
@@ -571,7 +573,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                         DateFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
                         context.lookup(),
-                        DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER
+                        DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER,
+                        OnScriptError.FAIL
                     );
                     DateFieldScript dateFieldScript = leafFactory.newInstance(leafReaderContext);
                     List<String> dates = new ArrayList<>();
@@ -584,7 +587,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                     DoubleFieldScript.LeafFactory leafFactory = factory.newFactory(
                         DoubleFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
-                        context.lookup()
+                        context.lookup(),
+                        OnScriptError.FAIL
                     );
                     DoubleFieldScript doubleFieldScript = leafFactory.newInstance(leafReaderContext);
                     List<Double> doubles = new ArrayList<>();
@@ -597,7 +601,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                     GeoPointFieldScript.LeafFactory leafFactory = factory.newFactory(
                         GeoPointFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
-                        context.lookup()
+                        context.lookup(),
+                        OnScriptError.FAIL
                     );
                     GeoPointFieldScript geoPointFieldScript = leafFactory.newInstance(leafReaderContext);
                     List<GeoPoint> points = new ArrayList<>();
@@ -615,7 +620,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                     IpFieldScript.LeafFactory leafFactory = factory.newFactory(
                         IpFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
-                        context.lookup()
+                        context.lookup(),
+                        OnScriptError.FAIL
                     );
                     IpFieldScript ipFieldScript = leafFactory.newInstance(leafReaderContext);
                     List<String> ips = new ArrayList<>();
@@ -634,7 +640,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                     LongFieldScript.LeafFactory leafFactory = factory.newFactory(
                         LongFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
-                        context.lookup()
+                        context.lookup(),
+                        OnScriptError.FAIL
                     );
                     LongFieldScript longFieldScript = leafFactory.newInstance(leafReaderContext);
                     List<Long> longs = new ArrayList<>();
@@ -647,7 +654,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                     StringFieldScript.LeafFactory leafFactory = factory.newFactory(
                         StringFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
-                        context.lookup()
+                        context.lookup(),
+                        OnScriptError.FAIL
                     );
                     StringFieldScript stringFieldScript = leafFactory.newInstance(leafReaderContext);
                     List<String> keywords = new ArrayList<>();
@@ -660,7 +668,8 @@ public class PainlessExecuteAction extends ActionType<PainlessExecuteAction.Resp
                     CompositeFieldScript.LeafFactory leafFactory = factory.newFactory(
                         CompositeFieldScript.CONTEXT.name,
                         request.getScript().getParams(),
-                        context.lookup()
+                        context.lookup(),
+                        OnScriptError.FAIL
                     );
                     CompositeFieldScript compositeFieldScript = leafFactory.newInstance(leafReaderContext);
                     compositeFieldScript.runForDoc(0);

+ 124 - 0
modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/114_composite_errors.yml

@@ -0,0 +1,124 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: test
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+          mappings:
+            runtime:
+              rtf:
+                type: composite
+                on_script_error: continue
+                script:
+                  source: |
+                    if (doc["message"].value.equals("fail")) throw new Exception("error");
+                    emit('msg', doc['message'].value);
+                fields:
+                  msg:
+                    type: keyword
+              rtf_strict:
+                type: composite
+                on_script_error: fail
+                script:
+                  source: |
+                    if (doc["message"].value.equals("fail")) throw new Exception("error");
+                    emit('msg', doc['message'].value);
+                fields:
+                  msg:
+                    type: keyword
+            properties:
+              timestamp:
+                type: date
+              message:
+                type: keyword
+
+  - do:
+      bulk:
+        index: test
+        refresh: true
+        body: |
+          {"index":{}}
+          {"timestamp": "1998-04-30T14:30:17-05:00", "message" : "this is okay"}
+          {"index":{}}
+          {"timestamp": "1998-04-30T14:30:53-05:00", "message" : "fail"}
+
+---
+"query with continue on error":
+  - do:
+      search:
+        index: test
+        body:
+          query:
+            term:
+              rtf.msg: "this is okay"
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0._source.message: "this is okay"}
+
+---
+"query with fail on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: test
+        body:
+          query:
+            term:
+              rtf_strict.msg: "this is okay"
+
+---
+"query with search time field":
+  - do:
+      search:
+        index: test
+        body:
+          query:
+            term:
+              rtf_search.msg: "this is okay"
+          runtime_mappings:
+            rtf_search:
+              type: composite
+              on_script_error: continue
+              script:
+                source: |
+                  if (doc["message"].value.equals("fail")) throw new Exception("error");
+                  emit('msg', doc['message'].value);
+              fields:
+                msg:
+                  type: keyword
+
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0._source.message: "this is okay"}
+
+---
+fetch:
+  - do:
+      search:
+        index: test
+        body:
+          sort: timestamp
+          fields:
+            - message
+            - rtf.msg
+  - match: {hits.total.value: 2}
+  - match: {hits.hits.0.fields.message: ["this is okay"] }
+  - match: {hits.hits.0.fields.rtf\.msg: ["this is okay"] }
+  - match: {hits.hits.1.fields.message: ["fail"] }
+  - is_false: hits.hits.1.fields.rtf.msg
+
+---
+"terms agg":
+  - do:
+      search:
+        index: test
+        body:
+          aggs:
+            messages:
+              terms:
+                field: rtf.msg
+  - match: { hits.total.value: 2}
+  - length: { aggregations.messages.buckets: 1 }
+  - match: { aggregations.messages.buckets.0.key: "this is okay" }
+  - match: { aggregations.messages.buckets.0.doc_count: 1 }

+ 216 - 0
modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/14_keyword_errors.yml

@@ -0,0 +1,216 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: testindex
+        body:
+          settings:
+            number_of_shards: 1
+          mappings:
+            runtime:
+              first_char:
+                type: keyword
+                script: |
+                  emit(doc['name'].value.substring(0,1));
+                on_script_error: continue
+              first_char_strict_error:
+                type: keyword
+                script: |
+                  emit(doc['name'].value.substring(0,1));
+                on_script_error: fail
+            properties:
+              name:
+                type: keyword
+
+  - do:
+      bulk:
+        index: testindex
+        refresh: true
+        body: |
+          {"index":{}}
+          {"name": "foo"}
+          {"index":{}}
+          {"name": ""}
+
+---
+"Query rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              first_char: "f"
+          fields: [ name, first_char ]
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.first_char: [ f ] }
+
+---
+"Query rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              first_char_strict_error: "f"
+          fields: [ name, first_char_strict_error ]
+
+---
+"Aggregate on rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: first_char
+  - length: { aggregations.firstchar.buckets: 1 }
+  - match: { aggregations.firstchar.buckets.0.key: "f" }
+
+---
+"Aggregate on rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: first_char_strict_error
+
+---
+"Fields retrieval with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name, first_char ]
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.first_char: [ f ] }
+  - match: { hits.hits.1.fields.name: [ "" ] }
+  - is_false: hits.hits.1.fields.first_char
+
+---
+"Fields retrieval with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name, first_char_strict_error ]
+
+---
+"Sorting with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name ]
+          sort: first_char
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.1.fields.name: [ "" ] }
+
+---
+"Sorting with with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name ]
+          sort: first_char_strict_error
+
+---
+"Query search time rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              first_char_search: "f"
+          fields: [ name, first_char_search ]
+          runtime_mappings:
+            first_char_search:
+              type: keyword
+              script: |
+                emit(doc['name'].value.substring(0,1));
+              on_script_error: continue
+
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.first_char_search: [ f ] }
+
+---
+"Query search time rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              first_char_search: "f"
+          fields: [ name, first_char_search ]
+          runtime_mappings:
+            first_char_search:
+              type: keyword
+              script: |
+                emit(doc['name'].value.substring(0,1));
+              on_script_error: fail
+
+---
+"Change error behaviour for lenient runtime field":
+
+  - do:
+      indices.put_mapping:
+        index: testindex
+        body:
+          runtime:
+            first_char_variant:
+              type: keyword
+              script: |
+                emit(doc['name'].value.substring(0,1));
+
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              first_char_variant: "f"
+
+  - do:
+      indices.put_mapping:
+        index: testindex
+        body:
+          runtime:
+            first_char_variant:
+              type: keyword
+              script: |
+                emit(doc['name'].value.substring(0,1));
+              on_script_error: continue
+
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              first_char_variant: "f"
+          fields: [ name, first_char_variant ]
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.first_char_variant: [ f ] }

+ 176 - 0
modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/24_long_errors.yml

@@ -0,0 +1,176 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: testindex
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+          mappings:
+            runtime:
+              rtf:
+                type: long
+                script: |
+                  if(doc['name'].value.equals("")) throw new Exception("empty");
+                  emit(doc['name'].value.length());
+                on_script_error: continue
+              rtf_strict_error:
+                type: long
+                script: |
+                  if(doc['name'].value.equals("")) throw new Exception("empty");
+                  emit(doc['name'].value.length());
+                on_script_error: fail
+            properties:
+              name:
+                type: keyword
+
+  - do:
+      bulk:
+        index: testindex
+        refresh: true
+        body: |
+          {"index":{}}
+          {"name": "foo"}
+          {"index":{}}
+          {"name": ""}
+
+---
+"Query rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf: 3
+          fields: [ name, rtf ]
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.rtf: [ 3 ] }
+
+---
+"Query rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_strict_error: 3
+          fields: [ name, rtf_strict_error ]
+
+---
+"Aggregate on rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: rtf
+  - length: { aggregations.firstchar.buckets: 1 }
+  - match: { aggregations.firstchar.buckets.0.key: 3 }
+
+---
+"Aggregate on rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: rtf_strict_error
+
+---
+"Fields retrieval with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name, rtf ]
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.rtf: [ 3 ] }
+  - match: { hits.hits.1.fields.name: [ "" ] }
+  - is_false: hits.hits.1.fields.rtf
+
+---
+"Fields retrieval with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name, rtf_strict_error ]
+
+---
+"Sorting with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name ]
+          sort: rtf
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.1.fields.name: [ "" ] }
+
+---
+"Sorting with with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name ]
+          sort: rtf_strict_error
+
+---
+"Query search time rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: 3
+          fields: [ name, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: long
+              script: |
+                if(doc['name'].value.equals("")) throw new Exception("empty");
+                emit(doc['name'].value.length());
+              on_script_error: continue
+
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.rtf_search: [ 3 ] }
+
+---
+"Query search time rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: 3
+          fields: [ name, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: long
+              script: |
+                if(doc['name'].value.equals("")) throw new Exception("empty");
+                emit(doc['name'].value.length());
+              on_script_error: fail

+ 176 - 0
modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/34_double_errors.yml

@@ -0,0 +1,176 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: testindex
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+          mappings:
+            runtime:
+              rtf:
+                type: double
+                script: |
+                  if(doc['name'].value.equals("")) throw new Exception("empty");
+                  emit(doc['name'].value.length());
+                on_script_error: continue
+              rtf_strict_error:
+                type: double
+                script: |
+                  if(doc['name'].value.equals("")) throw new Exception("empty");
+                  emit(doc['name'].value.length());
+                on_script_error: fail
+            properties:
+              name:
+                type: keyword
+
+  - do:
+      bulk:
+        index: testindex
+        refresh: true
+        body: |
+          {"index":{}}
+          {"name": "foo"}
+          {"index":{}}
+          {"name": ""}
+
+---
+"Query rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf: 3
+          fields: [ name, rtf ]
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.rtf: [ 3.0 ] }
+
+---
+"Query rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_strict_error: 3
+          fields: [ name, rtf_strict_error ]
+
+---
+"Aggregate on rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: rtf
+  - length: { aggregations.firstchar.buckets: 1 }
+  - match: { aggregations.firstchar.buckets.0.key: 3.0 }
+
+---
+"Aggregate on rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: rtf_strict_error
+
+---
+"Fields retrieval with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name, rtf ]
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.rtf: [ 3.0 ] }
+  - match: { hits.hits.1.fields.name: [ "" ] }
+  - is_false: hits.hits.1.fields.rtf
+
+---
+"Fields retrieval with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name, rtf_strict_error ]
+
+---
+"Sorting with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name ]
+          sort: rtf
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.1.fields.name: [ "" ] }
+
+---
+"Sorting with with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ name ]
+          sort: rtf_strict_error
+
+---
+"Query search time rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: 3
+          fields: [ name, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: double
+              script: |
+                if(doc['name'].value.equals("")) throw new Exception("empty");
+                emit(doc['name'].value.length());
+              on_script_error: continue
+
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.name: [ foo ] }
+  - match: { hits.hits.0.fields.rtf_search: [ 3.0 ] }
+
+---
+"Query search time rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: 3
+          fields: [ name, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: double
+              script: |
+                if(doc['name'].value.equals("")) throw new Exception("empty");
+                emit(doc['name'].value.length());
+              on_script_error: fail

+ 184 - 0
modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/44_date_errors.yml

@@ -0,0 +1,184 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: testindex
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+          mappings:
+            runtime:
+              rtf:
+                type: date
+                format: yyyy-MM-dd
+                script: |
+                  if(doc['millis_since_epoch'].value < 0) throw new Exception("date before 1970");
+                  emit(doc['millis_since_epoch'].value);
+                on_script_error: continue
+              rtf_strict_error:
+                type: date
+                format: yyyy-MM-dd
+                script: |
+                  if(doc['millis_since_epoch'].value < 0) throw new Exception("date before 1970");
+                  emit(doc['millis_since_epoch'].value);
+                on_script_error: fail
+            properties:
+              millis_since_epoch:
+                type: long
+
+  - do:
+      bulk:
+        index: testindex
+        refresh: true
+        body: |
+          {"index":{}}
+          {"millis_since_epoch": 1671033474411}
+          {"index":{}}
+          {"millis_since_epoch": -1}
+
+---
+"Query rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            range:
+              rtf:
+                gte: "2022-12-14"
+          fields: [ millis_since_epoch, rtf ]
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.millis_since_epoch: [ 1671033474411 ] }
+  - match: { hits.hits.0.fields.rtf: [ "2022-12-14" ] }
+
+---
+"Query rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            range:
+              rtf_strict_error:
+                gte: "2022-12-14"
+          fields: [ millis_since_epoch, rtf_strict_error ]
+
+---
+"Aggregate on rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: rtf
+  - length: { aggregations.firstchar.buckets: 1 }
+  - match: { aggregations.firstchar.buckets.0.key_as_string: "2022-12-14" }
+
+---
+"Aggregate on rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          aggs:
+            firstchar:
+              terms:
+                field: rtf_strict_error
+
+---
+"Fields retrieval with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ millis_since_epoch, rtf ]
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.millis_since_epoch: [ 1671033474411 ] }
+  - match: { hits.hits.0.fields.rtf: [ "2022-12-14" ] }
+  - match: { hits.hits.1.fields.millis_since_epoch: [ -1 ] }
+  - is_false: hits.hits.1.fields.rtf
+
+---
+"Fields retrieval with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ millis_since_epoch, rtf_strict_error ]
+
+---
+"Sorting with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ millis_since_epoch ]
+          sort: rtf
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.millis_since_epoch: [ 1671033474411 ] }
+  - match: { hits.hits.1.fields.millis_since_epoch: [ -1 ] }
+
+---
+"Sorting with with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ millis_since_epoch ]
+          sort: rtf_strict_error
+
+---
+"Query search time rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            range:
+              rtf_search:
+                gte: "2022-12-14"
+          fields: [ millis_since_epoch, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: date
+              format: yyyy-MM-dd
+              script: |
+                if(doc['millis_since_epoch'].value < 0) throw new Exception("date before 1970");
+                emit(doc['millis_since_epoch'].value);
+              on_script_error: continue
+
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.millis_since_epoch: [ 1671033474411 ] }
+  - match: { hits.hits.0.fields.rtf_search: [ "2022-12-14" ] }
+
+---
+"Query search time rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            range:
+              rtf_search:
+                gte: "2022-12-14"
+          fields: [ millis_since_epoch, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: date
+              format: yyyy-MM-dd
+              script: |
+                if(doc['millis_since_epoch'].value < 0) throw new Exception("date before 1970");
+                emit(doc['millis_since_epoch'].value);
+              on_script_error: fail

+ 177 - 0
modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/54_ip_errors.yml

@@ -0,0 +1,177 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: testindex
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+          mappings:
+            runtime:
+              rtf:
+                type: ip
+                script: |
+                  if(doc['ip_string'].value.length() <= 0) throw new Exception("empty");
+                  emit(doc['ip_string'].value);
+                on_script_error: continue
+              rtf_strict_error:
+                type: ip
+                script: |
+                  if(doc['ip_string'].value.length() <= 0) throw new Exception("empty");
+                  emit(doc['ip_string'].value);
+                on_script_error: fail
+            properties:
+              ip_string:
+                type: keyword
+
+  - do:
+      bulk:
+        index: testindex
+        refresh: true
+        body: |
+          {"index":{}}
+          {"ip_string": "192.68.0.1"}
+          {"index":{}}
+          {"ip_string": ""}
+
+---
+"Query rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            term:
+              rtf:
+                192.68.0.1
+          fields: [ ip_string, rtf ]
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.ip_string: [ "192.68.0.1" ] }
+  - match: { hits.hits.0.fields.rtf: [ 192.68.0.1 ] }
+
+---
+"Query rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            term:
+              rtf_strict_error: 192.68.0.1
+          fields: [ ip_string, rtf_strict_error ]
+
+---
+"Aggregate on rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          aggs:
+            rtf:
+              terms:
+                field: rtf
+  - length: { aggregations.rtf.buckets: 1 }
+  - match: { aggregations.rtf.buckets.0.key: 192.68.0.1 }
+
+---
+"Aggregate on rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          aggs:
+            rtf:
+              terms:
+                field: rtf_strict_error
+
+---
+"Fields retrieval with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ ip_string, rtf ]
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.ip_string: [ "192.68.0.1" ] }
+  - match: { hits.hits.0.fields.rtf: [ "192.68.0.1" ] }
+  - match: { hits.hits.1.fields.ip_string: [ "" ] }
+  - is_false: hits.hits.1.fields.rtf
+
+---
+"Fields retrieval with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ ip_string, rtf_strict_error ]
+
+---
+"Sorting with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ ip_string ]
+          sort: rtf
+  - match: { hits.total.value: 2 }
+  - match: { hits.hits.0.fields.ip_string: [ "192.68.0.1" ] }
+  - match: { hits.hits.1.fields.ip_string: [ "" ] }
+
+---
+"Sorting with with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ ip_string ]
+          sort: rtf_strict_error
+
+---
+"Query search time rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: "192.68.0.1"
+          fields: [ ip_string, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: ip
+              script: |
+                if(doc['ip_string'].value.length() <= 0) throw new Exception("empty");
+                emit(doc['ip_string'].value);
+              on_script_error: continue
+
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.ip_string: [ "192.68.0.1" ] }
+  - match: { hits.hits.0.fields.rtf_search: [ "192.68.0.1" ] }
+
+---
+"Query search time rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: "192.68.0.1"
+          fields: [ ip_string, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: ip
+              script: |
+                if(doc['ip_string'].value.length() <= 0) throw new Exception("empty");
+                emit(doc['ip_string'].value);
+              on_script_error: fail

+ 184 - 0
modules/runtime-fields-common/src/yamlRestTest/resources/rest-api-spec/test/runtime_fields/64_boolean_errors.yml

@@ -0,0 +1,184 @@
+---
+setup:
+  - do:
+      indices.create:
+        index: testindex
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+          mappings:
+            runtime:
+              rtf:
+                type: boolean
+                script: |
+                  if(doc['age'].value < 0) throw new Exception("invalid age");
+                  emit(doc['age'].value >= 18);
+                on_script_error: continue
+              rtf_strict_error:
+                type: boolean
+                script: |
+                  if(doc['age'].value <= 0) throw new Exception("invalid age");
+                  emit(doc['age'].value >=18);
+                on_script_error: fail
+            properties:
+              age:
+                type: integer
+
+  - do:
+      bulk:
+        index: testindex
+        refresh: true
+        body: |
+          {"index":{}}
+          {"age": 14}
+          {"index":{}}
+          {"age": 20}
+          {"index":{}}
+          {"age": -1}
+
+---
+"Query rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf:
+                true
+          fields: [ age, rtf ]
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.age: [ 20 ] }
+  - match: { hits.hits.0.fields.rtf: [ true ] }
+
+---
+"Query rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_strict_error: true
+          fields: [ age, rtf_strict_error ]
+
+---
+"Aggregate on rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          aggs:
+            rtf:
+              terms:
+                field: rtf
+                order: { "_key": "asc" }
+  - length: { aggregations.rtf.buckets: 2 }
+  - match: { aggregations.rtf.buckets.0.key_as_string: "false" }
+  - match: { aggregations.rtf.buckets.1.key_as_string: "true" }
+---
+"Aggregate on rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          aggs:
+            rtf:
+              terms:
+                field: rtf_strict_error
+
+---
+"Fields retrieval with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ age, rtf ]
+          sort: { "age": "desc" }
+  - match: { hits.total.value: 3 }
+  - match: { hits.hits.0.fields.age: [ 20 ] }
+  - match: { hits.hits.0.fields.rtf: [ true ] }
+  - match: { hits.hits.1.fields.age: [ 14 ] }
+  - match: { hits.hits.1.fields.rtf: [ false ] }
+  - match: { hits.hits.2.fields.age: [ -1 ] }
+  - is_false: hits.hits.2.fields.rtf
+
+---
+"Fields retrieval with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ age, rtf_strict_error ]
+
+---
+"Sorting with ignoring error":
+  - do:
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ age ]
+          sort: rtf
+  - match: { hits.total.value: 3 }
+  - match: { hits.hits.0.fields.age: [ 14 ] }
+  - match: { hits.hits.1.fields.age: [ 20 ] }
+  - match: { hits.hits.2.fields.age: [ -1 ] }
+
+---
+"Sorting with with failing on error":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query: { match_all: { } }
+          fields: [ age ]
+          sort: rtf_strict_error
+
+---
+"Query search time rtf with on_script_error continue":
+  - do:
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: true
+          fields: [ age, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: boolean
+              script: |
+                if(doc['age'].value < 0) throw new Exception("invalid age");
+                emit(doc['age'].value >= 18);
+              on_script_error: continue
+
+  - match: { hits.total.value: 1 }
+  - match: { hits.hits.0.fields.age: [ 20 ] }
+  - match: { hits.hits.0.fields.rtf_search: [ true ] }
+
+---
+"Query search time rtf with on_script_error fail":
+  - do:
+      catch: /type=script_exception, reason=runtime error/
+      search:
+        index: testindex
+        body:
+          query:
+            match:
+              rtf_search: true
+          fields: [ age, rtf_search ]
+          runtime_mappings:
+            rtf_search:
+              type: boolean
+              script: |
+                if(doc['age'].value < 0) throw new Exception("invalid age");
+                emit(doc['age'].value >= 18);
+              on_script_error: fail

+ 26 - 7
server/src/main/java/org/elasticsearch/index/mapper/AbstractScriptFieldType.java

@@ -215,7 +215,7 @@ abstract class AbstractScriptFieldType<LeafFactory> extends MappedFieldType {
     abstract static class Builder<Factory> extends RuntimeField.Builder {
         private final ScriptContext<Factory> scriptContext;
 
-        final FieldMapper.Parameter<Script> script = new FieldMapper.Parameter<>(
+        private final FieldMapper.Parameter<Script> script = new FieldMapper.Parameter<>(
             "script",
             true,
             () -> null,
@@ -225,6 +225,8 @@ abstract class AbstractScriptFieldType<LeafFactory> extends MappedFieldType {
             Objects::toString
         ).setSerializerCheck((id, ic, v) -> ic);
 
+        private final FieldMapper.Parameter<String> onScriptError = FieldMapper.Parameter.onScriptErrorParam(m -> m.onScriptError, script);
+
         Builder(String name, ScriptContext<Factory> scriptContext) {
             super(name);
             this.scriptContext = scriptContext;
@@ -247,7 +249,8 @@ abstract class AbstractScriptFieldType<LeafFactory> extends MappedFieldType {
         protected final RuntimeField createChildRuntimeField(
             MappingParserContext parserContext,
             String parent,
-            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory
+            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory,
+            OnScriptError onScriptError
         ) {
             if (script.isConfigured()) {
                 throw new IllegalArgumentException(
@@ -257,7 +260,7 @@ abstract class AbstractScriptFieldType<LeafFactory> extends MappedFieldType {
             String fullName = parent + "." + name;
             return new LeafRuntimeField(
                 name,
-                createFieldType(fullName, getCompositeLeafFactory(parentScriptFactory), getScript(), meta()),
+                createFieldType(fullName, getCompositeLeafFactory(parentScriptFactory), getScript(), meta(), onScriptError),
                 getParameters()
             );
         }
@@ -267,26 +270,41 @@ abstract class AbstractScriptFieldType<LeafFactory> extends MappedFieldType {
         }
 
         final RuntimeField createRuntimeField(Factory scriptFactory, Version indexVersion) {
-            var fieldType = createFieldType(name, scriptFactory, getScript(), meta(), indexVersion);
+            var fieldType = createFieldType(
+                name,
+                scriptFactory,
+                getScript(),
+                meta(),
+                indexVersion,
+                OnScriptError.fromString(onScriptError.get())
+            );
             return new LeafRuntimeField(name, fieldType, getParameters());
         }
 
-        abstract AbstractScriptFieldType<?> createFieldType(String name, Factory factory, Script script, Map<String, String> meta);
+        abstract AbstractScriptFieldType<?> createFieldType(
+            String name,
+            Factory factory,
+            Script script,
+            Map<String, String> meta,
+            OnScriptError onScriptError
+        );
 
         AbstractScriptFieldType<?> createFieldType(
             String name,
             Factory factory,
             Script script,
             Map<String, String> meta,
-            Version supportedVersion
+            Version supportedVersion,
+            OnScriptError onScriptError
         ) {
-            return createFieldType(name, factory, script, meta);
+            return createFieldType(name, factory, script, meta, onScriptError);
         }
 
         @Override
         protected List<FieldMapper.Parameter<?>> getParameters() {
             List<FieldMapper.Parameter<?>> parameters = new ArrayList<>(super.getParameters());
             parameters.add(script);
+            parameters.add(onScriptError);
             return Collections.unmodifiableList(parameters);
         }
 
@@ -296,5 +314,6 @@ abstract class AbstractScriptFieldType<LeafFactory> extends MappedFieldType {
             }
             return script.get();
         }
+
     }
 }

+ 1 - 1
server/src/main/java/org/elasticsearch/index/mapper/BooleanFieldMapper.java

@@ -139,7 +139,7 @@ public class BooleanFieldMapper extends FieldMapper {
             BooleanFieldScript.Factory scriptFactory = scriptCompiler.compile(script.get(), BooleanFieldScript.CONTEXT);
             return scriptFactory == null
                 ? null
-                : (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(name, script.get().getParams(), lookup)
+                : (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(name, script.get().getParams(), lookup, OnScriptError.FAIL)
                     .newInstance(ctx)
                     .runForDoc(doc, consumer);
         }

+ 11 - 4
server/src/main/java/org/elasticsearch/index/mapper/BooleanScriptFieldType.java

@@ -46,9 +46,10 @@ public final class BooleanScriptFieldType extends AbstractScriptFieldType<Boolea
             String name,
             BooleanFieldScript.Factory factory,
             Script script,
-            Map<String, String> meta
+            Map<String, String> meta,
+            OnScriptError onScriptError
         ) {
-            return new BooleanScriptFieldType(name, factory, script, meta);
+            return new BooleanScriptFieldType(name, factory, script, meta, onScriptError);
         }
 
         @Override
@@ -67,10 +68,16 @@ public final class BooleanScriptFieldType extends AbstractScriptFieldType<Boolea
         return new Builder(name).createRuntimeField(BooleanFieldScript.PARSE_FROM_SOURCE);
     }
 
-    BooleanScriptFieldType(String name, BooleanFieldScript.Factory scriptFactory, Script script, Map<String, String> meta) {
+    BooleanScriptFieldType(
+        String name,
+        BooleanFieldScript.Factory scriptFactory,
+        Script script,
+        Map<String, String> meta,
+        OnScriptError onScriptError
+    ) {
         super(
             name,
-            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup),
+            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, onScriptError),
             script,
             scriptFactory.isResultDeterministic(),
             meta

+ 14 - 6
server/src/main/java/org/elasticsearch/index/mapper/CompositeRuntimeField.java

@@ -47,6 +47,8 @@ public class CompositeRuntimeField implements RuntimeField {
             }
         });
 
+        private final FieldMapper.Parameter<String> onScriptError = FieldMapper.Parameter.onScriptErrorParam(m -> m.onScriptError, script);
+
         private final FieldMapper.Parameter<Map<String, Object>> fields = new FieldMapper.Parameter<Map<String, Object>>(
             "fields",
             false,
@@ -66,6 +68,7 @@ public class CompositeRuntimeField implements RuntimeField {
             List<FieldMapper.Parameter<?>> parameters = new ArrayList<>(super.getParameters());
             parameters.add(script);
             parameters.add(fields);
+            parameters.add(onScriptError);
             return Collections.unmodifiableList(parameters);
         }
 
@@ -73,7 +76,8 @@ public class CompositeRuntimeField implements RuntimeField {
         protected RuntimeField createChildRuntimeField(
             MappingParserContext parserContext,
             String parent,
-            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory
+            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory,
+            OnScriptError onScriptError
         ) {
             throw new IllegalArgumentException("Composite field [" + name + "] cannot be a child of composite field [" + parent + "]");
         }
@@ -81,11 +85,15 @@ public class CompositeRuntimeField implements RuntimeField {
         @Override
         protected RuntimeField createRuntimeField(MappingParserContext parserContext) {
             CompositeFieldScript.Factory factory = parserContext.scriptCompiler().compile(script.get(), CompositeFieldScript.CONTEXT);
-            Function<RuntimeField.Builder, RuntimeField> builder = b -> b.createChildRuntimeField(
-                parserContext,
-                name,
-                lookup -> factory.newFactory(name, script.get().getParams(), lookup)
-            );
+            Function<RuntimeField.Builder, RuntimeField> builder = b -> {
+                OnScriptError onScriptError = OnScriptError.fromString(this.onScriptError.get());
+                return b.createChildRuntimeField(
+                    parserContext,
+                    name,
+                    lookup -> factory.newFactory(name, script.get().getParams(), lookup, onScriptError),
+                    onScriptError
+                );
+            };
             Map<String, RuntimeField> runtimeFields = RuntimeField.parseRuntimeFields(
                 new HashMap<>(fields.getValue()),
                 parserContext,

+ 7 - 3
server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java

@@ -303,9 +303,13 @@ public final class DateFieldMapper extends FieldMapper {
             DateFieldScript.Factory factory = scriptCompiler.compile(script.get(), DateFieldScript.CONTEXT);
             return factory == null
                 ? null
-                : (lookup, ctx, doc, consumer) -> factory.newFactory(name, script.get().getParams(), lookup, buildFormatter())
-                    .newInstance(ctx)
-                    .runForDoc(doc, consumer::accept);
+                : (lookup, ctx, doc, consumer) -> factory.newFactory(
+                    name,
+                    script.get().getParams(),
+                    lookup,
+                    buildFormatter(),
+                    OnScriptError.FAIL
+                ).newInstance(ctx).runForDoc(doc, consumer::accept);
         }
 
         @Override

+ 14 - 7
server/src/main/java/org/elasticsearch/index/mapper/DateScriptFieldType.java

@@ -87,23 +87,29 @@ public class DateScriptFieldType extends AbstractScriptFieldType<DateFieldScript
             return Collections.unmodifiableList(parameters);
         }
 
-        @Override
         AbstractScriptFieldType<?> createFieldType(
             String name,
             DateFieldScript.Factory factory,
             Script script,
             Map<String, String> meta,
-            Version supportedVersion
+            Version supportedVersion,
+            OnScriptError onScriptError
         ) {
             String pattern = format.getValue() == null ? DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.pattern() : format.getValue();
             Locale locale = this.locale.getValue() == null ? Locale.ROOT : this.locale.getValue();
             DateFormatter dateTimeFormatter = DateFormatter.forPattern(pattern, supportedVersion).withLocale(locale);
-            return new DateScriptFieldType(name, factory, dateTimeFormatter, script, meta);
+            return new DateScriptFieldType(name, factory, dateTimeFormatter, script, meta, onScriptError);
         }
 
         @Override
-        AbstractScriptFieldType<?> createFieldType(String name, DateFieldScript.Factory factory, Script script, Map<String, String> meta) {
-            return createFieldType(name, factory, script, meta, Version.CURRENT);
+        AbstractScriptFieldType<?> createFieldType(
+            String name,
+            DateFieldScript.Factory factory,
+            Script script,
+            Map<String, String> meta,
+            OnScriptError onScriptError
+        ) {
+            return createFieldType(name, factory, script, meta, Version.CURRENT, onScriptError);
         }
 
         @Override
@@ -131,11 +137,12 @@ public class DateScriptFieldType extends AbstractScriptFieldType<DateFieldScript
         DateFieldScript.Factory scriptFactory,
         DateFormatter dateTimeFormatter,
         Script script,
-        Map<String, String> meta
+        Map<String, String> meta,
+        OnScriptError onScriptError
     ) {
         super(
             name,
-            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, dateTimeFormatter),
+            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, dateTimeFormatter, onScriptError),
             script,
             scriptFactory.isResultDeterministic(),
             meta

+ 11 - 4
server/src/main/java/org/elasticsearch/index/mapper/DoubleScriptFieldType.java

@@ -47,9 +47,10 @@ public final class DoubleScriptFieldType extends AbstractScriptFieldType<DoubleF
             String name,
             DoubleFieldScript.Factory factory,
             Script script,
-            Map<String, String> meta
+            Map<String, String> meta,
+            OnScriptError onScriptError
         ) {
-            return new DoubleScriptFieldType(name, factory, script, meta);
+            return new DoubleScriptFieldType(name, factory, script, meta, onScriptError);
         }
 
         @Override
@@ -67,10 +68,16 @@ public final class DoubleScriptFieldType extends AbstractScriptFieldType<DoubleF
         return new Builder(name).createRuntimeField(DoubleFieldScript.PARSE_FROM_SOURCE);
     }
 
-    DoubleScriptFieldType(String name, DoubleFieldScript.Factory scriptFactory, Script script, Map<String, String> meta) {
+    DoubleScriptFieldType(
+        String name,
+        DoubleFieldScript.Factory scriptFactory,
+        Script script,
+        Map<String, String> meta,
+        OnScriptError onScriptError
+    ) {
         super(
             name,
-            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup),
+            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, onScriptError),
             script,
             scriptFactory.isResultDeterministic(),
             meta

+ 1 - 1
server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java

@@ -143,7 +143,7 @@ public class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<GeoPoi
             GeoPointFieldScript.Factory factory = scriptCompiler.compile(this.script.get(), GeoPointFieldScript.CONTEXT);
             return factory == null
                 ? null
-                : (lookup, ctx, doc, consumer) -> factory.newFactory(name, script.get().getParams(), lookup)
+                : (lookup, ctx, doc, consumer) -> factory.newFactory(name, script.get().getParams(), lookup, OnScriptError.FAIL)
                     .newInstance(ctx)
                     .runForDoc(doc, consumer);
         }

+ 12 - 5
server/src/main/java/org/elasticsearch/index/mapper/GeoPointScriptFieldType.java

@@ -50,9 +50,10 @@ public final class GeoPointScriptFieldType extends AbstractScriptFieldType<GeoPo
             String name,
             GeoPointFieldScript.Factory factory,
             Script script,
-            Map<String, String> meta
+            Map<String, String> meta,
+            OnScriptError onScriptError
         ) {
-            return new GeoPointScriptFieldType(name, factory, getScript(), meta());
+            return new GeoPointScriptFieldType(name, factory, getScript(), meta(), onScriptError);
         }
 
         @Override
@@ -66,10 +67,16 @@ public final class GeoPointScriptFieldType extends AbstractScriptFieldType<GeoPo
         }
     });
 
-    GeoPointScriptFieldType(String name, GeoPointFieldScript.Factory scriptFactory, Script script, Map<String, String> meta) {
+    GeoPointScriptFieldType(
+        String name,
+        GeoPointFieldScript.Factory scriptFactory,
+        Script script,
+        Map<String, String> meta,
+        OnScriptError onScriptError
+    ) {
         super(
             name,
-            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup),
+            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, onScriptError),
             script,
             scriptFactory.isResultDeterministic(),
             meta
@@ -154,7 +161,7 @@ public final class GeoPointScriptFieldType extends AbstractScriptFieldType<GeoPo
     ) {
         return ctx -> {
             GeoPointFieldScript script = delegateLeafFactory.apply(ctx);
-            return new AbstractLongFieldScript(name, Map.of(), lookup, ctx) {
+            return new AbstractLongFieldScript(name, Map.of(), lookup, OnScriptError.FAIL, ctx) {
                 private int docId;
 
                 @Override

+ 1 - 1
server/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java

@@ -145,7 +145,7 @@ public class IpFieldMapper extends FieldMapper {
             IpFieldScript.Factory factory = scriptCompiler.compile(this.script.get(), IpFieldScript.CONTEXT);
             return factory == null
                 ? null
-                : (lookup, ctx, doc, consumer) -> factory.newFactory(name, script.get().getParams(), lookup)
+                : (lookup, ctx, doc, consumer) -> factory.newFactory(name, script.get().getParams(), lookup, OnScriptError.FAIL)
                     .newInstance(ctx)
                     .runForDoc(doc, consumer);
         }

+ 16 - 4
server/src/main/java/org/elasticsearch/index/mapper/IpScriptFieldType.java

@@ -45,8 +45,14 @@ public final class IpScriptFieldType extends AbstractScriptFieldType<IpFieldScri
 
     public static final RuntimeField.Parser PARSER = new RuntimeField.Parser(name -> new Builder<>(name, IpFieldScript.CONTEXT) {
         @Override
-        AbstractScriptFieldType<?> createFieldType(String name, IpFieldScript.Factory factory, Script script, Map<String, String> meta) {
-            return new IpScriptFieldType(name, factory, getScript(), meta());
+        AbstractScriptFieldType<?> createFieldType(
+            String name,
+            IpFieldScript.Factory factory,
+            Script script,
+            Map<String, String> meta,
+            OnScriptError onScriptError
+        ) {
+            return new IpScriptFieldType(name, factory, getScript(), meta(), onScriptError);
         }
 
         @Override
@@ -60,10 +66,16 @@ public final class IpScriptFieldType extends AbstractScriptFieldType<IpFieldScri
         }
     });
 
-    IpScriptFieldType(String name, IpFieldScript.Factory scriptFactory, Script script, Map<String, String> meta) {
+    IpScriptFieldType(
+        String name,
+        IpFieldScript.Factory scriptFactory,
+        Script script,
+        Map<String, String> meta,
+        OnScriptError onScriptError
+    ) {
         super(
             name,
-            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup),
+            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, onScriptError),
             script,
             scriptFactory.isResultDeterministic(),
             meta

+ 1 - 1
server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java

@@ -245,7 +245,7 @@ public final class KeywordFieldMapper extends FieldMapper {
             StringFieldScript.Factory scriptFactory = scriptCompiler.compile(script.get(), StringFieldScript.CONTEXT);
             return scriptFactory == null
                 ? null
-                : (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(name, script.get().getParams(), lookup)
+                : (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(name, script.get().getParams(), lookup, OnScriptError.FAIL)
                     .newInstance(ctx)
                     .runForDoc(doc, consumer);
         }

+ 11 - 4
server/src/main/java/org/elasticsearch/index/mapper/KeywordScriptFieldType.java

@@ -54,9 +54,10 @@ public final class KeywordScriptFieldType extends AbstractScriptFieldType<String
             String name,
             StringFieldScript.Factory factory,
             Script script,
-            Map<String, String> meta
+            Map<String, String> meta,
+            OnScriptError onScriptError
         ) {
-            return new KeywordScriptFieldType(name, factory, script, meta);
+            return new KeywordScriptFieldType(name, factory, script, meta, onScriptError);
         }
 
         @Override
@@ -74,10 +75,16 @@ public final class KeywordScriptFieldType extends AbstractScriptFieldType<String
         return new Builder(name).createRuntimeField(StringFieldScript.PARSE_FROM_SOURCE);
     }
 
-    public KeywordScriptFieldType(String name, StringFieldScript.Factory scriptFactory, Script script, Map<String, String> meta) {
+    public KeywordScriptFieldType(
+        String name,
+        StringFieldScript.Factory scriptFactory,
+        Script script,
+        Map<String, String> meta,
+        OnScriptError onScriptError
+    ) {
         super(
             name,
-            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup),
+            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, onScriptError),
             script,
             scriptFactory.isResultDeterministic(),
             meta

+ 16 - 4
server/src/main/java/org/elasticsearch/index/mapper/LongScriptFieldType.java

@@ -43,8 +43,14 @@ public final class LongScriptFieldType extends AbstractScriptFieldType<LongField
         }
 
         @Override
-        AbstractScriptFieldType<?> createFieldType(String name, LongFieldScript.Factory factory, Script script, Map<String, String> meta) {
-            return new LongScriptFieldType(name, factory, script, meta);
+        AbstractScriptFieldType<?> createFieldType(
+            String name,
+            LongFieldScript.Factory factory,
+            Script script,
+            Map<String, String> meta,
+            OnScriptError onScriptError
+        ) {
+            return new LongScriptFieldType(name, factory, script, meta, onScriptError);
         }
 
         @Override
@@ -62,10 +68,16 @@ public final class LongScriptFieldType extends AbstractScriptFieldType<LongField
         return new Builder(name).createRuntimeField(LongFieldScript.PARSE_FROM_SOURCE);
     }
 
-    public LongScriptFieldType(String name, LongFieldScript.Factory scriptFactory, Script script, Map<String, String> meta) {
+    public LongScriptFieldType(
+        String name,
+        LongFieldScript.Factory scriptFactory,
+        Script script,
+        Map<String, String> meta,
+        OnScriptError onScriptError
+    ) {
         super(
             name,
-            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup),
+            searchLookup -> scriptFactory.newFactory(name, script.getParams(), searchLookup, onScriptError),
             script,
             scriptFactory.isResultDeterministic(),
             meta

+ 2 - 1
server/src/main/java/org/elasticsearch/index/mapper/LookupRuntimeFieldType.java

@@ -159,7 +159,8 @@ public final class LookupRuntimeFieldType extends MappedFieldType {
         protected RuntimeField createChildRuntimeField(
             MappingParserContext parserContext,
             String parentName,
-            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory
+            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory,
+            OnScriptError onScriptError
         ) {
             return createRuntimeField(parserContext);
         }

+ 2 - 2
server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

@@ -592,7 +592,7 @@ public class NumberFieldMapper extends FieldMapper {
             @Override
             public FieldValues<Number> compile(String fieldName, Script script, ScriptCompiler compiler) {
                 DoubleFieldScript.Factory scriptFactory = compiler.compile(script, DoubleFieldScript.CONTEXT);
-                return (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(fieldName, script.getParams(), lookup)
+                return (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(fieldName, script.getParams(), lookup, OnScriptError.FAIL)
                     .newInstance(ctx)
                     .runForDoc(doc, consumer::accept);
             }
@@ -1055,7 +1055,7 @@ public class NumberFieldMapper extends FieldMapper {
             @Override
             public FieldValues<Number> compile(String fieldName, Script script, ScriptCompiler compiler) {
                 final LongFieldScript.Factory scriptFactory = compiler.compile(script, LongFieldScript.CONTEXT);
-                return (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(fieldName, script.getParams(), lookup)
+                return (lookup, ctx, doc, consumer) -> scriptFactory.newFactory(fieldName, script.getParams(), lookup, OnScriptError.FAIL)
                     .newInstance(ctx)
                     .runForDoc(doc, consumer::accept);
             }

+ 33 - 0
server/src/main/java/org/elasticsearch/index/mapper/OnScriptError.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.index.mapper;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Represents the behaviour when a runtime field or an index-time script fails: either fail and raise the error,
+ * or continue and ignore the error.
+ */
+public enum OnScriptError {
+    FAIL,
+    CONTINUE;
+
+    /**
+     * Parses the on_script_error parameter from a string into its corresponding enum instance
+     */
+    public static OnScriptError fromString(final String str) {
+        Objects.requireNonNull(str, "input string is null");
+        return switch (str.toLowerCase(Locale.ROOT)) {
+            case "fail" -> FAIL;
+            case "continue" -> CONTINUE;
+            default -> throw new IllegalArgumentException("Unknown onScriptError [" + str + "]");
+        };
+    }
+}

+ 2 - 1
server/src/main/java/org/elasticsearch/index/mapper/RuntimeField.java

@@ -63,7 +63,8 @@ public interface RuntimeField extends ToXContentFragment {
         protected abstract RuntimeField createChildRuntimeField(
             MappingParserContext parserContext,
             String parentName,
-            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory
+            Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory,
+            OnScriptError onScriptError
         );
 
         public final void parse(String name, MappingParserContext parserContext, Map<String, Object> fieldNode) {

+ 20 - 4
server/src/main/java/org/elasticsearch/script/AbstractFieldScript.java

@@ -10,6 +10,7 @@ package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 import org.elasticsearch.search.lookup.SourceLookup;
 
@@ -69,10 +70,16 @@ public abstract class AbstractFieldScript extends DocBasedScript {
     protected final String fieldName;
     protected final SourceLookup sourceLookup;
     private final Map<String, Object> params;
-
-    public AbstractFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
+    private final OnScriptError onScriptError;
+
+    public AbstractFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        LeafReaderContext ctx,
+        OnScriptError onScriptError
+    ) {
         super(new DocValuesDocReader(searchLookup, ctx));
-
         this.fieldName = fieldName;
         Map<String, Object> docAsMap = docAsMap();
         this.sourceLookup = (SourceLookup) docAsMap.get("_source");
@@ -80,6 +87,7 @@ public abstract class AbstractFieldScript extends DocBasedScript {
         params.put("_source", sourceLookup);
         params.put("_fields", docAsMap.get("_fields"));
         this.params = new DynamicMap(params, PARAMS_FUNCTIONS);
+        this.onScriptError = onScriptError;
     }
 
     /**
@@ -141,7 +149,15 @@ public abstract class AbstractFieldScript extends DocBasedScript {
     public final void runForDoc(int docId) {
         prepareExecute();
         setDocument(docId);
-        execute();
+        try {
+            execute();
+        } catch (Exception e) {
+            if (onScriptError == OnScriptError.CONTINUE) {
+                // ignore
+            } else {
+                throw e;
+            }
+        }
     }
 
     public abstract void execute();

+ 9 - 2
server/src/main/java/org/elasticsearch/script/AbstractLongFieldScript.java

@@ -10,6 +10,7 @@ package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.util.ArrayUtil;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.Map;
@@ -22,8 +23,14 @@ public abstract class AbstractLongFieldScript extends AbstractFieldScript {
     private long[] values = new long[1];
     private int count;
 
-    public AbstractLongFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public AbstractLongFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, ctx, onScriptError);
     }
 
     @Override

+ 14 - 7
server/src/main/java/org/elasticsearch/script/BooleanFieldScript.java

@@ -10,6 +10,7 @@ package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.elasticsearch.core.Booleans;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.Map;
@@ -22,8 +23,8 @@ public abstract class BooleanFieldScript extends AbstractFieldScript {
 
     public static final Factory PARSE_FROM_SOURCE = new Factory() {
         @Override
-        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup) {
-            return ctx -> new BooleanFieldScript(field, params, lookup, ctx) {
+        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup, OnScriptError onScriptError) {
+            return ctx -> new BooleanFieldScript(field, params, lookup, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emitFromSource();
@@ -38,11 +39,11 @@ public abstract class BooleanFieldScript extends AbstractFieldScript {
     };
 
     public static Factory leafAdapter(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentFactory) {
-        return (leafFieldName, params, searchLookup) -> {
+        return (leafFieldName, params, searchLookup, onScriptError) -> {
             CompositeFieldScript.LeafFactory parentLeafFactory = parentFactory.apply(searchLookup);
             return (LeafFactory) ctx -> {
                 CompositeFieldScript compositeFieldScript = parentLeafFactory.newInstance(ctx);
-                return new BooleanFieldScript(leafFieldName, params, searchLookup, ctx) {
+                return new BooleanFieldScript(leafFieldName, params, searchLookup, onScriptError, ctx) {
                     @Override
                     public void setDocument(int docId) {
                         compositeFieldScript.setDocument(docId);
@@ -61,7 +62,7 @@ public abstract class BooleanFieldScript extends AbstractFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup);
+        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup, OnScriptError onScriptError);
     }
 
     public interface LeafFactory {
@@ -71,8 +72,14 @@ public abstract class BooleanFieldScript extends AbstractFieldScript {
     private int trues;
     private int falses;
 
-    public BooleanFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public BooleanFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, ctx, onScriptError);
     }
 
     @Override

+ 15 - 3
server/src/main/java/org/elasticsearch/script/CompositeFieldScript.java

@@ -9,6 +9,7 @@
 package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.ArrayList;
@@ -27,7 +28,12 @@ public abstract class CompositeFieldScript extends AbstractFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        CompositeFieldScript.LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup);
+        CompositeFieldScript.LeafFactory newFactory(
+            String fieldName,
+            Map<String, Object> params,
+            SearchLookup searchLookup,
+            OnScriptError onScriptError
+        );
     }
 
     public interface LeafFactory {
@@ -36,8 +42,14 @@ public abstract class CompositeFieldScript extends AbstractFieldScript {
 
     private final Map<String, List<Object>> fieldValues = new HashMap<>();
 
-    public CompositeFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public CompositeFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, ctx, onScriptError);
     }
 
     /**

+ 20 - 6
server/src/main/java/org/elasticsearch/script/DateFieldScript.java

@@ -10,6 +10,7 @@ package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.elasticsearch.common.time.DateFormatter;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.Map;
@@ -21,8 +22,14 @@ public abstract class DateFieldScript extends AbstractLongFieldScript {
 
     public static final Factory PARSE_FROM_SOURCE = new Factory() {
         @Override
-        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup, DateFormatter formatter) {
-            return ctx -> new DateFieldScript(field, params, lookup, formatter, ctx) {
+        public LeafFactory newFactory(
+            String field,
+            Map<String, Object> params,
+            SearchLookup lookup,
+            DateFormatter formatter,
+            OnScriptError onScriptError
+        ) {
+            return ctx -> new DateFieldScript(field, params, lookup, formatter, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emitFromSource();
@@ -37,11 +44,11 @@ public abstract class DateFieldScript extends AbstractLongFieldScript {
     };
 
     public static Factory leafAdapter(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentFactory) {
-        return (leafFieldName, params, searchLookup, formatter) -> {
+        return (leafFieldName, params, searchLookup, formatter, onScriptError) -> {
             CompositeFieldScript.LeafFactory parentLeafFactory = parentFactory.apply(searchLookup);
             return (LeafFactory) ctx -> {
                 CompositeFieldScript compositeFieldScript = parentLeafFactory.newInstance(ctx);
-                return new DateFieldScript(leafFieldName, params, searchLookup, formatter, ctx) {
+                return new DateFieldScript(leafFieldName, params, searchLookup, formatter, onScriptError, ctx) {
                     @Override
                     public void setDocument(int docId) {
                         compositeFieldScript.setDocument(docId);
@@ -60,7 +67,13 @@ public abstract class DateFieldScript extends AbstractLongFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup, DateFormatter formatter);
+        LeafFactory newFactory(
+            String fieldName,
+            Map<String, Object> params,
+            SearchLookup searchLookup,
+            DateFormatter formatter,
+            OnScriptError onScriptError
+        );
     }
 
     public interface LeafFactory {
@@ -74,9 +87,10 @@ public abstract class DateFieldScript extends AbstractLongFieldScript {
         Map<String, Object> params,
         SearchLookup searchLookup,
         DateFormatter formatter,
+        OnScriptError onScriptError,
         LeafReaderContext ctx
     ) {
-        super(fieldName, params, searchLookup, ctx);
+        super(fieldName, params, searchLookup, onScriptError, ctx);
         this.formatter = formatter;
     }
 

+ 14 - 7
server/src/main/java/org/elasticsearch/script/DoubleFieldScript.java

@@ -10,6 +10,7 @@ package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.util.ArrayUtil;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.Map;
@@ -21,8 +22,8 @@ public abstract class DoubleFieldScript extends AbstractFieldScript {
 
     public static final Factory PARSE_FROM_SOURCE = new Factory() {
         @Override
-        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup) {
-            return ctx -> new DoubleFieldScript(field, params, lookup, ctx) {
+        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup, OnScriptError onScriptError) {
+            return ctx -> new DoubleFieldScript(field, params, lookup, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emitFromSource();
@@ -37,11 +38,11 @@ public abstract class DoubleFieldScript extends AbstractFieldScript {
     };
 
     public static Factory leafAdapter(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentFactory) {
-        return (leafFieldName, params, searchLookup) -> {
+        return (leafFieldName, params, searchLookup, onScriptError) -> {
             CompositeFieldScript.LeafFactory parentLeafFactory = parentFactory.apply(searchLookup);
             return (LeafFactory) ctx -> {
                 CompositeFieldScript compositeFieldScript = parentLeafFactory.newInstance(ctx);
-                return new DoubleFieldScript(leafFieldName, params, searchLookup, ctx) {
+                return new DoubleFieldScript(leafFieldName, params, searchLookup, onScriptError, ctx) {
                     @Override
                     public void setDocument(int docId) {
                         compositeFieldScript.setDocument(docId);
@@ -60,7 +61,7 @@ public abstract class DoubleFieldScript extends AbstractFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup);
+        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup, OnScriptError onScriptError);
     }
 
     public interface LeafFactory {
@@ -70,8 +71,14 @@ public abstract class DoubleFieldScript extends AbstractFieldScript {
     private double[] values = new double[1];
     private int count;
 
-    public DoubleFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public DoubleFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, ctx, onScriptError);
     }
 
     @Override

+ 14 - 7
server/src/main/java/org/elasticsearch/script/GeoPointFieldScript.java

@@ -14,6 +14,7 @@ import org.apache.lucene.util.ArrayUtil;
 import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.geo.GeoUtils;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.Collections;
@@ -31,8 +32,8 @@ public abstract class GeoPointFieldScript extends AbstractFieldScript {
 
     public static final Factory PARSE_FROM_SOURCE = new Factory() {
         @Override
-        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup) {
-            return ctx -> new GeoPointFieldScript(field, params, lookup, ctx) {
+        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup, OnScriptError onScriptError) {
+            return ctx -> new GeoPointFieldScript(field, params, lookup, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emitFromSource();
@@ -47,11 +48,11 @@ public abstract class GeoPointFieldScript extends AbstractFieldScript {
     };
 
     public static Factory leafAdapter(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentFactory) {
-        return (leafFieldName, params, searchLookup) -> {
+        return (leafFieldName, params, searchLookup, onScriptError) -> {
             CompositeFieldScript.LeafFactory parentLeafFactory = parentFactory.apply(searchLookup);
             return (LeafFactory) ctx -> {
                 CompositeFieldScript compositeFieldScript = parentLeafFactory.newInstance(ctx);
-                return new GeoPointFieldScript(leafFieldName, params, searchLookup, ctx) {
+                return new GeoPointFieldScript(leafFieldName, params, searchLookup, onScriptError, ctx) {
                     @Override
                     public void setDocument(int docId) {
                         compositeFieldScript.setDocument(docId);
@@ -70,7 +71,7 @@ public abstract class GeoPointFieldScript extends AbstractFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup);
+        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup, OnScriptError onScriptError);
     }
 
     public interface LeafFactory {
@@ -81,8 +82,14 @@ public abstract class GeoPointFieldScript extends AbstractFieldScript {
     private double[] lons = new double[1];
     private int count;
 
-    public GeoPointFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public GeoPointFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, ctx, onScriptError);
     }
 
     @Override

+ 14 - 7
server/src/main/java/org/elasticsearch/script/IpFieldScript.java

@@ -14,6 +14,7 @@ import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.common.network.InetAddresses;
 import org.elasticsearch.index.mapper.IpFieldMapper;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.net.Inet4Address;
@@ -42,8 +43,8 @@ public abstract class IpFieldScript extends AbstractFieldScript {
 
     public static final Factory PARSE_FROM_SOURCE = new Factory() {
         @Override
-        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup) {
-            return ctx -> new IpFieldScript(field, params, lookup, ctx) {
+        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup, OnScriptError onScriptError) {
+            return ctx -> new IpFieldScript(field, params, lookup, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emitFromSource();
@@ -58,11 +59,11 @@ public abstract class IpFieldScript extends AbstractFieldScript {
     };
 
     public static Factory leafAdapter(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentFactory) {
-        return (leafFieldName, params, searchLookup) -> {
+        return (leafFieldName, params, searchLookup, onScriptError) -> {
             CompositeFieldScript.LeafFactory parentLeafFactory = parentFactory.apply(searchLookup);
             return (LeafFactory) ctx -> {
                 CompositeFieldScript compositeFieldScript = parentLeafFactory.newInstance(ctx);
-                return new IpFieldScript(leafFieldName, params, searchLookup, ctx) {
+                return new IpFieldScript(leafFieldName, params, searchLookup, onScriptError, ctx) {
                     @Override
                     public void setDocument(int docId) {
                         compositeFieldScript.setDocument(docId);
@@ -81,7 +82,7 @@ public abstract class IpFieldScript extends AbstractFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup);
+        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup, OnScriptError onScriptError);
     }
 
     public interface LeafFactory {
@@ -91,8 +92,14 @@ public abstract class IpFieldScript extends AbstractFieldScript {
     private BytesRef[] values = new BytesRef[1];
     private int count;
 
-    public IpFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public IpFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, ctx, onScriptError);
     }
 
     @Override

+ 14 - 7
server/src/main/java/org/elasticsearch/script/LongFieldScript.java

@@ -10,6 +10,7 @@ package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.elasticsearch.index.mapper.NumberFieldMapper;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.Map;
@@ -20,8 +21,8 @@ public abstract class LongFieldScript extends AbstractLongFieldScript {
 
     public static final Factory PARSE_FROM_SOURCE = new Factory() {
         @Override
-        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup) {
-            return ctx -> new LongFieldScript(field, params, lookup, ctx) {
+        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup, OnScriptError onScriptError) {
+            return ctx -> new LongFieldScript(field, params, lookup, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emitFromSource();
@@ -36,11 +37,11 @@ public abstract class LongFieldScript extends AbstractLongFieldScript {
     };
 
     public static Factory leafAdapter(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentFactory) {
-        return (leafFieldName, params, searchLookup) -> {
+        return (leafFieldName, params, searchLookup, onScriptError) -> {
             CompositeFieldScript.LeafFactory parentLeafFactory = parentFactory.apply(searchLookup);
             return (LeafFactory) ctx -> {
                 CompositeFieldScript compositeFieldScript = parentLeafFactory.newInstance(ctx);
-                return new LongFieldScript(leafFieldName, params, searchLookup, ctx) {
+                return new LongFieldScript(leafFieldName, params, searchLookup, onScriptError, ctx) {
                     @Override
                     public void setDocument(int docId) {
                         compositeFieldScript.setDocument(docId);
@@ -59,15 +60,21 @@ public abstract class LongFieldScript extends AbstractLongFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup);
+        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup, OnScriptError onScriptError);
     }
 
     public interface LeafFactory {
         LongFieldScript newInstance(LeafReaderContext ctx);
     }
 
-    public LongFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public LongFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, onScriptError, ctx);
     }
 
     @Override

+ 2 - 1
server/src/main/java/org/elasticsearch/script/SortedNumericDocValuesLongFieldScript.java

@@ -10,6 +10,7 @@ package org.elasticsearch.script;
 
 import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.LeafReaderContext;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.script.field.LongDocValuesField;
 import org.elasticsearch.search.lookup.SearchLookup;
 
@@ -21,7 +22,7 @@ public class SortedNumericDocValuesLongFieldScript extends AbstractLongFieldScri
     final LongDocValuesField longDocValuesField;
 
     public SortedNumericDocValuesLongFieldScript(String fieldName, SearchLookup lookup, LeafReaderContext ctx) {
-        super(fieldName, Map.of(), lookup, ctx);
+        super(fieldName, Map.of(), lookup, OnScriptError.FAIL, ctx);
         try {
             longDocValuesField = new LongDocValuesField(DocValues.getSortedNumeric(ctx.reader(), fieldName), fieldName);
         } catch (IOException e) {

+ 2 - 1
server/src/main/java/org/elasticsearch/script/SortedSetDocValuesStringFieldScript.java

@@ -12,6 +12,7 @@ import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.SortedSetDocValues;
 import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.io.IOException;
@@ -23,7 +24,7 @@ public class SortedSetDocValuesStringFieldScript extends StringFieldScript {
     boolean hasValue = false;
 
     public SortedSetDocValuesStringFieldScript(String fieldName, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, Map.of(), searchLookup, ctx);
+        super(fieldName, Map.of(), searchLookup, OnScriptError.FAIL, ctx);
         try {
             sortedSetDocValues = DocValues.getSortedSet(ctx.reader(), fieldName);
         } catch (IOException e) {

+ 14 - 7
server/src/main/java/org/elasticsearch/script/StringFieldScript.java

@@ -9,6 +9,7 @@
 package org.elasticsearch.script;
 
 import org.apache.lucene.index.LeafReaderContext;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 
 import java.util.ArrayList;
@@ -28,8 +29,8 @@ public abstract class StringFieldScript extends AbstractFieldScript {
 
     public static final StringFieldScript.Factory PARSE_FROM_SOURCE = new Factory() {
         @Override
-        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup) {
-            return ctx -> new StringFieldScript(field, params, lookup, ctx) {
+        public LeafFactory newFactory(String field, Map<String, Object> params, SearchLookup lookup, OnScriptError onScriptError) {
+            return ctx -> new StringFieldScript(field, params, lookup, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emitFromSource();
@@ -44,11 +45,11 @@ public abstract class StringFieldScript extends AbstractFieldScript {
     };
 
     public static Factory leafAdapter(Function<SearchLookup, CompositeFieldScript.LeafFactory> parentFactory) {
-        return (leafFieldName, params, searchLookup) -> {
+        return (leafFieldName, params, searchLookup, onScriptError) -> {
             CompositeFieldScript.LeafFactory parentLeafFactory = parentFactory.apply(searchLookup);
             return (LeafFactory) ctx -> {
                 CompositeFieldScript compositeFieldScript = parentLeafFactory.newInstance(ctx);
-                return new StringFieldScript(leafFieldName, params, searchLookup, ctx) {
+                return new StringFieldScript(leafFieldName, params, searchLookup, onScriptError, ctx) {
                     @Override
                     public void setDocument(int docId) {
                         compositeFieldScript.setDocument(docId);
@@ -67,7 +68,7 @@ public abstract class StringFieldScript extends AbstractFieldScript {
     public static final String[] PARAMETERS = {};
 
     public interface Factory extends ScriptFactory {
-        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup);
+        LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup, OnScriptError onScriptError);
     }
 
     public interface LeafFactory {
@@ -77,8 +78,14 @@ public abstract class StringFieldScript extends AbstractFieldScript {
     private final List<String> results = new ArrayList<>();
     private long chars;
 
-    public StringFieldScript(String fieldName, Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
-        super(fieldName, params, searchLookup, ctx);
+    public StringFieldScript(
+        String fieldName,
+        Map<String, Object> params,
+        SearchLookup searchLookup,
+        OnScriptError onScriptError,
+        LeafReaderContext ctx
+    ) {
+        super(fieldName, params, searchLookup, ctx, onScriptError);
     }
 
     @Override

+ 7 - 1
server/src/test/java/org/elasticsearch/index/TimeSeriesModeTests.java

@@ -12,6 +12,7 @@ import org.elasticsearch.cluster.metadata.IndexMetadata;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.time.DateUtils;
 import org.elasticsearch.index.mapper.MapperServiceTestCase;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.script.Script;
 import org.elasticsearch.script.ScriptContext;
 import org.elasticsearch.script.StringFieldScript;
@@ -264,7 +265,12 @@ public class TimeSeriesModeTests extends MapperServiceTestCase {
         if (context.equals(StringFieldScript.CONTEXT) && script.getLang().equals("mock")) {
             return (T) new StringFieldScript.Factory() {
                 @Override
-                public LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup) {
+                public LeafFactory newFactory(
+                    String fieldName,
+                    Map<String, Object> params,
+                    SearchLookup searchLookup,
+                    OnScriptError onScriptError
+                ) {
                     throw new UnsupportedOperationException("error should be thrown before getting here");
                 }
             };

+ 48 - 0
server/src/test/java/org/elasticsearch/index/mapper/AbstractScriptFieldTypeTestCase.java

@@ -8,9 +8,14 @@
 
 package org.elasticsearch.index.mapper;
 
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.tests.index.RandomIndexWriter;
+import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesReference;
@@ -18,6 +23,7 @@ import org.elasticsearch.common.geo.ShapeRelation;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.index.fielddata.FieldDataContext;
 import org.elasticsearch.index.fielddata.IndexFieldDataCache;
+import org.elasticsearch.index.query.ExistsQueryBuilder;
 import org.elasticsearch.index.query.SearchExecutionContext;
 import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
 import org.elasticsearch.script.BooleanFieldScript;
@@ -38,7 +44,9 @@ import org.elasticsearch.xcontent.json.JsonXContent;
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.BiConsumer;
 
 import static org.hamcrest.Matchers.containsString;
@@ -157,6 +165,45 @@ public abstract class AbstractScriptFieldTypeTestCase extends MapperServiceTestC
         assertEquals(concreteIndexType.isAggregatable(), scriptFieldType.isAggregatable());
     }
 
+    /**
+     * Check that running query on a runtime field script that fails has the expected behaviour according to its configuration
+     */
+    public final void testOnScriptError() throws IOException {
+        try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
+            iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [1]}"))));
+            try (DirectoryReader reader = iw.getReader()) {
+                IndexSearcher searcher = newUnthreadedSearcher(reader);
+                {
+                    AbstractScriptFieldType<?> fieldType = build("error", Collections.emptyMap(), OnScriptError.CONTINUE);
+                    SearchExecutionContext searchExecutionContext = mockContext(true, fieldType);
+                    Query query = new ExistsQueryBuilder("test").rewrite(searchExecutionContext).toQuery(searchExecutionContext);
+                    assertEquals(0, searcher.count(query));
+                }
+                {
+                    AbstractScriptFieldType<?> fieldType = build("error", Collections.emptyMap(), OnScriptError.FAIL);
+                    SearchExecutionContext searchExecutionContext = mockContext(true, fieldType);
+                    Query query = new ExistsQueryBuilder("test").rewrite(searchExecutionContext).toQuery(searchExecutionContext);
+                    expectThrows(RuntimeException.class, () -> searcher.count(query));
+                }
+            }
+        }
+    }
+
+    public final void testOnScriptErrorFail() throws IOException {
+        try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
+            iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [1]}"))));
+            try (DirectoryReader reader = iw.getReader()) {
+                IndexSearcher searcher = newUnthreadedSearcher(reader);
+                AbstractScriptFieldType<?> fieldType = build("error", Collections.emptyMap(), OnScriptError.FAIL);
+                SearchExecutionContext searchExecutionContext = mockContext(true, fieldType);
+                Query query = new ExistsQueryBuilder("test").rewrite(searchExecutionContext).toQuery(searchExecutionContext);
+                expectThrows(RuntimeException.class, () -> searcher.count(query));
+            }
+        }
+    }
+
+    protected abstract AbstractScriptFieldType<?> build(String error, Map<String, Object> emptyMap, OnScriptError onScriptError);
+
     @SuppressWarnings("unused")
     public abstract void testDocValues() throws IOException;
 
@@ -237,6 +284,7 @@ public abstract class AbstractScriptFieldTypeTestCase extends MapperServiceTestC
             return ft.fielddataBuilder(new FieldDataContext("test", context::lookup, context::sourcePath, fdo))
                 .build(new IndexFieldDataCache.None(), new NoneCircuitBreakerService());
         });
+        when(context.getMatchingFieldNames(any())).thenReturn(Set.of("dummy_field"));
         return context;
     }
 

+ 14 - 2
server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldMapperTests.java

@@ -258,7 +258,13 @@ public class BooleanFieldMapperTests extends MapperTestCase {
         return new IngestScriptSupport() {
             @Override
             protected BooleanFieldScript.Factory emptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new BooleanFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new BooleanFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {}
                 };
@@ -266,7 +272,13 @@ public class BooleanFieldMapperTests extends MapperTestCase {
 
             @Override
             protected BooleanFieldScript.Factory nonEmptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new BooleanFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new BooleanFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         emit(true);

+ 3 - 1
server/src/test/java/org/elasticsearch/index/mapper/BooleanFieldScriptTests.java

@@ -24,10 +24,11 @@ import java.util.List;
 import java.util.Map;
 
 public class BooleanFieldScriptTests extends FieldScriptTestCase<BooleanFieldScript.Factory> {
-    public static final BooleanFieldScript.Factory DUMMY = (fieldName, params, lookup) -> ctx -> new BooleanFieldScript(
+    public static final BooleanFieldScript.Factory DUMMY = (fieldName, params, lookup, onScriptError) -> ctx -> new BooleanFieldScript(
         fieldName,
         params,
         lookup,
+        OnScriptError.FAIL,
         ctx
     ) {
         @Override
@@ -59,6 +60,7 @@ public class BooleanFieldScriptTests extends FieldScriptTestCase<BooleanFieldScr
                     "test",
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override

+ 40 - 13
server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptFieldTypeTests.java

@@ -253,7 +253,10 @@ public class BooleanScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeT
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(true, mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery("true", mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(false, mockContext())), equalTo(0));
-                assertThat(searcher.count(build("xor_param", Map.of("param", false)).termQuery(true, mockContext())), equalTo(1));
+                assertThat(
+                    searcher.count(build("xor_param", Map.of("param", false), OnScriptError.FAIL).termQuery(true, mockContext())),
+                    equalTo(1)
+                );
             }
         }
         try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
@@ -264,7 +267,10 @@ public class BooleanScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeT
                 assertThat(searcher.count(simpleMappedFieldType().termQuery("false", mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(null, mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(true, mockContext())), equalTo(0));
-                assertThat(searcher.count(build("xor_param", Map.of("param", false)).termQuery(false, mockContext())), equalTo(1));
+                assertThat(
+                    searcher.count(build("xor_param", Map.of("param", false), OnScriptError.FAIL).termQuery(false, mockContext())),
+                    equalTo(1)
+                );
             }
         }
     }
@@ -400,12 +406,12 @@ public class BooleanScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeT
 
     @Override
     protected BooleanScriptFieldType simpleMappedFieldType() {
-        return build("read_foo", Map.of());
+        return build("read_foo", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
     protected MappedFieldType loopFieldType() {
-        return build("loop", Map.of());
+        return build("loop", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
@@ -413,13 +419,20 @@ public class BooleanScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeT
         return "boolean";
     }
 
-    private static BooleanScriptFieldType build(String code, Map<String, Object> params) {
-        return build(new Script(ScriptType.INLINE, "test", code, params));
+    protected BooleanScriptFieldType build(String code, Map<String, Object> params, OnScriptError onScriptError) {
+        Script script = new Script(ScriptType.INLINE, "test", code, params);
+        return new BooleanScriptFieldType("test", factory(script), script, emptyMap(), onScriptError);
     }
 
     private static BooleanFieldScript.Factory factory(Script script) {
         return switch (script.getIdOrCode()) {
-            case "read_foo" -> (fieldName, params, lookup) -> (ctx) -> new BooleanFieldScript(fieldName, params, lookup, ctx) {
+            case "read_foo" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new BooleanFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -427,7 +440,13 @@ public class BooleanScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeT
                     }
                 }
             };
-            case "xor_param" -> (fieldName, params, lookup) -> (ctx) -> new BooleanFieldScript(fieldName, params, lookup, ctx) {
+            case "xor_param" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new BooleanFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -435,16 +454,24 @@ public class BooleanScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeT
                     }
                 }
             };
-            case "loop" -> (fieldName, params, lookup) -> {
+            case "loop" -> (fieldName, params, lookup, onScriptError) -> {
                 // Indicate that this script wants the field call "test", which *is* the name of this field
                 lookup.forkAndTrackFieldReferences("test");
                 throw new IllegalStateException("should have thrown on the line above");
             };
+            case "error" -> (fieldName, params, lookup, onScriptError) -> ctx -> new BooleanFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
+                @Override
+                public void execute() {
+                    throw new RuntimeException("test error");
+                }
+            };
             default -> throw new IllegalArgumentException("unsupported script [" + script.getIdOrCode() + "]");
         };
     }
-
-    private static BooleanScriptFieldType build(Script script) {
-        return new BooleanScriptFieldType("test", factory(script), script, emptyMap());
-    }
 }

+ 7 - 2
server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptMapperTests.java

@@ -21,11 +21,16 @@ public class BooleanScriptMapperTests extends MapperScriptTestCase<BooleanFieldS
     private static BooleanFieldScript.Factory factory(Consumer<BooleanFieldScript> executor) {
         return new BooleanFieldScript.Factory() {
             @Override
-            public BooleanFieldScript.LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup) {
+            public BooleanFieldScript.LeafFactory newFactory(
+                String fieldName,
+                Map<String, Object> params,
+                SearchLookup searchLookup,
+                OnScriptError onScriptError
+            ) {
                 return new BooleanFieldScript.LeafFactory() {
                     @Override
                     public BooleanFieldScript newInstance(LeafReaderContext ctx) {
-                        return new BooleanFieldScript(fieldName, params, searchLookup, ctx) {
+                        return new BooleanFieldScript(fieldName, params, searchLookup, OnScriptError.FAIL, ctx) {
                             @Override
                             public void execute() {
                                 executor.accept(this);

+ 9 - 2
server/src/test/java/org/elasticsearch/index/mapper/CompositeRuntimeFieldTests.java

@@ -33,10 +33,11 @@ public class CompositeRuntimeFieldTests extends MapperServiceTestCase {
     @SuppressWarnings("unchecked")
     protected <T> T compileScript(Script script, ScriptContext<T> context) {
         if (context == CompositeFieldScript.CONTEXT) {
-            return (T) (CompositeFieldScript.Factory) (fieldName, params, searchLookup) -> ctx -> new CompositeFieldScript(
+            return (T) (CompositeFieldScript.Factory) (fieldName, params, searchLookup, onScriptError) -> ctx -> new CompositeFieldScript(
                 fieldName,
                 params,
                 searchLookup,
+                OnScriptError.FAIL,
                 ctx
             ) {
                 @Override
@@ -52,7 +53,13 @@ public class CompositeRuntimeFieldTests extends MapperServiceTestCase {
             };
         }
         if (context == LongFieldScript.CONTEXT) {
-            return (T) (LongFieldScript.Factory) (field, params, lookup) -> ctx -> new LongFieldScript(field, params, lookup, ctx) {
+            return (T) (LongFieldScript.Factory) (field, params, lookup, onScriptError) -> ctx -> new LongFieldScript(
+                field,
+                params,
+                lookup,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
 

+ 4 - 2
server/src/test/java/org/elasticsearch/index/mapper/DateFieldMapperTests.java

@@ -669,11 +669,12 @@ public class DateFieldMapperTests extends MapperTestCase {
         return new IngestScriptSupport() {
             @Override
             protected DateFieldScript.Factory emptyFieldScript() {
-                return (fieldName, params, searchLookup, formatter) -> ctx -> new DateFieldScript(
+                return (fieldName, params, searchLookup, formatter, onScriptError) -> ctx -> new DateFieldScript(
                     fieldName,
                     params,
                     searchLookup,
                     formatter,
+                    OnScriptError.FAIL,
                     ctx
                 ) {
                     @Override
@@ -683,11 +684,12 @@ public class DateFieldMapperTests extends MapperTestCase {
 
             @Override
             protected DateFieldScript.Factory nonEmptyFieldScript() {
-                return (fieldName, params, searchLookup, formatter) -> ctx -> new DateFieldScript(
+                return (fieldName, params, searchLookup, formatter, onScriptError) -> ctx -> new DateFieldScript(
                     fieldName,
                     params,
                     searchLookup,
                     formatter,
+                    OnScriptError.FAIL,
                     ctx
                 ) {
                     @Override

+ 5 - 2
server/src/test/java/org/elasticsearch/index/mapper/DateFieldScriptTests.java

@@ -32,11 +32,12 @@ import java.util.Map;
 import static org.hamcrest.Matchers.equalTo;
 
 public class DateFieldScriptTests extends FieldScriptTestCase<DateFieldScript.Factory> {
-    public static final DateFieldScript.Factory DUMMY = (fieldName, params, lookup, formatter) -> ctx -> new DateFieldScript(
+    public static final DateFieldScript.Factory DUMMY = (fieldName, params, lookup, formatter, onScriptError) -> ctx -> new DateFieldScript(
         fieldName,
         params,
         lookup,
         formatter,
+        OnScriptError.FAIL,
         ctx
     ) {
         @Override
@@ -69,6 +70,7 @@ public class DateFieldScriptTests extends FieldScriptTestCase<DateFieldScript.Fa
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
                     DateFormatter.forPattern(randomDateFormatterPattern()).withLocale(randomLocale(random())),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -104,7 +106,8 @@ public class DateFieldScriptTests extends FieldScriptTestCase<DateFieldScript.Fa
                     "field",
                     Collections.emptyMap(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
-                    DateFormatter.forPattern("epoch_millis")
+                    DateFormatter.forPattern("epoch_millis"),
+                    OnScriptError.FAIL
                 );
                 DateFieldScript dateFieldScript = leafFactory.newInstance(reader.leaves().get(0));
                 List<Long> results = new ArrayList<>();

+ 39 - 14
server/src/test/java/org/elasticsearch/index/mapper/DateScriptFieldTypeTests.java

@@ -128,7 +128,11 @@ public class DateScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
 
     public void testFormatDuel() throws IOException {
         DateFormatter formatter = DateFormatter.forPattern(randomDateFormatterPattern()).withLocale(randomLocale(random()));
-        DateScriptFieldType scripted = build(new Script(ScriptType.INLINE, "test", "read_timestamp", Map.of()), formatter);
+        DateScriptFieldType scripted = build(
+            new Script(ScriptType.INLINE, "test", "read_timestamp", Map.of()),
+            formatter,
+            OnScriptError.FAIL
+        );
         DateFieldMapper.DateFieldType indexed = new DateFieldMapper.DateFieldType("test", formatter);
         for (int i = 0; i < 100; i++) {
             long date = randomDate();
@@ -149,7 +153,7 @@ public class DateScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
             List<Long> results = new ArrayList<>();
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                DateScriptFieldType ft = build("add_days", Map.of("days", 1));
+                DateScriptFieldType ft = build("add_days", Map.of("days", 1), OnScriptError.FAIL);
                 DateScriptFieldData ifd = ft.fielddataBuilder(mockFielddataContext()).build(null, null);
                 searcher.search(new MatchAllDocsQuery(), new Collector() {
                     @Override
@@ -381,7 +385,9 @@ public class DateScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(1595432181354L, mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(2595432181354L, mockContext())), equalTo(0));
                 assertThat(
-                    searcher.count(build("add_days", Map.of("days", 1)).termQuery("2020-07-23T15:36:21.354Z", mockContext())),
+                    searcher.count(
+                        build("add_days", Map.of("days", 1), OnScriptError.FAIL).termQuery("2020-07-23T15:36:21.354Z", mockContext())
+                    ),
                     equalTo(1)
                 );
                 checkBadDate(() -> searcher.count(simpleMappedFieldType().termQuery("2020-07-22(-■_■)15:36:21.354Z", mockContext())));
@@ -462,7 +468,11 @@ public class DateScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
     }
 
     private DateScriptFieldType coolFormattedFieldType() {
-        return build(simpleMappedFieldType().script, DateFormatter.forPattern("yyyy-MM-dd(-■_■)HH:mm:ss.SSSz||epoch_millis"));
+        return build(
+            simpleMappedFieldType().script,
+            DateFormatter.forPattern("yyyy-MM-dd(-■_■)HH:mm:ss.SSSz||epoch_millis"),
+            OnScriptError.FAIL
+        );
     }
 
     @Override
@@ -470,21 +480,22 @@ public class DateScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
         return "date";
     }
 
-    private static DateScriptFieldType build(String code) {
-        return build(code, Map.of());
+    private DateScriptFieldType build(String code) {
+        return build(code, Map.of(), OnScriptError.FAIL);
     }
 
-    private static DateScriptFieldType build(String code, Map<String, Object> params) {
-        return build(new Script(ScriptType.INLINE, "test", code, params), DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER);
+    protected DateScriptFieldType build(String code, Map<String, Object> params, OnScriptError onScriptError) {
+        return build(new Script(ScriptType.INLINE, "test", code, params), DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER, onScriptError);
     }
 
     private static DateFieldScript.Factory factory(Script script) {
         return switch (script.getIdOrCode()) {
-            case "read_timestamp" -> (fieldName, params, lookup, formatter) -> ctx -> new DateFieldScript(
+            case "read_timestamp" -> (fieldName, params, lookup, formatter, onScriptError) -> ctx -> new DateFieldScript(
                 fieldName,
                 params,
                 lookup,
                 formatter,
+                onScriptError,
                 ctx
             ) {
                 @Override
@@ -495,11 +506,12 @@ public class DateScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
                     }
                 }
             };
-            case "add_days" -> (fieldName, params, lookup, formatter) -> ctx -> new DateFieldScript(
+            case "add_days" -> (fieldName, params, lookup, formatter, onScriptError) -> ctx -> new DateFieldScript(
                 fieldName,
                 params,
                 lookup,
                 formatter,
+                onScriptError,
                 ctx
             ) {
                 @Override
@@ -512,17 +524,30 @@ public class DateScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
                     }
                 }
             };
-            case "loop" -> (fieldName, params, lookup, formatter) -> {
+            case "loop" -> (fieldName, params, lookup, formatter, onScriptError) -> {
                 // Indicate that this script wants the field call "test", which *is* the name of this field
                 lookup.forkAndTrackFieldReferences("test");
-                throw new IllegalStateException("shoud have thrown on the line above");
+                throw new IllegalStateException("should have thrown on the line above");
+            };
+            case "error" -> (fieldName, params, lookup, formatter, onScriptError) -> ctx -> new DateFieldScript(
+                fieldName,
+                params,
+                lookup,
+                formatter,
+                onScriptError,
+                ctx
+            ) {
+                @Override
+                public void execute() {
+                    throw new RuntimeException("test error");
+                }
             };
             default -> throw new IllegalArgumentException("unsupported script [" + script.getIdOrCode() + "]");
         };
     }
 
-    private static DateScriptFieldType build(Script script, DateFormatter dateTimeFormatter) {
-        return new DateScriptFieldType("test", factory(script), dateTimeFormatter, script, emptyMap());
+    private static DateScriptFieldType build(Script script, DateFormatter dateTimeFormatter, OnScriptError onScriptError) {
+        return new DateScriptFieldType("test", factory(script), dateTimeFormatter, script, emptyMap(), onScriptError);
     }
 
     private static long randomDate() {

+ 3 - 2
server/src/test/java/org/elasticsearch/index/mapper/DateScriptMapperTests.java

@@ -26,12 +26,13 @@ public class DateScriptMapperTests extends MapperScriptTestCase<DateFieldScript.
                 String fieldName,
                 Map<String, Object> params,
                 SearchLookup searchLookup,
-                DateFormatter formatter
+                DateFormatter formatter,
+                OnScriptError onScriptError
             ) {
                 return new DateFieldScript.LeafFactory() {
                     @Override
                     public DateFieldScript newInstance(LeafReaderContext ctx) {
-                        return new DateFieldScript(fieldName, params, searchLookup, formatter, ctx) {
+                        return new DateFieldScript(fieldName, params, searchLookup, formatter, OnScriptError.FAIL, ctx) {
                             @Override
                             public void execute() {
                                 executor.accept(this);

+ 2 - 1
server/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java

@@ -2604,7 +2604,8 @@ public class DocumentParserTests extends MapperServiceTestCase {
                 protected RuntimeField createChildRuntimeField(
                     MappingParserContext parserContext,
                     String parentName,
-                    Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory
+                    Function<SearchLookup, CompositeFieldScript.LeafFactory> parentScriptFactory,
+                    OnScriptError onScriptError
                 ) {
                     throw new UnsupportedOperationException();
                 }

+ 14 - 2
server/src/test/java/org/elasticsearch/index/mapper/DoubleFieldMapperTests.java

@@ -105,7 +105,13 @@ public class DoubleFieldMapperTests extends NumberFieldMapperTests {
 
             @Override
             protected DoubleFieldScript.Factory emptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new DoubleFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new DoubleFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {}
                 };
@@ -113,7 +119,13 @@ public class DoubleFieldMapperTests extends NumberFieldMapperTests {
 
             @Override
             protected DoubleFieldScript.Factory nonEmptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new DoubleFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new DoubleFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         emit(1.0);

+ 5 - 2
server/src/test/java/org/elasticsearch/index/mapper/DoubleFieldScriptTests.java

@@ -31,10 +31,11 @@ import java.util.Map;
 import static org.hamcrest.Matchers.equalTo;
 
 public class DoubleFieldScriptTests extends FieldScriptTestCase<DoubleFieldScript.Factory> {
-    public static final DoubleFieldScript.Factory DUMMY = (fieldName, params, lookup) -> ctx -> new DoubleFieldScript(
+    public static final DoubleFieldScript.Factory DUMMY = (fieldName, params, lookup, onScriptError) -> ctx -> new DoubleFieldScript(
         fieldName,
         params,
         lookup,
+        OnScriptError.FAIL,
         ctx
     ) {
         @Override
@@ -66,6 +67,7 @@ public class DoubleFieldScriptTests extends FieldScriptTestCase<DoubleFieldScrip
                     "test",
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -100,7 +102,8 @@ public class DoubleFieldScriptTests extends FieldScriptTestCase<DoubleFieldScrip
                 DoubleFieldScript.LeafFactory leafFactory = fromSource().newFactory(
                     "field",
                     Collections.emptyMap(),
-                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider())
+                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL
                 );
                 DoubleFieldScript doubleFieldScript = leafFactory.newInstance(reader.leaves().get(0));
                 List<Double> results = new ArrayList<>();

+ 38 - 14
server/src/test/java/org/elasticsearch/index/mapper/DoubleScriptFieldTypeTests.java

@@ -64,7 +64,7 @@ public class DoubleScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTe
             List<Double> results = new ArrayList<>();
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                DoubleScriptFieldType ft = build("add_param", Map.of("param", 1));
+                DoubleScriptFieldType ft = build("add_param", Map.of("param", 1), OnScriptError.FAIL);
                 DoubleScriptFieldData ifd = ft.fielddataBuilder(mockFielddataContext()).build(null, null);
                 searcher.search(new MatchAllDocsQuery(), new Collector() {
                     @Override
@@ -190,7 +190,10 @@ public class DoubleScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTe
                 assertThat(searcher.count(simpleMappedFieldType().termQuery("1", mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(1, mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(1.1, mockContext())), equalTo(0));
-                assertThat(searcher.count(build("add_param", Map.of("param", 1)).termQuery(2, mockContext())), equalTo(1));
+                assertThat(
+                    searcher.count(build("add_param", Map.of("param", 1), OnScriptError.FAIL).termQuery(2, mockContext())),
+                    equalTo(1)
+                );
             }
         }
     }
@@ -223,12 +226,12 @@ public class DoubleScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTe
 
     @Override
     protected DoubleScriptFieldType simpleMappedFieldType() {
-        return build("read_foo", Map.of());
+        return build("read_foo", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
     protected MappedFieldType loopFieldType() {
-        return build("loop", Map.of());
+        return build("loop", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
@@ -236,13 +239,20 @@ public class DoubleScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTe
         return "double";
     }
 
-    private static DoubleScriptFieldType build(String code, Map<String, Object> params) {
-        return build(new Script(ScriptType.INLINE, "test", code, params));
+    protected DoubleScriptFieldType build(String code, Map<String, Object> params, OnScriptError onScriptError) {
+        Script script = new Script(ScriptType.INLINE, "test", code, params);
+        return new DoubleScriptFieldType("test", factory(script), script, emptyMap(), onScriptError);
     }
 
     private static DoubleFieldScript.Factory factory(Script script) {
         return switch (script.getIdOrCode()) {
-            case "read_foo" -> (fieldName, params, lookup) -> (ctx) -> new DoubleFieldScript(fieldName, params, lookup, ctx) {
+            case "read_foo" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new DoubleFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -250,7 +260,13 @@ public class DoubleScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTe
                     }
                 }
             };
-            case "add_param" -> (fieldName, params, lookup) -> (ctx) -> new DoubleFieldScript(fieldName, params, lookup, ctx) {
+            case "add_param" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new DoubleFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -258,16 +274,24 @@ public class DoubleScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTe
                     }
                 }
             };
-            case "loop" -> (fieldName, params, lookup) -> {
+            case "loop" -> (fieldName, params, lookup, onScriptError) -> {
                 // Indicate that this script wants the field call "test", which *is* the name of this field
                 lookup.forkAndTrackFieldReferences("test");
-                throw new IllegalStateException("shoud have thrown on the line above");
+                throw new IllegalStateException("should have thrown on the line above");
+            };
+            case "error" -> (fieldName, params, lookup, onScriptError) -> ctx -> new DoubleFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
+                @Override
+                public void execute() {
+                    throw new RuntimeException("test error");
+                }
             };
             default -> throw new IllegalArgumentException("unsupported script [" + script.getIdOrCode() + "]");
         };
     }
-
-    private static DoubleScriptFieldType build(Script script) {
-        return new DoubleScriptFieldType("test", factory(script), script, emptyMap());
-    }
 }

+ 7 - 2
server/src/test/java/org/elasticsearch/index/mapper/DoubleScriptMapperTests.java

@@ -21,11 +21,16 @@ public class DoubleScriptMapperTests extends MapperScriptTestCase<DoubleFieldScr
     private static DoubleFieldScript.Factory factory(Consumer<DoubleFieldScript> executor) {
         return new DoubleFieldScript.Factory() {
             @Override
-            public DoubleFieldScript.LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup) {
+            public DoubleFieldScript.LeafFactory newFactory(
+                String fieldName,
+                Map<String, Object> params,
+                SearchLookup searchLookup,
+                OnScriptError onScriptError
+            ) {
                 return new DoubleFieldScript.LeafFactory() {
                     @Override
                     public DoubleFieldScript newInstance(LeafReaderContext ctx) {
-                        return new DoubleFieldScript(fieldName, params, searchLookup, ctx) {
+                        return new DoubleFieldScript(fieldName, params, searchLookup, OnScriptError.FAIL, ctx) {
                             @Override
                             public void execute() {
                                 executor.accept(this);

+ 3 - 1
server/src/test/java/org/elasticsearch/index/mapper/GeoPointFieldScriptTests.java

@@ -26,10 +26,11 @@ import java.util.Map;
 import static org.hamcrest.Matchers.equalTo;
 
 public class GeoPointFieldScriptTests extends FieldScriptTestCase<GeoPointFieldScript.Factory> {
-    public static final GeoPointFieldScript.Factory DUMMY = (fieldName, params, lookup) -> ctx -> new GeoPointFieldScript(
+    public static final GeoPointFieldScript.Factory DUMMY = (fieldName, params, lookup, onScriptError) -> ctx -> new GeoPointFieldScript(
         fieldName,
         params,
         lookup,
+        OnScriptError.FAIL,
         ctx
     ) {
         @Override
@@ -61,6 +62,7 @@ public class GeoPointFieldScriptTests extends FieldScriptTestCase<GeoPointFieldS
                     "test",
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override

+ 27 - 12
server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptFieldTypeTests.java

@@ -64,7 +64,7 @@ public class GeoPointScriptFieldTypeTests extends AbstractNonTextScriptFieldType
             List<Object> results = new ArrayList<>();
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                GeoPointScriptFieldType ft = build("fromLatLon", Map.of());
+                GeoPointScriptFieldType ft = build("fromLatLon", Map.of(), OnScriptError.FAIL);
                 GeoPointScriptFieldData ifd = ft.fielddataBuilder(mockFielddataContext()).build(null, null);
                 searcher.search(new MatchAllDocsQuery(), new Collector() {
                     @Override
@@ -213,12 +213,12 @@ public class GeoPointScriptFieldTypeTests extends AbstractNonTextScriptFieldType
 
     @Override
     protected GeoPointScriptFieldType simpleMappedFieldType() {
-        return build("fromLatLon", Map.of());
+        return build("fromLatLon", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
     protected MappedFieldType loopFieldType() {
-        return build("loop", Map.of());
+        return build("loop", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
@@ -226,29 +226,44 @@ public class GeoPointScriptFieldTypeTests extends AbstractNonTextScriptFieldType
         return "geo_point";
     }
 
-    private static GeoPointScriptFieldType build(String code, Map<String, Object> params) {
-        return build(new Script(ScriptType.INLINE, "test", code, params));
+    protected GeoPointScriptFieldType build(String code, Map<String, Object> params, OnScriptError onScriptError) {
+        Script script = new Script(ScriptType.INLINE, "test", code, params);
+        return new GeoPointScriptFieldType("test", factory(script), script, emptyMap(), onScriptError);
     }
 
     private static GeoPointFieldScript.Factory factory(Script script) {
         return switch (script.getIdOrCode()) {
-            case "fromLatLon" -> (fieldName, params, lookup) -> (ctx) -> new GeoPointFieldScript(fieldName, params, lookup, ctx) {
+            case "fromLatLon" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new GeoPointFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     Map<?, ?> foo = (Map<?, ?>) lookup.source().source().get("foo");
                     emit(((Number) foo.get("lat")).doubleValue(), ((Number) foo.get("lon")).doubleValue());
                 }
             };
-            case "loop" -> (fieldName, params, lookup) -> {
+            case "loop" -> (fieldName, params, lookup, onScriptError) -> {
                 // Indicate that this script wants the field call "test", which *is* the name of this field
                 lookup.forkAndTrackFieldReferences("test");
-                throw new IllegalStateException("shoud have thrown on the line above");
+                throw new IllegalStateException("should have thrown on the line above");
+            };
+            case "error" -> (fieldName, params, lookup, onScriptError) -> ctx -> new GeoPointFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
+                @Override
+                public void execute() {
+                    throw new RuntimeException("test error");
+                }
             };
             default -> throw new IllegalArgumentException("unsupported script [" + script.getIdOrCode() + "]");
         };
     }
-
-    private static GeoPointScriptFieldType build(Script script) {
-        return new GeoPointScriptFieldType("test", factory(script), script, emptyMap());
-    }
 }

+ 7 - 2
server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptMapperTests.java

@@ -21,11 +21,16 @@ public class GeoPointScriptMapperTests extends MapperScriptTestCase<GeoPointFiel
     private static GeoPointFieldScript.Factory factory(Consumer<GeoPointFieldScript.Emit> executor) {
         return new GeoPointFieldScript.Factory() {
             @Override
-            public GeoPointFieldScript.LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup) {
+            public GeoPointFieldScript.LeafFactory newFactory(
+                String fieldName,
+                Map<String, Object> params,
+                SearchLookup searchLookup,
+                OnScriptError onScriptError
+            ) {
                 return new GeoPointFieldScript.LeafFactory() {
                     @Override
                     public GeoPointFieldScript newInstance(LeafReaderContext ctx) {
-                        return new GeoPointFieldScript(fieldName, params, searchLookup, ctx) {
+                        return new GeoPointFieldScript(fieldName, params, searchLookup, OnScriptError.FAIL, ctx) {
                             @Override
                             public void execute() {
                                 executor.accept(new Emit(this));

+ 4 - 4
server/src/test/java/org/elasticsearch/index/mapper/IndexTimeScriptTests.java

@@ -128,7 +128,7 @@ public class IndexTimeScriptTests extends MapperServiceTestCase {
     @SuppressWarnings("unchecked")
     protected <T> T compileScript(Script script, ScriptContext<T> context) {
         if (context.factoryClazz == LongFieldScript.Factory.class) {
-            return (T) (LongFieldScript.Factory) (n, p, l) -> ctx -> new TestLongFieldScript(
+            return (T) (LongFieldScript.Factory) (n, p, l, onScriptError) -> ctx -> new TestLongFieldScript(
                 n,
                 p,
                 l,
@@ -137,7 +137,7 @@ public class IndexTimeScriptTests extends MapperServiceTestCase {
             );
         }
         if (context.factoryClazz == DoubleFieldScript.Factory.class) {
-            return (T) (DoubleFieldScript.Factory) (n, p, l) -> ctx -> new TestDoubleFieldScript(
+            return (T) (DoubleFieldScript.Factory) (n, p, l, onScriptError) -> ctx -> new TestDoubleFieldScript(
                 n,
                 p,
                 l,
@@ -195,7 +195,7 @@ public class IndexTimeScriptTests extends MapperServiceTestCase {
             LeafReaderContext ctx,
             Consumer<TestLongFieldScript> executor
         ) {
-            super(fieldName, params, searchLookup, ctx);
+            super(fieldName, params, searchLookup, OnScriptError.FAIL, ctx);
             this.executor = executor;
         }
 
@@ -224,7 +224,7 @@ public class IndexTimeScriptTests extends MapperServiceTestCase {
             LeafReaderContext ctx,
             Consumer<TestDoubleFieldScript> executor
         ) {
-            super(fieldName, params, searchLookup, ctx);
+            super(fieldName, params, searchLookup, OnScriptError.FAIL, ctx);
             this.executor = executor;
         }
 

+ 14 - 2
server/src/test/java/org/elasticsearch/index/mapper/IpFieldMapperTests.java

@@ -388,7 +388,13 @@ public class IpFieldMapperTests extends MapperTestCase {
         return new IngestScriptSupport() {
             @Override
             protected IpFieldScript.Factory emptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new IpFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new IpFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {}
                 };
@@ -396,7 +402,13 @@ public class IpFieldMapperTests extends MapperTestCase {
 
             @Override
             protected IpFieldScript.Factory nonEmptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new IpFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new IpFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         emit("192.168.0.1");

+ 5 - 2
server/src/test/java/org/elasticsearch/index/mapper/IpFieldScriptTests.java

@@ -32,10 +32,11 @@ import java.util.Map;
 import static org.hamcrest.Matchers.equalTo;
 
 public class IpFieldScriptTests extends FieldScriptTestCase<IpFieldScript.Factory> {
-    public static final IpFieldScript.Factory DUMMY = (fieldName, params, lookup) -> ctx -> new IpFieldScript(
+    public static final IpFieldScript.Factory DUMMY = (fieldName, params, lookup, onScriptError) -> ctx -> new IpFieldScript(
         fieldName,
         params,
         lookup,
+        OnScriptError.FAIL,
         ctx
     ) {
         @Override
@@ -67,6 +68,7 @@ public class IpFieldScriptTests extends FieldScriptTestCase<IpFieldScript.Factor
                     "test",
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -101,7 +103,8 @@ public class IpFieldScriptTests extends FieldScriptTestCase<IpFieldScript.Factor
                 IpFieldScript.LeafFactory leafFactory = fromSource().newFactory(
                     "field",
                     Collections.emptyMap(),
-                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider())
+                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL
                 );
                 IpFieldScript ipFieldScript = leafFactory.newInstance(reader.leaves().get(0));
                 List<InetAddress> results = new ArrayList<>();

+ 35 - 14
server/src/test/java/org/elasticsearch/index/mapper/IpScriptFieldTypeTests.java

@@ -66,7 +66,7 @@ public class IpScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase {
             List<Object> results = new ArrayList<>();
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                IpScriptFieldType ft = build("append_param", Map.of("param", ".1"));
+                IpScriptFieldType ft = build("append_param", Map.of("param", ".1"), OnScriptError.FAIL);
                 BinaryScriptFieldData ifd = ft.fielddataBuilder(mockFielddataContext()).build(null, null);
                 DocValueFormat format = ft.docValueFormat(null, null);
                 searcher.search(new MatchAllDocsQuery(), new Collector() {
@@ -198,7 +198,7 @@ public class IpScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase {
             iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [\"200.0.0\"]}"))));
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                IpScriptFieldType fieldType = build("append_param", Map.of("param", ".1"));
+                IpScriptFieldType fieldType = build("append_param", Map.of("param", ".1"), OnScriptError.FAIL);
                 assertThat(searcher.count(fieldType.termQuery("192.168.0.1", mockContext())), equalTo(1));
                 assertThat(searcher.count(fieldType.termQuery("192.168.0.7", mockContext())), equalTo(0));
                 assertThat(searcher.count(fieldType.termQuery("192.168.0.0/16", mockContext())), equalTo(2));
@@ -240,12 +240,12 @@ public class IpScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase {
 
     @Override
     protected IpScriptFieldType simpleMappedFieldType() {
-        return build("read_foo", Map.of());
+        return build("read_foo", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
     protected MappedFieldType loopFieldType() {
-        return build("loop", Map.of());
+        return build("loop", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
@@ -253,13 +253,20 @@ public class IpScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase {
         return "ip";
     }
 
-    private static IpScriptFieldType build(String code, Map<String, Object> params) {
-        return build(new Script(ScriptType.INLINE, "test", code, params));
+    protected IpScriptFieldType build(String code, Map<String, Object> params, OnScriptError onScriptError) {
+        Script script = new Script(ScriptType.INLINE, "test", code, params);
+        return new IpScriptFieldType("test", factory(script), script, emptyMap(), onScriptError);
     }
 
     private static IpFieldScript.Factory factory(Script script) {
         return switch (script.getIdOrCode()) {
-            case "read_foo" -> (fieldName, params, lookup) -> (ctx) -> new IpFieldScript(fieldName, params, lookup, ctx) {
+            case "read_foo" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new IpFieldScript(
+                fieldName,
+                params,
+                lookup,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -267,7 +274,13 @@ public class IpScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase {
                     }
                 }
             };
-            case "append_param" -> (fieldName, params, lookup) -> (ctx) -> new IpFieldScript(fieldName, params, lookup, ctx) {
+            case "append_param" -> (fieldName, params, lookup, onScriptError) -> (ctx) -> new IpFieldScript(
+                fieldName,
+                params,
+                lookup,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -275,16 +288,24 @@ public class IpScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase {
                     }
                 }
             };
-            case "loop" -> (fieldName, params, lookup) -> {
+            case "loop" -> (fieldName, params, lookup, onScriptError) -> {
                 // Indicate that this script wants the field call "test", which *is* the name of this field
                 lookup.forkAndTrackFieldReferences("test");
-                throw new IllegalStateException("shoud have thrown on the line above");
+                throw new IllegalStateException("should have thrown on the line above");
+            };
+            case "error" -> (fieldName, params, lookup, onScriptError) -> ctx -> new IpFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
+                @Override
+                public void execute() {
+                    throw new RuntimeException("test error");
+                }
             };
             default -> throw new IllegalArgumentException("unsupported script [" + script.getIdOrCode() + "]");
         };
     }
-
-    private static IpScriptFieldType build(Script script) {
-        return new IpScriptFieldType("test", factory(script), script, emptyMap());
-    }
 }

+ 7 - 2
server/src/test/java/org/elasticsearch/index/mapper/IpScriptMapperTests.java

@@ -21,11 +21,16 @@ public class IpScriptMapperTests extends MapperScriptTestCase<IpFieldScript.Fact
     private static IpFieldScript.Factory factory(Consumer<IpFieldScript> executor) {
         return new IpFieldScript.Factory() {
             @Override
-            public IpFieldScript.LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup) {
+            public IpFieldScript.LeafFactory newFactory(
+                String fieldName,
+                Map<String, Object> params,
+                SearchLookup searchLookup,
+                OnScriptError onScriptError
+            ) {
                 return new IpFieldScript.LeafFactory() {
                     @Override
                     public IpFieldScript newInstance(LeafReaderContext ctx) {
-                        return new IpFieldScript(fieldName, params, searchLookup, ctx) {
+                        return new IpFieldScript(fieldName, params, searchLookup, OnScriptError.FAIL, ctx) {
                             @Override
                             public void execute() {
                                 executor.accept(this);

+ 14 - 2
server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldMapperTests.java

@@ -729,7 +729,13 @@ public class KeywordFieldMapperTests extends MapperTestCase {
         return new IngestScriptSupport() {
             @Override
             protected StringFieldScript.Factory emptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new StringFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new StringFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {}
                 };
@@ -737,7 +743,13 @@ public class KeywordFieldMapperTests extends MapperTestCase {
 
             @Override
             protected StringFieldScript.Factory nonEmptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new StringFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new StringFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         emit("foo");

+ 37 - 16
server/src/test/java/org/elasticsearch/index/mapper/KeywordScriptFieldTypeTests.java

@@ -60,7 +60,7 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
             List<String> results = new ArrayList<>();
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                KeywordScriptFieldType ft = build("append_param", Map.of("param", "-suffix"));
+                KeywordScriptFieldType ft = build("append_param", Map.of("param", "-suffix"), OnScriptError.FAIL);
                 StringScriptFieldData ifd = ft.fielddataBuilder(mockFielddataContext()).build(null, null);
                 searcher.search(new MatchAllDocsQuery(), new Collector() {
                     @Override
@@ -287,7 +287,7 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
             iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [2]}"))));
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                KeywordScriptFieldType fieldType = build("append_param", Map.of("param", "-suffix"));
+                KeywordScriptFieldType fieldType = build("append_param", Map.of("param", "-suffix"), OnScriptError.FAIL);
                 assertThat(searcher.count(fieldType.termQuery("1-suffix", mockContext())), equalTo(1));
             }
         }
@@ -299,7 +299,7 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
             iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [2]}"))));
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                KeywordScriptFieldType fieldType = build("append_param", Map.of("param", "-suffix"));
+                KeywordScriptFieldType fieldType = build("append_param", Map.of("param", "-suffix"), OnScriptError.FAIL);
                 expectThrows(
                     IllegalArgumentException.class,
                     () -> {
@@ -375,7 +375,7 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
             iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [2]}"))));
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                KeywordScriptFieldType fieldType = build("append_param", Map.of("param", "-Suffix"));
+                KeywordScriptFieldType fieldType = build("append_param", Map.of("param", "-Suffix"), OnScriptError.FAIL);
                 SearchExecutionContext searchExecutionContext = mockContext(true, fieldType);
                 Query query = new MatchQueryBuilder("test", "1-Suffix").toQuery(searchExecutionContext);
                 assertThat(searcher.count(query), equalTo(1));
@@ -385,12 +385,12 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
 
     @Override
     protected KeywordScriptFieldType simpleMappedFieldType() {
-        return build("read_foo", Map.of());
+        return build("read_foo", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
     protected KeywordScriptFieldType loopFieldType() {
-        return build("loop", Map.of());
+        return build("loop", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
@@ -398,13 +398,20 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
         return "keyword";
     }
 
-    private static KeywordScriptFieldType build(String code, Map<String, Object> params) {
-        return build(new Script(ScriptType.INLINE, "test", code, params));
+    protected KeywordScriptFieldType build(String code, Map<String, Object> params, OnScriptError onScriptError) {
+        Script script = new Script(ScriptType.INLINE, "test", code, params);
+        return new KeywordScriptFieldType("test", factory(script), script, emptyMap(), onScriptError);
     }
 
     private static StringFieldScript.Factory factory(Script script) {
         return switch (script.getIdOrCode()) {
-            case "read_foo" -> (fieldName, params, lookup) -> ctx -> new StringFieldScript(fieldName, params, lookup, ctx) {
+            case "read_foo" -> (fieldName, params, lookup, onScriptError) -> ctx -> new StringFieldScript(
+                fieldName,
+                params,
+                lookup,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -412,7 +419,13 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
                     }
                 }
             };
-            case "append_param" -> (fieldName, params, lookup) -> ctx -> new StringFieldScript(fieldName, params, lookup, ctx) {
+            case "append_param" -> (fieldName, params, lookup, onScriptError) -> ctx -> new StringFieldScript(
+                fieldName,
+                params,
+                lookup,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -420,16 +433,24 @@ public class KeywordScriptFieldTypeTests extends AbstractScriptFieldTypeTestCase
                     }
                 }
             };
-            case "loop" -> (fieldName, params, lookup) -> {
+            case "loop" -> (fieldName, params, lookup, onScriptError) -> {
                 // Indicate that this script wants the field call "test", which *is* the name of this field
                 lookup.forkAndTrackFieldReferences("test");
-                throw new IllegalStateException("shoud have thrown on the line above");
+                throw new IllegalStateException("should have thrown on the line above");
+            };
+            case "error" -> (fieldName, params, lookup, onScriptError) -> ctx -> new StringFieldScript(
+                fieldName,
+                params,
+                lookup,
+                onScriptError,
+                ctx
+            ) {
+                @Override
+                public void execute() {
+                    throw new RuntimeException("test error");
+                }
             };
             default -> throw new IllegalArgumentException("unsupported script [" + script.getIdOrCode() + "]");
         };
     }
-
-    private static KeywordScriptFieldType build(Script script) {
-        return new KeywordScriptFieldType("test", factory(script), script, emptyMap());
-    }
 }

+ 7 - 2
server/src/test/java/org/elasticsearch/index/mapper/KeywordScriptMapperTests.java

@@ -22,11 +22,16 @@ public class KeywordScriptMapperTests extends MapperScriptTestCase<StringFieldSc
     private static StringFieldScript.Factory factory(Consumer<StringFieldScript> executor) {
         return new StringFieldScript.Factory() {
             @Override
-            public StringFieldScript.LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup) {
+            public StringFieldScript.LeafFactory newFactory(
+                String fieldName,
+                Map<String, Object> params,
+                SearchLookup searchLookup,
+                OnScriptError onScriptError
+            ) {
                 return new StringFieldScript.LeafFactory() {
                     @Override
                     public StringFieldScript newInstance(LeafReaderContext ctx) {
-                        return new StringFieldScript(fieldName, params, searchLookup, ctx) {
+                        return new StringFieldScript(fieldName, params, searchLookup, OnScriptError.FAIL, ctx) {
                             @Override
                             public void execute() {
                                 executor.accept(this);

+ 14 - 2
server/src/test/java/org/elasticsearch/index/mapper/LongFieldMapperTests.java

@@ -132,7 +132,13 @@ public class LongFieldMapperTests extends WholeNumberFieldMapperTests {
 
             @Override
             protected LongFieldScript.Factory emptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new LongFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new LongFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {}
                 };
@@ -140,7 +146,13 @@ public class LongFieldMapperTests extends WholeNumberFieldMapperTests {
 
             @Override
             protected LongFieldScript.Factory nonEmptyFieldScript() {
-                return (fieldName, params, searchLookup) -> ctx -> new LongFieldScript(fieldName, params, searchLookup, ctx) {
+                return (fieldName, params, searchLookup, onScriptError) -> ctx -> new LongFieldScript(
+                    fieldName,
+                    params,
+                    searchLookup,
+                    OnScriptError.FAIL,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         emit(1);

+ 5 - 2
server/src/test/java/org/elasticsearch/index/mapper/LongFieldScriptTests.java

@@ -31,10 +31,11 @@ import java.util.Map;
 import static org.hamcrest.Matchers.equalTo;
 
 public class LongFieldScriptTests extends FieldScriptTestCase<LongFieldScript.Factory> {
-    public static final LongFieldScript.Factory DUMMY = (fieldName, params, lookup) -> ctx -> new LongFieldScript(
+    public static final LongFieldScript.Factory DUMMY = (fieldName, params, lookup, onScriptError) -> ctx -> new LongFieldScript(
         fieldName,
         params,
         lookup,
+        OnScriptError.FAIL,
         ctx
     ) {
         @Override
@@ -66,6 +67,7 @@ public class LongFieldScriptTests extends FieldScriptTestCase<LongFieldScript.Fa
                     "test",
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -100,7 +102,8 @@ public class LongFieldScriptTests extends FieldScriptTestCase<LongFieldScript.Fa
                 LongFieldScript.LeafFactory leafFactory = fromSource().newFactory(
                     "field",
                     Collections.emptyMap(),
-                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider())
+                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL
                 );
                 LongFieldScript longFieldScript = leafFactory.newInstance(reader.leaves().get(0));
                 List<Long> results = new ArrayList<>();

+ 48 - 16
server/src/test/java/org/elasticsearch/index/mapper/LongScriptFieldTypeTests.java

@@ -76,7 +76,7 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
             List<Long> results = new ArrayList<>();
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                LongScriptFieldType ft = build("add_param", Map.of("param", 1));
+                LongScriptFieldType ft = build("add_param", Map.of("param", 1), OnScriptError.FAIL);
                 LongScriptFieldData ifd = ft.fielddataBuilder(mockFielddataContext()).build(null, null);
                 searcher.search(new MatchAllDocsQuery(), new Collector() {
                     @Override
@@ -132,7 +132,8 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
             iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"timestamp\": [1595432181356]}"))));
             try (DirectoryReader reader = iw.getReader()) {
                 IndexSearcher searcher = newUnthreadedSearcher(reader);
-                LongScriptFieldData ifd = build("millis_ago", Map.of()).fielddataBuilder(mockFielddataContext()).build(null, null);
+                LongScriptFieldData ifd = build("millis_ago", Map.of(), OnScriptError.FAIL).fielddataBuilder(mockFielddataContext())
+                    .build(null, null);
                 SortField sf = ifd.sortField(null, MultiValueMode.MIN, null, false);
                 TopFieldDocs docs = searcher.search(new MatchAllDocsQuery(), 3, new Sort(sf));
                 assertThat(readSource(reader, docs.scoreDocs[0].doc), equalTo("{\"timestamp\": [1595432181356]}"));
@@ -222,7 +223,10 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
                 assertThat(searcher.count(simpleMappedFieldType().termQuery("1", mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(1, mockContext())), equalTo(1));
                 assertThat(searcher.count(simpleMappedFieldType().termQuery(1.1, mockContext())), equalTo(0));
-                assertThat(searcher.count(build("add_param", Map.of("param", 1)).termQuery(2, mockContext())), equalTo(1));
+                assertThat(
+                    searcher.count(build("add_param", Map.of("param", 1), OnScriptError.FAIL).termQuery(2, mockContext())),
+                    equalTo(1)
+                );
             }
         }
     }
@@ -255,12 +259,12 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
 
     @Override
     protected LongScriptFieldType simpleMappedFieldType() {
-        return build("read_foo", Map.of());
+        return build("read_foo", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
     protected LongScriptFieldType loopFieldType() {
-        return build("loop", Map.of());
+        return build("loop", Map.of(), OnScriptError.FAIL);
     }
 
     @Override
@@ -268,14 +272,21 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
         return "long";
     }
 
-    private static LongScriptFieldType build(String code, Map<String, Object> params) {
-        return build(new Script(ScriptType.INLINE, "test", code, params));
+    protected LongScriptFieldType build(String code, Map<String, Object> params, OnScriptError onScriptError) {
+        Script script = new Script(ScriptType.INLINE, "test", code, params);
+        return new LongScriptFieldType("test", factory(script), script, emptyMap(), onScriptError);
     }
 
     private static LongFieldScript.Factory factory(Script script) {
         switch (script.getIdOrCode()) {
             case "read_foo":
-                return (fieldName, params, lookup) -> (ctx) -> new LongFieldScript(fieldName, params, lookup, ctx) {
+                return (fieldName, params, lookup, onScriptError) -> (ctx) -> new LongFieldScript(
+                    fieldName,
+                    params,
+                    lookup,
+                    onScriptError,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -284,7 +295,13 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
                     }
                 };
             case "add_param":
-                return (fieldName, params, lookup) -> (ctx) -> new LongFieldScript(fieldName, params, lookup, ctx) {
+                return (fieldName, params, lookup, onScriptError) -> (ctx) -> new LongFieldScript(
+                    fieldName,
+                    params,
+                    lookup,
+                    onScriptError,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         for (Object foo : (List<?>) lookup.source().source().get("foo")) {
@@ -295,7 +312,13 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
             case "millis_ago":
                 // Painless actually call System.currentTimeMillis. We could mock the time but this works fine too.
                 long now = System.currentTimeMillis();
-                return (fieldName, params, lookup) -> (ctx) -> new LongFieldScript(fieldName, params, lookup, ctx) {
+                return (fieldName, params, lookup, onScriptError) -> (ctx) -> new LongFieldScript(
+                    fieldName,
+                    params,
+                    lookup,
+                    onScriptError,
+                    ctx
+                ) {
                     @Override
                     public void execute() {
                         for (Object timestamp : (List<?>) lookup.source().source().get("timestamp")) {
@@ -304,17 +327,26 @@ public class LongScriptFieldTypeTests extends AbstractNonTextScriptFieldTypeTest
                     }
                 };
             case "loop":
-                return (fieldName, params, lookup) -> {
+                return (fieldName, params, lookup, onScriptError) -> {
                     // Indicate that this script wants the field call "test", which *is* the name of this field
                     lookup.forkAndTrackFieldReferences("test");
-                    throw new IllegalStateException("shoud have thrown on the line above");
+                    throw new IllegalStateException("should have thrown on the line above");
+                };
+            case "error":
+                return (fieldName, params, lookup, onScriptError) -> ctx -> new LongFieldScript(
+                    fieldName,
+                    params,
+                    lookup,
+                    onScriptError,
+                    ctx
+                ) {
+                    @Override
+                    public void execute() {
+                        throw new RuntimeException("test error");
+                    }
                 };
             default:
                 throw new IllegalArgumentException("unsupported script [" + script.getIdOrCode() + "]");
         }
     }
-
-    private static LongScriptFieldType build(Script script) {
-        return new LongScriptFieldType("test", factory(script), script, emptyMap());
-    }
 }

+ 7 - 2
server/src/test/java/org/elasticsearch/index/mapper/LongScriptMapperTests.java

@@ -21,11 +21,16 @@ public class LongScriptMapperTests extends MapperScriptTestCase<LongFieldScript.
     private static LongFieldScript.Factory factory(Consumer<LongFieldScript> executor) {
         return new LongFieldScript.Factory() {
             @Override
-            public LongFieldScript.LeafFactory newFactory(String fieldName, Map<String, Object> params, SearchLookup searchLookup) {
+            public LongFieldScript.LeafFactory newFactory(
+                String fieldName,
+                Map<String, Object> params,
+                SearchLookup searchLookup,
+                OnScriptError onScriptError
+            ) {
                 return new LongFieldScript.LeafFactory() {
                     @Override
                     public LongFieldScript newInstance(LeafReaderContext ctx) {
-                        return new LongFieldScript(fieldName, params, searchLookup, ctx) {
+                        return new LongFieldScript(fieldName, params, searchLookup, OnScriptError.FAIL, ctx) {
                             @Override
                             public void execute() {
                                 executor.accept(this);

+ 8 - 3
server/src/test/java/org/elasticsearch/index/mapper/StringFieldScriptTests.java

@@ -30,10 +30,11 @@ import java.util.Map;
 import static org.hamcrest.Matchers.equalTo;
 
 public class StringFieldScriptTests extends FieldScriptTestCase<StringFieldScript.Factory> {
-    public static final StringFieldScript.Factory DUMMY = (fieldName, params, lookup) -> ctx -> new StringFieldScript(
+    public static final StringFieldScript.Factory DUMMY = (fieldName, params, lookup, onScriptError) -> ctx -> new StringFieldScript(
         fieldName,
         params,
         lookup,
+        OnScriptError.FAIL,
         ctx
     ) {
         @Override
@@ -65,6 +66,7 @@ public class StringFieldScriptTests extends FieldScriptTestCase<StringFieldScrip
                     "test",
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -91,6 +93,7 @@ public class StringFieldScriptTests extends FieldScriptTestCase<StringFieldScrip
                     "test",
                     Map.of(),
                     new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -130,7 +133,8 @@ public class StringFieldScriptTests extends FieldScriptTestCase<StringFieldScrip
                 StringFieldScript.LeafFactory leafFactory = fromSource().newFactory(
                     "field",
                     Collections.emptyMap(),
-                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider())
+                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL
                 );
                 StringFieldScript stringFieldScript = leafFactory.newInstance(reader.leaves().get(0));
                 stringFieldScript.runForDoc(0);
@@ -159,7 +163,8 @@ public class StringFieldScriptTests extends FieldScriptTestCase<StringFieldScrip
                 StringFieldScript.LeafFactory leafFactory = fromSource().newFactory(
                     "field",
                     Collections.emptyMap(),
-                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider())
+                    new SearchLookup(field -> null, (ft, lookup, fdt) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL
                 );
                 StringFieldScript stringFieldScript = leafFactory.newInstance(reader.leaves().get(0));
                 stringFieldScript.runForDoc(0);

+ 4 - 0
server/src/test/java/org/elasticsearch/script/CompositeFieldScriptTests.java

@@ -13,6 +13,7 @@ import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.tests.index.RandomIndexWriter;
 import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.search.lookup.SearchLookup;
 import org.elasticsearch.search.lookup.SourceLookup;
 import org.elasticsearch.test.ESTestCase;
@@ -33,6 +34,7 @@ public class CompositeFieldScriptTests extends ESTestCase {
                     "composite",
                     Collections.emptyMap(),
                     new SearchLookup(field -> null, (ft, lookup, ftd) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -64,6 +66,7 @@ public class CompositeFieldScriptTests extends ESTestCase {
                     "composite",
                     Collections.emptyMap(),
                     new SearchLookup(field -> null, (ft, lookup, ftd) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override
@@ -77,6 +80,7 @@ public class CompositeFieldScriptTests extends ESTestCase {
                     "composite.leaf",
                     Collections.emptyMap(),
                     new SearchLookup(field -> null, (ft, lookup, ftd) -> null, new SourceLookup.ReaderSourceProvider()),
+                    OnScriptError.FAIL,
                     reader.leaves().get(0)
                 ) {
                     @Override

+ 10 - 3
server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java

@@ -27,6 +27,7 @@ import org.elasticsearch.index.mapper.LuceneDocument;
 import org.elasticsearch.index.mapper.MappedFieldType;
 import org.elasticsearch.index.mapper.NumberFieldMapper;
 import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.script.LongFieldScript;
 import org.elasticsearch.script.Script;
 import org.elasticsearch.script.StringFieldScript;
@@ -599,7 +600,7 @@ public class RangeAggregatorTests extends AggregatorTestCase {
     public void testRuntimeFieldTopLevelQueryNotOptimized() throws IOException {
         long totalDocs = (long) RangeAggregator.DOCS_PER_RANGE_TO_USE_FILTERS * 4;
         SearchLookup lookup = new SearchLookup(s -> null, (ft, l, ftd) -> null, new SourceLookup.ReaderSourceProvider());
-        StringFieldScript.LeafFactory scriptFactory = ctx -> new StringFieldScript("dummy", Map.of(), lookup, ctx) {
+        StringFieldScript.LeafFactory scriptFactory = ctx -> new StringFieldScript("dummy", Map.of(), lookup, OnScriptError.FAIL, ctx) {
             @Override
             public void execute() {
                 emit("cat");
@@ -641,13 +642,19 @@ public class RangeAggregatorTests extends AggregatorTestCase {
      */
     public void testRuntimeFieldRangesNotOptimized() throws IOException {
         long totalDocs = (long) RangeAggregator.DOCS_PER_RANGE_TO_USE_FILTERS * 4;
-        LongFieldScript.Factory scriptFactory = (fieldName, params, l) -> ctx -> new LongFieldScript(fieldName, Map.of(), l, ctx) {
+        LongFieldScript.Factory scriptFactory = (fieldName, params, l, onScriptError) -> ctx -> new LongFieldScript(
+            fieldName,
+            Map.of(),
+            l,
+            OnScriptError.FAIL,
+            ctx
+        ) {
             @Override
             public void execute() {
                 emit((long) getDoc().get(NUMBER_FIELD_NAME).get(0));
             }
         };
-        MappedFieldType dummyFt = new LongScriptFieldType("dummy", scriptFactory, new Script("test"), Map.of());
+        MappedFieldType dummyFt = new LongScriptFieldType("dummy", scriptFactory, new Script("test"), Map.of(), OnScriptError.FAIL);
         MappedFieldType numberFt = new NumberFieldMapper.NumberFieldType(NUMBER_FIELD_NAME, NumberFieldMapper.NumberType.INTEGER);
         debugTestCase(
             new RangeAggregationBuilder("r").field("dummy").addRange(0, 1).addRange(1, 2).addRange(2, 3),

+ 5 - 3
server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java

@@ -60,6 +60,7 @@ import org.elasticsearch.index.mapper.NumberFieldMapper;
 import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType;
 import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
 import org.elasticsearch.index.mapper.ObjectMapper;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.index.mapper.ProvidedIdFieldMapper;
 import org.elasticsearch.index.mapper.RangeFieldMapper;
 import org.elasticsearch.index.mapper.RangeType;
@@ -1994,7 +1995,7 @@ public class TermsAggregatorTests extends AggregatorTestCase {
     public void testRuntimeFieldTopLevelNotOptimized() throws IOException {
         long totalDocs = 500;
         SearchLookup lookup = new SearchLookup(s -> null, (ft, l, ftd) -> null, new SourceLookup.ReaderSourceProvider());
-        StringFieldScript.LeafFactory scriptFactory = ctx -> new StringFieldScript("dummy", Map.of(), lookup, ctx) {
+        StringFieldScript.LeafFactory scriptFactory = ctx -> new StringFieldScript("dummy", Map.of(), lookup, OnScriptError.FAIL, ctx) {
             @Override
             public void execute() {
                 emit("cat");
@@ -2043,10 +2044,11 @@ public class TermsAggregatorTests extends AggregatorTestCase {
      */
     public void testRuntimeFieldTermsNotOptimized() throws IOException {
         long totalDocs = 500;
-        StringFieldScript.Factory scriptFactory = (fieldName, params, lookup) -> ctx -> new StringFieldScript(
+        StringFieldScript.Factory scriptFactory = (fieldName, params, lookup, onScriptError) -> ctx -> new StringFieldScript(
             fieldName,
             Map.of(),
             lookup,
+            OnScriptError.FAIL,
             ctx
         ) {
             @Override
@@ -2056,7 +2058,7 @@ public class TermsAggregatorTests extends AggregatorTestCase {
         };
         BytesRef[] values = new BytesRef[] { new BytesRef("stuff"), new BytesRef("more_stuff"), new BytesRef("other_stuff"), };
         MappedFieldType keywordFt = new KeywordFieldType("k", true, true, Collections.emptyMap());
-        MappedFieldType dummyFt = new KeywordScriptFieldType("dummy", scriptFactory, new Script("test"), Map.of());
+        MappedFieldType dummyFt = new KeywordScriptFieldType("dummy", scriptFactory, new Script("test"), Map.of(), OnScriptError.FAIL);
         debugTestCase(new TermsAggregationBuilder("t").field("dummy"), new MatchAllDocsQuery(), iw -> {
             for (int d = 0; d < totalDocs; d++) {
                 BytesRef value = values[d % values.length];

+ 2 - 0
server/src/test/java/org/elasticsearch/search/runtime/GeoPointScriptFieldDistanceFeatureQueryTests.java

@@ -21,6 +21,7 @@ import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.geo.GeoUtils;
 import org.elasticsearch.index.mapper.GeoPointScriptFieldType;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.script.AbstractLongFieldScript;
 import org.elasticsearch.script.GeoPointFieldScript;
 import org.elasticsearch.script.Script;
@@ -88,6 +89,7 @@ public class GeoPointScriptFieldDistanceFeatureQueryTests extends AbstractScript
                     "test",
                     Map.of(),
                     searchLookup,
+                    OnScriptError.FAIL,
                     ctx
                 ) {
                     @Override

+ 2 - 0
server/src/test/java/org/elasticsearch/search/runtime/LongScriptFieldDistanceFeatureQueryTests.java

@@ -17,6 +17,7 @@ import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.tests.index.RandomIndexWriter;
 import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.script.AbstractLongFieldScript;
 import org.elasticsearch.script.DateFieldScript;
 import org.elasticsearch.script.Script;
@@ -75,6 +76,7 @@ public class LongScriptFieldDistanceFeatureQueryTests extends AbstractScriptFiel
                     Map.of(),
                     searchLookup,
                     null,
+                    OnScriptError.FAIL,
                     ctx
                 ) {
                     @Override

+ 52 - 8
test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java

@@ -15,6 +15,7 @@ import org.apache.lucene.search.Rescorer;
 import org.apache.lucene.search.Scorable;
 import org.apache.lucene.search.SortField;
 import org.elasticsearch.common.util.Maps;
+import org.elasticsearch.index.mapper.OnScriptError;
 import org.elasticsearch.index.query.IntervalFilterScript;
 import org.elasticsearch.index.similarity.ScriptedSimilarity.Doc;
 import org.elasticsearch.index.similarity.ScriptedSimilarity.Field;
@@ -269,7 +270,13 @@ public class MockScriptEngine implements ScriptEngine {
             IntervalFilterScript.Factory factory = mockCompiled::createIntervalFilterScript;
             return context.factoryClazz.cast(factory);
         } else if (context.instanceClazz.equals(BooleanFieldScript.class)) {
-            BooleanFieldScript.Factory booleanFieldScript = (f, p, s) -> ctx -> new BooleanFieldScript(f, p, s, ctx) {
+            BooleanFieldScript.Factory booleanFieldScript = (f, p, s, onScriptError) -> ctx -> new BooleanFieldScript(
+                f,
+                p,
+                s,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     emit(true);
@@ -277,7 +284,13 @@ public class MockScriptEngine implements ScriptEngine {
             };
             return context.factoryClazz.cast(booleanFieldScript);
         } else if (context.instanceClazz.equals(StringFieldScript.class)) {
-            StringFieldScript.Factory stringFieldScript = (f, p, s) -> ctx -> new StringFieldScript(f, p, s, ctx) {
+            StringFieldScript.Factory stringFieldScript = (f, p, s, onScriptError) -> ctx -> new StringFieldScript(
+                f,
+                p,
+                s,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     emit("test");
@@ -285,7 +298,13 @@ public class MockScriptEngine implements ScriptEngine {
             };
             return context.factoryClazz.cast(stringFieldScript);
         } else if (context.instanceClazz.equals(LongFieldScript.class)) {
-            LongFieldScript.Factory longFieldScript = (f, p, s) -> ctx -> new LongFieldScript(f, p, s, ctx) {
+            LongFieldScript.Factory longFieldScript = (f, p, s, onScriptError) -> ctx -> new LongFieldScript(
+                f,
+                p,
+                s,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     emit(1L);
@@ -293,7 +312,13 @@ public class MockScriptEngine implements ScriptEngine {
             };
             return context.factoryClazz.cast(longFieldScript);
         } else if (context.instanceClazz.equals(DoubleFieldScript.class)) {
-            DoubleFieldScript.Factory doubleFieldScript = (f, p, s) -> ctx -> new DoubleFieldScript(f, p, s, ctx) {
+            DoubleFieldScript.Factory doubleFieldScript = (f, p, s, onScriptError) -> ctx -> new DoubleFieldScript(
+                f,
+                p,
+                s,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     emit(1.2D);
@@ -301,7 +326,14 @@ public class MockScriptEngine implements ScriptEngine {
             };
             return context.factoryClazz.cast(doubleFieldScript);
         } else if (context.instanceClazz.equals(DateFieldScript.class)) {
-            DateFieldScript.Factory dateFieldScript = (f, p, s, formatter) -> ctx -> new DateFieldScript(f, p, s, formatter, ctx) {
+            DateFieldScript.Factory dateFieldScript = (f, p, s, formatter, onScriptError) -> ctx -> new DateFieldScript(
+                f,
+                p,
+                s,
+                formatter,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     emit(123L);
@@ -309,7 +341,7 @@ public class MockScriptEngine implements ScriptEngine {
             };
             return context.factoryClazz.cast(dateFieldScript);
         } else if (context.instanceClazz.equals(IpFieldScript.class)) {
-            IpFieldScript.Factory ipFieldScript = (f, p, s) -> ctx -> new IpFieldScript(f, p, s, ctx) {
+            IpFieldScript.Factory ipFieldScript = (f, p, s, onScriptError) -> ctx -> new IpFieldScript(f, p, s, OnScriptError.FAIL, ctx) {
                 @Override
                 public void execute() {
                     emit("127.0.0.1");
@@ -317,7 +349,13 @@ public class MockScriptEngine implements ScriptEngine {
             };
             return context.factoryClazz.cast(ipFieldScript);
         } else if (context.instanceClazz.equals(GeoPointFieldScript.class)) {
-            GeoPointFieldScript.Factory geoPointFieldScript = (f, p, s) -> ctx -> new GeoPointFieldScript(f, p, s, ctx) {
+            GeoPointFieldScript.Factory geoPointFieldScript = (f, p, s, onScriptError) -> ctx -> new GeoPointFieldScript(
+                f,
+                p,
+                s,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     emit(1.2D, 1.2D);
@@ -325,7 +363,13 @@ public class MockScriptEngine implements ScriptEngine {
             };
             return context.factoryClazz.cast(geoPointFieldScript);
         } else if (context.instanceClazz.equals(CompositeFieldScript.class)) {
-            CompositeFieldScript.Factory objectFieldScript = (f, p, s) -> ctx -> new CompositeFieldScript(f, p, s, ctx) {
+            CompositeFieldScript.Factory objectFieldScript = (f, p, s, onScriptError) -> ctx -> new CompositeFieldScript(
+                f,
+                p,
+                s,
+                OnScriptError.FAIL,
+                ctx
+            ) {
                 @Override
                 public void execute() {
                     emit("field1", "value1");