Painless contexts
A Painless script is evaluated within a context. Each context has values that are available as local variables, a whitelist that controls the available classes, and the methods and fields within those classes (API), and if and what type of value is returned.
A Painless script is typically executed within one of the contexts in the table below. Note this is not necessarily a comprehensive list as custom plugins and specialized code may define new ways to use a Painless script.
Name | Painless Documentation | Elasticsearch Documentation |
---|---|---|
Ingest processor |
{ref}/script-processor.html[Elasticsearch Documentation] |
|
Update |
{ref}/docs-update.html[Elasticsearch Documentation] |
|
Update by query |
{ref}/docs-update-by-query.html[Elasticsearch Documentation] |
|
Reindex |
{ref}/docs-reindex.html[Elasticsearch Documentation] |
|
Sort |
{ref}/search-request-sort.html[Elasticsearch Documentation] |
|
Similarity |
{ref}/index-modules-similarity.html[Elasticsearch Documentation] |
|
Weight |
{ref}/index-modules-similarity.html[Elasticsearch Documentation] |
|
Score |
{ref}/query-dsl-function-score-query.html[Elasticsearch Documentation] |
|
Field |
{ref}/search-request-script-fields.html[Elasticsearch Documentation] |
|
Filter |
{ref}/query-dsl-script-query.html[Elasticsearch Documentation] |
|
Minimum should match |
{ref}/query-dsl-terms-set-query.html[Elasticsearch Documentation] |
|
Metric aggregation initialization |
{ref}/search-aggregations-metrics-scripted-metric-aggregation.html[Elasticsearch Documentation] |
|
Metric aggregation map |
{ref}/search-aggregations-metrics-scripted-metric-aggregation.html[Elasticsearch Documentation] |
|
Metric aggregation combine |
{ref}/search-aggregations-metrics-scripted-metric-aggregation.html[Elasticsearch Documentation] |
|
Metric aggregation reduce |
{ref}/search-aggregations-metrics-scripted-metric-aggregation.html[Elasticsearch Documentation] |
|
Bucket script aggregation |
{ref}/search-aggregations-pipeline-bucket-script-aggregation.html[Elasticsearch Documentation] |
|
Bucket selector aggregation |
{ref}/search-aggregations-pipeline-bucket-selector-aggregation.html[Elasticsearch Documentation] |
|
Watcher condition |
{ref}/condition-script.html[Elasticsearch Documentation] |
|
Watcher transform |
{ref}/transform-script.html[Elasticsearch Documentation] |
Context examples
To run the examples, index the sample seat data into Elasticsearch. The examples must be run sequentially to work correctly.
-
Download the seat data. This data set contains booking information for a collection of plays. Each document represents a single seat for a play at a particular theater on a specific date and time.
Each document contains the following fields:
theatre
({ref}/keyword.html[keyword
])-
The name of the theater the play is in.
play
({ref}/text.html[text
])-
The name of the play.
actors
({ref}/text.html[text
])-
A list of actors in the play.
row
({ref}/number.html[integer
])-
The row of the seat.
number
({ref}/number.html[integer
])-
The number of the seat within a row.
cost
({ref}/number.html[double
])-
The cost of the ticket for the seat.
sold
({ref}/boolean.html[boolean
])-
Whether or not the seat is sold.
datetime
({ref}/date.html[date
])-
The date and time of the play as a date object.
date
({ref}/keyword.html[keyword
])-
The date of the play as a keyword.
time
({ref}/keyword.html[keyword
])-
The time of the play as a keyword.
-
{defguide}/running-elasticsearch.html[Start] Elasticsearch. Note these examples assume Elasticsearch and Kibana are running locally. To use the Console editor with a remote Kibana instance, click the settings icon and enter the Console URL. To submit a cURL request to a remote Elasticsearch instance, edit the request URL.
-
Create {ref}/mapping.html[mappings] for the sample data:
PUT /seats { "mappings": { "seat": { "properties": { "theatre": { "type": "keyword" }, "play": { "type": "text" }, "actors": { "type": "text" }, "row": { "type": "integer" }, "number": { "type": "integer" }, "cost": { "type": "double" }, "sold": { "type": "boolean" }, "datetime": { "type": "date" }, "date": { "type": "keyword" }, "time": { "type": "keyword" } } } } }
-
Run the ingest processor context example. This sets up a script ingest processor used on each document as the seat data is indexed.
-
Index the seat data:
curl -XPOST localhost:9200/seats/seat/_bulk?pipeline=seats -H "Content-Type: application/x-ndjson" --data-binary "@/<local-file-path>/seats-init.json"
Ingest processor context
Use a Painless script in an {ref}/script-processor.html[ingest processor] to modify documents upon insertion.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
- {ref}/mapping-index-field.html[
ctx['_index']
] (String
) -
The name of the index.
- {ref}/mapping-type-field.html[
ctx['_type']
] (String
) -
The type of document within an index.
ctx
(Map
)-
Contains extracted JSON in a
Map
andList
structure for the fields that are part of the document.
Side Effects
- {ref}/mapping-index-field.html[
ctx['_index']
] -
Modify this to change the destination index for the current document.
- {ref}/mapping-type-field.html[
ctx['_type']
] -
Modify this to change the type for the current document.
ctx
(Map
)-
Modify the values in the
Map/List
structure to add, modify, or delete the fields of a document.
Return
- void
-
No expected return value.
API
The standard Painless API is available.
Example
To run this example, first follow the steps in context examples.
The seat data contains:
-
A date in the format
YYYY-MM-DD
where the second digit of both month and day is optional. -
A time in the format HH:MM* where the second digit of both hours and minutes is optional. The star (*) represents either the
String
AM
orPM
.
The following ingest script processes the date and time Strings
and stores the
result in a datetime
field.
String[] split(String s, char d) { (1)
int count = 0;
for (char c : s.toCharArray()) { (2)
if (c == d) {
++count;
}
}
if (count == 0) {
return new String[] {s}; (3)
}
String[] r = new String[count + 1]; (4)
int i0 = 0, i1 = 0;
count = 0;
for (char c : s.toCharArray()) { (5)
if (c == d) {
r[count++] = s.substring(i0, i1);
i0 = i1 + 1;
}
++i1;
}
r[count] = s.substring(i0, i1); (6)
return r;
}
String[] dateSplit = split(ctx.date, (char)"-"); (7)
String year = dateSplit[0].trim();
String month = dateSplit[1].trim();
if (month.length() == 1) { (8)
month = "0" + month;
}
String day = dateSplit[2].trim();
if (day.length() == 1) { (9)
day = "0" + day;
}
boolean pm = ctx.time.substring(ctx.time.length() - 2).equals("PM"); (10)
String[] timeSplit = split(
ctx.time.substring(0, ctx.time.length() - 2), (char)":"); (11)
int hours = Integer.parseInt(timeSplit[0].trim());
int minutes = Integer.parseInt(timeSplit[1].trim());
if (pm) { (12)
hours += 12;
}
String dts = year + "-" + month + "-" + day + "T" +
(hours < 10 ? "0" + hours : "" + hours) + ":" +
(minutes < 10 ? "0" + minutes : "" + minutes) +
":00+08:00"; (13)
ZonedDateTime dt = ZonedDateTime.parse(
dts, DateTimeFormatter.ISO_OFFSET_DATE_TIME); (14)
ctx.datetime = dt.getLong(ChronoField.INSTANT_SECONDS)*1000L; (15)
-
Creates a
split
function to split aString
type value using achar
type value as the delimiter. This is useful for handling the necessity of pulling out the individual pieces of the date and timeStrings
from the original seat data. -
The first pass through each
char
in theString
collects how many newStrings
the original is split into. -
Returns the original
String
if there are no instances of the delimitingchar
. -
Creates an array type value to collect the split
Strings
into based on the number ofchar
delimiters found in the first pass. -
The second pass through each
char
in theString
collects each split substring into an array type value ofStrings
. -
Collects the last substring into the array type value of
Strings
. -
Uses the
split
function to separate the dateString
from the seat data into year, month, and dayStrings
.- Note
-
-
The use of a
String
type value tochar
type value cast as part of the second argument since character literals do not exist. -
The use of the
ctx
ingest processor context variable to retrieve the data from thedate
field.
-
-
Appends the string literal
"0"
value to a single digit month since the format of the seat data allows for this case. -
Appends the string literal
"0"
value to a single digit day since the format of the seat data allows for this case. -
Sets the
boolean type
variable totrue
if the timeString
is a time in the afternoon or evening.- Note
-
-
The use of the
ctx
ingest processor context variable to retrieve the data from thetime
field.
-
-
Uses the
split
function to separate the timeString
from the seat data into hours and minutesStrings
.- Note
-
-
The use of the
substring
method to remove theAM
orPM
portion of the timeString
. -
The use of a
String
type value tochar
type value cast as part of the second argument since character literals do not exist. -
The use of the
ctx
ingest processor context variable to retrieve the data from thedate
field.
-
-
If the time
String
is an afternoon or evening value adds the integer literal12
to the existing hours to move to a 24-hour based time. -
Builds a new time
String
that is parsable using existing API methods. -
Creates a
ZonedDateTime
reference type value by using the API methodparse
to parse the new timeString
. -
Sets the datetime field
datetime
to the number of milliseconds retrieved from the API methodgetLong
.- Note
-
-
The use of the
ctx
ingest processor context variable to set the fielddatetime
. Manipulate each document’s fields with thectx
variable as each document is indexed.
-
Submit the following request:
PUT /_ingest/pipeline/seats
{
"description": "update datetime for seats",
"processors": [
{
"script": {
"source": "String[] split(String s, char d) { int count = 0; for (char c : s.toCharArray()) { if (c == d) { ++count; } } if (count == 0) { return new String[] {s}; } String[] r = new String[count + 1]; int i0 = 0, i1 = 0; count = 0; for (char c : s.toCharArray()) { if (c == d) { r[count++] = s.substring(i0, i1); i0 = i1 + 1; } ++i1; } r[count] = s.substring(i0, i1); return r; } String[] dateSplit = split(ctx.date, (char)\"-\"); String year = dateSplit[0].trim(); String month = dateSplit[1].trim(); if (month.length() == 1) { month = \"0\" + month; } String day = dateSplit[2].trim(); if (day.length() == 1) { day = \"0\" + day; } boolean pm = ctx.time.substring(ctx.time.length() - 2).equals(\"PM\"); String[] timeSplit = split(ctx.time.substring(0, ctx.time.length() - 2), (char)\":\"); int hours = Integer.parseInt(timeSplit[0].trim()); int minutes = Integer.parseInt(timeSplit[1].trim()); if (pm) { hours += 12; } String dts = year + \"-\" + month + \"-\" + day + \"T\" + (hours < 10 ? \"0\" + hours : \"\" + hours) + \":\" + (minutes < 10 ? \"0\" + minutes : \"\" + minutes) + \":00+08:00\"; ZonedDateTime dt = ZonedDateTime.parse(dts, DateTimeFormatter.ISO_OFFSET_DATE_TIME); ctx.datetime = dt.getLong(ChronoField.INSTANT_SECONDS)*1000L;"
}
}
]
}
Update context
Use a Painless script in an {ref}/docs-update.html[update] operation to add, modify, or delete fields within a single document.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
ctx['op']
(String
)-
The name of the operation.
- {ref}/mapping-routing-field.html[
ctx['_routing']
] (String
, read-only) -
The value used to select a shard for document storage.
- {ref}/mapping-index-field.html[
ctx['_index']
] (String
, read-only) -
The name of the index.
- {ref}/mapping-type-field.html[
ctx['_type']
] (String
, read-only) -
The type of document within an index.
- {ref}/mapping-id-field.html[
ctx['_id']
] (int
, read-only) -
The unique document id.
ctx['_version']
(int
, read-only)-
The current version of the document.
ctx['_now']
(long
, read-only)-
The current timestamp in milliseconds.
- {ref}/mapping-source-field.html[
ctx['_source']
] (Map
) -
Contains extracted JSON in a
Map
andList
structure for the fields existing in a stored document.
Side Effects
ctx['op']
-
Use the default of
index
to update a document. Set tonone
to specify no operation ordelete
to delete the current document from the index. - {ref}/mapping-source-field.html[
ctx['_source']
] -
Modify the values in the
Map/List
structure to add, modify, or delete the fields of a document.
Return
void
-
No expected return value.
API
The standard Painless API is available.
Update by query context
Use a Painless script in an {ref}/docs-update-by-query.html[update by query] operation to add, modify, or delete fields within each of a set of documents collected as the result of query.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
ctx['op']
(String
)-
The name of the operation.
- {ref}/mapping-routing-field.html[
ctx['_routing']
] (String
, read-only) -
The value used to select a shard for document storage.
- {ref}/mapping-index-field.html[
ctx['_index']
] (String
, read-only) -
The name of the index.
- {ref}/mapping-type-field.html[
ctx['_type']
] (String
, read-only) -
The type of document within an index.
- {ref}/mapping-id-field.html[
ctx['_id']
] (int
, read-only) -
The unique document id.
ctx['_version']
(int
, read-only)-
The current version of the document.
- {ref}/mapping-source-field.html[
ctx['_source']
] (Map
) -
Contains extracted JSON in a
Map
andList
structure for the fields existing in a stored document.
Side Effects
ctx['op']
-
Use the default of
index
to update a document. Set tonone
to specify no operation ordelete
to delete the current document from the index. - {ref}/mapping-source-field.html[
ctx['_source']
] -
Modify the values in the
Map/List
structure to add, modify, or delete the fields of a document.
Return
void
-
No expected return value.
API
The standard Painless API is available.
Reindex context
Use a Painless script in a {ref}/docs-reindex.html[reindex] operation to add, modify, or delete fields within each document in an original index as its reindexed into a target index.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
ctx['op']
(String
)-
The name of the operation.
- {ref}/mapping-routing-field.html[
ctx['_routing']
] (String
) -
The value used to select a shard for document storage.
- {ref}/mapping-index-field.html[
ctx['_index']
] (String
) -
The name of the index.
- {ref}/mapping-type-field.html[
ctx['_type']
] (String
) -
The type of document within an index.
- {ref}/mapping-id-field.html[
ctx['_id']
] (int
, read-only) -
The unique document id.
ctx['_version']
(int
)-
The current version of the document.
- {ref}/mapping-source-field.html[
ctx['_source']
] (Map
) -
Contains extracted JSON in a
Map
andList
structure for the fields existing in a stored document.
Side Effects
ctx['op']
-
Use the default of
index
to update a document. Set tonone
to specify no operation ordelete
to delete the current document from the index. - {ref}/mapping-routing-field.html[
ctx['_routing']
] -
Modify this to change the routing value for the current document.
- {ref}/mapping-index-field.html[
ctx['_index']
] -
Modify this to change the destination index for the current document.
- {ref}/mapping-type-field.html[
ctx['_type']
] -
Modify this to change the type for the current document.
- {ref}/mapping-id-field.html[
ctx['_id']
] -
Modify this to change the id for the current document.
ctx['_version']
(int
)-
Modify this to modify the version for the current document.
- {ref}/mapping-source-field.html[
ctx['_source']
] -
Modify the values in the
Map/List
structure to add, modify, or delete the fields of a document.
Return
void
-
No expected return value.
API
The standard Painless API is available.
Sort context
Use a Painless script to {ref}/search-request-sort.html[sort] the documents in a query.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
doc
(Map
, read-only)-
Contains the fields of the current document. For single-valued fields, the value can be accessed via
doc['fieldname'].value
. For multi-valued fields, this returns the first value; other values can be accessed viadoc['fieldname'].get(index)
_score
(double
read-only)-
The similarity score of the current document.
Return
double
-
The score for the specified document.
API
The standard Painless API is available.
Example
To run this example, first follow the steps in context examples.
To sort results by the length of the theatre
field, submit the following query:
GET /_search
{
"query" : {
"term" : { "sold" : "true" }
},
"sort" : {
"_script" : {
"type" : "number",
"script" : {
"lang": "painless",
"source": "doc['theatre'].value.length() * params.factor",
"params" : {
"factor" : 1.1
}
},
"order" : "asc"
}
}
}
Similarity context
Use a Painless script to create a {ref}/index-modules-similarity.html[similarity] equation for scoring documents in a query.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
Variables
params
(Map
, read-only)-
User-defined parameters passed in at query-time.
weight
(float
, read-only)-
The weight as calculated by a weight script
query.boost
(float
, read-only)-
The boost value if provided by the query. If this is not provided the value is
1.0f
. field.docCount
(long
, read-only)-
The number of documents that have a value for the current field.
field.sumDocFreq
(long
, read-only)-
The sum of all terms that exist for the current field. If this is not available the value is
-1
. field.sumTotalTermFreq
(long
, read-only)-
The sum of occurrences in the index for all the terms that exist in the current field. If this is not available the value is
-1
. term.docFreq
(long
, read-only)-
The number of documents that contain the current term in the index.
term.totalTermFreq
(long
, read-only)-
The total occurrences of the current term in the index.
doc.length
(long
, read-only)-
The number of tokens the current document has in the current field. This is decoded from the stored {ref}/norms.html[norms] and may be approximate for long fields
doc.freq
(long
, read-only)-
The number of occurrences of the current term in the current document for the current field.
Note that the query
, field
, and term
variables are also available to the
weight context. They are more efficiently used
there, as they are constant for all documents.
For queries that contain multiple terms, the script is called once for each
term with that term’s calculated weight, and the results are summed. Note that some
terms might have a doc.freq
value of 0
on a document, for example if a query
uses synonyms.
Return
double
-
The similarity score for the current document.
API
The standard Painless API is available.
Weight context
Use a Painless script to create a {ref}/index-modules-similarity.html[weight] for use in a similarity script. The weight makes up the part of the similarity calculation that is independent of the document being scored, and so can be built up front and cached.
Queries that contain multiple terms calculate a separate weight for each term.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
query.boost
(float
, read-only)-
The boost value if provided by the query. If this is not provided the value is
1.0f
. field.docCount
(long
, read-only)-
The number of documents that have a value for the current field.
field.sumDocFreq
(long
, read-only)-
The sum of all terms that exist for the current field. If this is not available the value is
-1
. field.sumTotalTermFreq
(long
, read-only)-
The sum of occurrences in the index for all the terms that exist in the current field. If this is not available the value is
-1
. term.docFreq
(long
, read-only)-
The number of documents that contain the current term in the index.
term.totalTermFreq
(long
, read-only)-
The total occurrences of the current term in the index.
Return
double
-
A scoring factor used across all documents.
API
The standard Painless API is available.
Score context
Use a Painless script in a {ref}/query-dsl-function-score-query.html[function score] to apply a new score to documents returned from a query.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
doc
(Map
, read-only)-
Contains the fields of the current document. For single-valued fields, the value can be accessed via
doc['fieldname'].value
. For multi-valued fields, this returns the first value; other values can be accessed viadoc['fieldname'].get(index)
_score
(double
read-only)-
The similarity score of the current document.
Return
double
-
The score for the current document.
API
The standard Painless API is available.
Example
To run this example, first follow the steps in context examples.
The following query finds all unsold seats, with lower 'row' values scored higher.
GET /seats/_search
{
"query": {
"function_score": {
"query": {
"match": { "sold": "false" }
},
"script_score" : {
"script" : {
"source": "1.0 / doc['row'].value"
}
}
}
}
}
Field context
Use a Painless script to create a {ref}/search-request-script-fields.html[script field] to return a customized value for each document in the results of a query.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
doc
(Map
, read-only)-
Contains the fields of the specified document where each field is a
List
of values. - {ref}/mapping-source-field.html[
ctx['_source']
] (Map
) -
Contains extracted JSON in a
Map
andList
structure for the fields existing in a stored document. _score
(double
read-only)-
The original score of the specified document.
Return
Object
-
The customized value for each document.
API
The standard Painless API is available.
Example
To run this example, first follow the steps in context examples.
You can then use these two example scripts to compute custom information for each search hit and output it to two new fields.
The first script gets the doc value for the datetime
field and calls
the getDayOfWeek
function to determine the corresponding day of the week.
doc['datetime'].value.getDayOfWeek();
The second script calculates the number of actors. Actors' names are stored
as a text array in the actors
field.
params['_source']['actors'].length; (1)
-
By default, doc values are not available for text fields. However, you can still calculate the number of actors by extracting actors from
_source
. Note thatparams['_source']['actors']
is a list.
Submit the following request:
GET seats/_search
{
"query" : {
"match_all": {}
},
"script_fields" : {
"day-of-week" : {
"script" : {
"source": "doc['datetime'].value.getDayOfWeek()"
}
},
"number-of-actors" : {
"script" : {
"source": "params['_source']['actors'].length"
}
}
}
}
Filter context
Use a Painless script as a {ref}/query-dsl-script-query.html[filter] in a query to include and exclude documents.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
doc
(Map
, read-only)-
Contains the fields of the current document where each field is a
List
of values.
Return
boolean
-
Return
true
if the current document should be returned as a result of the query, andfalse
otherwise.
API
The standard Painless API is available.
Example
To run this example, first follow the steps in context examples.
This script finds all unsold documents that cost less than $18.
doc['sold'].value == false && doc['cost'].value < 18
Defining cost as a script parameter enables the cost to be configured in the script query request. For example, the following request finds all available theatre seats for evening performances that are under $18.
GET evening/_search
{
"query": {
"bool" : {
"filter" : {
"script" : {
"script" : {
"source" : "doc['sold'].value == false && doc['cost'].value < params.cost",
"params" : {
"cost" : 18
}
}
}
}
}
}
}
Minimum should match context
Use a Painless script to specify the {ref}/query-dsl-terms-set-query.html[minimum] number of terms that a specified field needs to match with for a document to be part of the query results.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
params['num_terms']
(int
, read-only)-
The number of terms specified to match with.
doc
(Map
, read-only)-
Contains the fields of the current document where each field is a
List
of values.
Return
int
-
The minimum number of terms required to match the current document.
API
The standard Painless API is available.
Example
To run this example, first follow the steps in context examples.
Imagine that you want to find seats to performances by your favorite
actors. You have a list of favorite actors in mind, and you want
to find performances where the cast includes at least a certain
number of them. terms_set
query with minimum_should_match_script
is a way to accomplish this. To make the query request more configurable,
you can define min_actors_to_see
as a script parameter.
To ensure that the parameter min_actors_to_see
doesn’t exceed
the number of favorite actors, you can use num_term`s to get
the number of actors in the list and `Math.min
to get the lesser
of the two.
Math.min(params['num_terms'], params['min_actors_to_see'])
The following request finds seats to performances with at least two of the three specified actors.
GET seats/_search
{
"query" : {
"terms_set": {
"actors" : {
"terms" : ["smith", "earns", "black"],
"minimum_should_match_script": {
"source": "Math.min(params['num_terms'], params['min_actors_to_see'])",
"params" : {
"min_actors_to_see" : 2
}
}
}
}
}
}
Metric aggregation initialization context
Use a Painless script to {ref}/search-aggregations-metrics-scripted-metric-aggregation.html[initialize] values for use in a scripted metric aggregation. An initialization script is run prior to document collection once per shard and is optional as part of the full metric aggregation.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
state
(Map
)-
Empty
Map
used to add values for use in a map script.
Side Effects
state
(Map
)-
Add values to this
Map
to for use in a map. Additional values must be of the typeMap
,List
,String
or primitive.
Return
void
-
No expected return value.
API
The standard Painless API is available.
Metric aggregation map context
Use a Painless script to {ref}/search-aggregations-metrics-scripted-metric-aggregation.html[map] values for use in a scripted metric aggregation. A map script is run once per collected document following an optional initialization script and is required as part of a full metric aggregation.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
state
(Map
)-
Map
used to add values for processing in a combine script or to be returned from the aggregation. doc
(Map
, read-only)-
Contains the fields of the current document where each field is a
List
of values. _score
(double
read-only)-
The similarity score of the current document.
Side Effects
state
(Map
)-
Use this
Map
to add values for processing in a combine script. Additional values must be of the typeMap
,List
,String
or primitive. The samestate
Map
is shared between all aggregated documents on a given shard. If an initialization script is provided as part of the aggregation then values added from the initialization script are available. If no combine script is specified, values must be directly stored instate
in a usable form. If no combine script and no reduce script are specified, thestate
values are used as the result.
Return
void
-
No expected return value.
API
The standard Painless API is available.
Metric aggregation combine context
Use a Painless script to {ref}/search-aggregations-metrics-scripted-metric-aggregation.html[combine] values for use in a scripted metric aggregation. A combine script is run once per shard following a map script and is optional as part of a full metric aggregation.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
state
(Map
)-
Map
with values available from the prior map script.
Return
List
,Map
,String
, or primitive-
A value collected for use in a reduce script. If no reduce script is specified, the value is used as part of the result.
API
The standard Painless API is available.
Metric aggregation reduce context
Use a Painless script to {ref}/search-aggregations-metrics-scripted-metric-aggregation.html[reduce] values to produce the result of a scripted metric aggregation. A reduce script is run once on the coordinating node following a combine script (or a map script if no combine script is specified) and is optional as part of a full metric aggregation.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
states
(Map
)-
Map
with values available from the prior combine script (or a map script if no combine script is specified).
Return
List
,Map
,String
, or primitive-
A value used as the result.
API
The standard Painless API is available.
Bucket script aggregation context
Use a Painless script in an
{ref}/search-aggregations-pipeline-bucket-script-aggregation.html[bucket_script
pipeline aggregation]
to calculate a value as a result in a bucket.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query. The parameters include values defined as part of the
buckets_path
.
Return
- numeric
-
The calculated value as the result.
API
The standard Painless API is available.
Example
To run this example, first follow the steps in context examples.
The painless context in a bucket_script
aggregation provides a params
map. This map contains both
user-specified custom values, as well as the values from other aggregations specified in the buckets_path
property.
This example takes the values from a min and max aggregation, calculates the difference, and adds the user-specified base_cost to the result:
(params.max - params.min) + params.base_cost
Note that the values are extracted from the params
map. In context, the aggregation looks like this:
GET /seats/_search
{
"size": 0,
"aggs": {
"theatres": {
"terms": {
"field": "theatre",
"size": 10
},
"aggs": {
"min_cost": {
"min": {
"field": "cost"
}
},
"max_cost": {
"max": {
"field": "cost"
}
},
"spread_plus_base": {
"bucket_script": {
"buckets_path": { (1)
"min": "min_cost",
"max": "max_cost"
},
"script": {
"params": {
"base_cost": 5 (2)
},
"source": "(params.max - params.min) + params.base_cost"
}
}
}
}
}
}
}
-
The
buckets_path
points to two aggregations (min_cost
,max_cost
) and addsmin
/max
variables to theparams
map -
The user-specified
base_cost
is also added to the script’sparams
map
Bucket selector aggregation context
Use a Painless script in an
{ref}/search-aggregations-pipeline-bucket-selector-aggregation.html[bucket_selector
aggregation]
to determine if a bucket should be retained or filtered out.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query. The parameters include values defined as part of the
buckets_path
.
Return
- boolean
-
True if the the bucket should be retained, false if the bucket should be filtered out.
API
To run this example, first follow the steps in context examples.
The painless context in a bucket_selector
aggregation provides a params
map. This map contains both
user-specified custom values, as well as the values from other aggregations specified in the buckets_path
property.
Unlike some other aggregation contexts, the bucket_selector
context must return a boolean true
or false
.
This example finds the max of each bucket, adds a user-specified base_cost, and retains all of the
buckets that are greater than 10
.
params.max + params.base_cost > 10
Note that the values are extracted from the params
map. The script is in the form of an expression
that returns true
or false
. In context, the aggregation looks like this:
GET /seats/_search
{
"size": 0,
"aggs": {
"theatres": {
"terms": {
"field": "theatre",
"size": 10
},
"aggs": {
"max_cost": {
"max": {
"field": "cost"
}
},
"filtering_agg": {
"bucket_selector": {
"buckets_path": { (1)
"max": "max_cost"
},
"script": {
"params": {
"base_cost": 5 (2)
},
"source": "params.max + params.base_cost > 10"
}
}
}
}
}
}
}
-
The
buckets_path
points to the max aggregations (max_cost
) and addsmax
variables to theparams
map -
The user-specified
base_cost
is also added to theparams
map
Analysis Predicate Context
Use a painless script to determine whether or not the current token in an analysis chain matches a predicate.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
token.term
(CharSequence
, read-only)-
The characters of the current token
token.position
(int
, read-only)-
The position of the current token
token.positionIncrement
(int
, read-only)-
The position increment of the current token
token.positionLength
(int
, read-only)-
The position length of the current token
token.startOffset
(int
, read-only)-
The start offset of the current token
token.endOffset
(int
, read-only)-
The end offset of the current token
token.type
(String
, read-only)-
The type of the current token
token.keyword
('boolean`, read-only)-
Whether or not the current token is marked as a keyword
Return
boolean
-
Whether or not the current token matches the predicate
API
The standard Painless API is available.
Watcher condition context
Use a Painless script as a {ref}/condition-script.html[watch condition] that determines whether to execute a watch or a particular action within a watch. Condition scripts return a Boolean value to indicate the status of the condition.
The following variables are available in all watcher contexts.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
ctx['watch_id']
(String
, read-only)-
The id of the watch.
ctx['id']
(String
, read-only)-
The server generated unique identifer for the run watch.
ctx['metadata']
(Map
, read-only)-
Metadata can be added to the top level of the watch definition. This is user defined and is typically used to consolidate duplicate values in a watch.
ctx['execution_time']
(ZonedDateTime
, read-only)-
The time the watch began execution.
ctx['trigger']['scheduled_time']
(ZonedDateTime
, read-only)-
The scheduled trigger time for the watch. This is the time the watch should be executed.
ctx['trigger']['triggered_time']
(ZonedDateTime
, read-only)-
The actual trigger time for the watch. This is the time the watch was triggered for execution.
ctx['payload']
(Map
, read-only)-
The accessible watch data based upon the {ref}/input.html[watch input].
API
The standard Painless API is available.
To run this example, first follow the steps in context examples.
Return
boolean
-
Expects
true
if the condition is met, andfalse
if it is not.
API
The standard Painless API is available.
Example
POST _watcher/watch/_execute
{
"watch" : {
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : {
"term": { "sold": "true"}
},
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"condition" : {
"script" :
"""
return ctx.payload.aggregations.theatres.buckets.stream() (1)
.filter(theatre -> theatre.money.value < 15000 ||
theatre.money.value > 50000) (2)
.count() > 0 (3)
"""
},
"actions" : {
"my_log" : {
"logging" : {
"text" : "The output of the search was : {{ctx.payload.aggregations.theatres.buckets}}"
}
}
}
}
}
-
The Java Stream API is used in the condition. This API allows manipulation of the elements of the list in a pipeline.
-
The stream filter removes items that do not meet the filter criteria.
-
If there is at least one item in the list, the condition evaluates to true and the watch is executed.
The following action condition script controls execution of the my_log action based on the value of the seats sold for the plays in the data set. The script aggregates the total sold seats for each play and returns true if there is at least one play that has sold over $50,000.
POST _watcher/watch/_execute
{
"watch" : {
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : {
"term": { "sold": "true"}
},
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"actions" : {
"my_log" : {
"condition": { (1)
"script" :
"""
return ctx.payload.aggregations.theatres.buckets.stream()
.anyMatch(theatre -> theatre.money.value > 50000) (2)
"""
},
"logging" : {
"text" : "At least one play has grossed over $50,000: {{ctx.payload.aggregations.theatres.buckets}}"
}
}
}
}
}
This example uses a nearly identical condition as the previous example. The differences below are subtle and are worth calling out.
-
The location of the condition is no longer at the top level, but is within an individual action.
-
Instead of a filter,
anyMatch
is used to return a boolean value
The following example shows scripted watch and action conditions within the context of a complete watch. This watch also uses a scripted transform.
POST _watcher/watch/_execute
{
"watch" : {
"metadata" : { "high_threshold": 50000, "low_threshold": 15000 },
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : {
"term": { "sold": "true"}
},
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"condition" : {
"script" :
"""
return ctx.payload.aggregations.theatres.buckets.stream()
.anyMatch(theatre -> theatre.money.value < ctx.metadata.low_threshold ||
theatre.money.value > ctx.metadata.high_threshold)
"""
},
"transform" : {
"script":
"""
return [
'money_makers': ctx.payload.aggregations.theatres.buckets.stream()
.filter(t -> {
return t.money.value > ctx.metadata.high_threshold
})
.map(t -> {
return ['play': t.key, 'total_value': t.money.value ]
}).collect(Collectors.toList()),
'duds' : ctx.payload.aggregations.theatres.buckets.stream()
.filter(t -> {
return t.money.value < ctx.metadata.low_threshold
})
.map(t -> {
return ['play': t.key, 'total_value': t.money.value ]
}).collect(Collectors.toList())
]
"""
},
"actions" : {
"log_money_makers" : {
"condition": {
"script" : "return ctx.payload.money_makers.size() > 0"
},
"transform": {
"script" :
"""
def formatter = NumberFormat.getCurrencyInstance();
return [
'plays_value': ctx.payload.money_makers.stream()
.map(t-> formatter.format(t.total_value) + ' for the play ' + t.play)
.collect(Collectors.joining(", "))
]
"""
},
"logging" : {
"text" : "The following plays contain the highest grossing total income: {{ctx.payload.plays_value}}"
}
},
"log_duds" : {
"condition": {
"script" : "return ctx.payload.duds.size() > 0"
},
"transform": {
"script" :
"""
def formatter = NumberFormat.getCurrencyInstance();
return [
'plays_value': ctx.payload.duds.stream()
.map(t-> formatter.format(t.total_value) + ' for the play ' + t.play)
.collect(Collectors.joining(", "))
]
"""
},
"logging" : {
"text" : "The following plays need more advertising due to their low total income: {{ctx.payload.plays_value}}"
}
}
}
}
}
The following example shows the use of metadata and transforming dates into a readable format.
POST _watcher/watch/_execute
{
"watch" : {
"metadata" : { "min_hits": 10000 },
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : {
"term": { "sold": "true"}
},
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"condition" : {
"script" :
"""
return ctx.payload.hits.total > ctx.metadata.min_hits
"""
},
"transform" : {
"script" :
"""
def theDate = ZonedDateTime.ofInstant(ctx.execution_time.toInstant(), ctx.execution_time.getZone());
return ['human_date': DateTimeFormatter.RFC_1123_DATE_TIME.format(theDate),
'aggregations': ctx.payload.aggregations]
"""
},
"actions" : {
"my_log" : {
"logging" : {
"text" : "The watch was successfully executed on {{ctx.payload.human_date}} and contained {{ctx.payload.aggregations.theatres.buckets.size}} buckets"
}
}
}
}
}
Watcher transform context
Use a Painless script as a {ref}/transform-script.html[watch transform] to transform a payload into a new payload for further use in the watch. Transform scripts return an Object value of the new payload.
The following variables are available in all watcher contexts.
Variables
params
(Map
, read-only)-
User-defined parameters passed in as part of the query.
ctx['watch_id']
(String
, read-only)-
The id of the watch.
ctx['id']
(String
, read-only)-
The server generated unique identifer for the run watch.
ctx['metadata']
(Map
, read-only)-
Metadata can be added to the top level of the watch definition. This is user defined and is typically used to consolidate duplicate values in a watch.
ctx['execution_time']
(ZonedDateTime
, read-only)-
The time the watch began execution.
ctx['trigger']['scheduled_time']
(ZonedDateTime
, read-only)-
The scheduled trigger time for the watch. This is the time the watch should be executed.
ctx['trigger']['triggered_time']
(ZonedDateTime
, read-only)-
The actual trigger time for the watch. This is the time the watch was triggered for execution.
ctx['payload']
(Map
, read-only)-
The accessible watch data based upon the {ref}/input.html[watch input].
API
The standard Painless API is available.
To run this example, first follow the steps in context examples.
Return
Object
-
The new payload.
API
The standard Painless API is available.
Example
POST _watcher/watch/_execute
{
"watch" : {
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : { "term": { "sold": "true"} },
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"transform" : {
"script":
"""
return [
'money_makers': ctx.payload.aggregations.theatres.buckets.stream() (1)
.filter(t -> { (2)
return t.money.value > 50000
})
.map(t -> { (3)
return ['play': t.key, 'total_value': t.money.value ]
}).collect(Collectors.toList()), (4)
'duds' : ctx.payload.aggregations.theatres.buckets.stream() (5)
.filter(t -> {
return t.money.value < 15000
})
.map(t -> {
return ['play': t.key, 'total_value': t.money.value ]
}).collect(Collectors.toList())
]
"""
},
"actions" : {
"my_log" : {
"logging" : {
"text" : "The output of the payload was transformed to {{ctx.payload}}"
}
}
}
}
}
-
The Java Stream API is used in the transform. This API allows manipulation of the elements of the list in a pipeline.
-
The stream filter removes items that do not meet the filter criteria.
-
The stream map transforms each element into a new object.
-
The collector reduces the stream to a
java.util.List
. -
This is done again for the second set of values in the transform.
The following action transform changes each value in the mod_log action into a String
.
This transform does not change the values in the unmod_log action.
POST _watcher/watch/_execute
{
"watch" : {
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : {
"term": { "sold": "true"}
},
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"actions" : {
"mod_log" : {
"transform": { (1)
"script" :
"""
def formatter = NumberFormat.getCurrencyInstance();
return [
'msg': ctx.payload.aggregations.theatres.buckets.stream()
.map(t-> formatter.format(t.money.value) + ' for the play ' + t.key)
.collect(Collectors.joining(", "))
]
"""
},
"logging" : {
"text" : "The output of the payload was transformed to: {{ctx.payload.msg}}"
}
},
"unmod_log" : { (2)
"logging" : {
"text" : "The output of the payload was not transformed and this value should not exist: {{ctx.payload.msg}}"
}
}
}
}
}
This example uses the streaming API in a very similar manner. The differences below are subtle and worth calling out.
-
The location of the transform is no longer at the top level, but is within an individual action.
-
A second action that does not transform the payload is given for reference.
The following example shows scripted watch and action transforms within the context of a complete watch. This watch also uses a scripted condition.
POST _watcher/watch/_execute
{
"watch" : {
"metadata" : { "high_threshold": 50000, "low_threshold": 15000 },
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : {
"term": { "sold": "true"}
},
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"condition" : {
"script" :
"""
return ctx.payload.aggregations.theatres.buckets.stream()
.anyMatch(theatre -> theatre.money.value < ctx.metadata.low_threshold ||
theatre.money.value > ctx.metadata.high_threshold)
"""
},
"transform" : {
"script":
"""
return [
'money_makers': ctx.payload.aggregations.theatres.buckets.stream()
.filter(t -> {
return t.money.value > ctx.metadata.high_threshold
})
.map(t -> {
return ['play': t.key, 'total_value': t.money.value ]
}).collect(Collectors.toList()),
'duds' : ctx.payload.aggregations.theatres.buckets.stream()
.filter(t -> {
return t.money.value < ctx.metadata.low_threshold
})
.map(t -> {
return ['play': t.key, 'total_value': t.money.value ]
}).collect(Collectors.toList())
]
"""
},
"actions" : {
"log_money_makers" : {
"condition": {
"script" : "return ctx.payload.money_makers.size() > 0"
},
"transform": {
"script" :
"""
def formatter = NumberFormat.getCurrencyInstance();
return [
'plays_value': ctx.payload.money_makers.stream()
.map(t-> formatter.format(t.total_value) + ' for the play ' + t.play)
.collect(Collectors.joining(", "))
]
"""
},
"logging" : {
"text" : "The following plays contain the highest grossing total income: {{ctx.payload.plays_value}}"
}
},
"log_duds" : {
"condition": {
"script" : "return ctx.payload.duds.size() > 0"
},
"transform": {
"script" :
"""
def formatter = NumberFormat.getCurrencyInstance();
return [
'plays_value': ctx.payload.duds.stream()
.map(t-> formatter.format(t.total_value) + ' for the play ' + t.play)
.collect(Collectors.joining(", "))
]
"""
},
"logging" : {
"text" : "The following plays need more advertising due to their low total income: {{ctx.payload.plays_value}}"
}
}
}
}
}
The following example shows the use of metadata and transforming dates into a readable format.
POST _watcher/watch/_execute
{
"watch" : {
"metadata" : { "min_hits": 10000 },
"trigger" : { "schedule" : { "interval" : "24h" } },
"input" : {
"search" : {
"request" : {
"indices" : [ "seats" ],
"body" : {
"query" : {
"term": { "sold": "true"}
},
"aggs" : {
"theatres" : {
"terms" : { "field" : "play" },
"aggs" : {
"money" : {
"sum": { "field" : "cost" }
}
}
}
}
}
}
}
},
"condition" : {
"script" :
"""
return ctx.payload.hits.total > ctx.metadata.min_hits
"""
},
"transform" : {
"script" :
"""
def theDate = ZonedDateTime.ofInstant(ctx.execution_time.toInstant(), ctx.execution_time.getZone());
return ['human_date': DateTimeFormatter.RFC_1123_DATE_TIME.format(theDate),
'aggregations': ctx.payload.aggregations]
"""
},
"actions" : {
"my_log" : {
"logging" : {
"text" : "The watch was successfully executed on {{ctx.payload.human_date}} and contained {{ctx.payload.aggregations.theatres.buckets.size}} buckets"
}
}
}
}
}