Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Use the filter if you want to narrow the item set analysis to fields of interest

### Examples [frequent-item-sets-example]

In the following examples, we use the e-commerce {{kib}} sample data set.
The following examples use the {{kib}} eCommerce sample data set.


### Aggregation with two analyzed fields and an `exclude` parameter [_aggregation_with_two_analyzed_fields_and_an_exclude_parameter]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
---
mapped_pages:
- https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-walkthrough.html
applies_to:
stack: ga
serverless: ga
products:
- id: painless
---

:::{tip}
This walkthrough is designed for users experienced with scripting and already familiar with Painless, who need direct access to Painless specifications and advanced features.

<!--
If you're new to Painless scripting or need step-by-step guidance, start with [How to write scripts](docs-content://explore-analyze/scripting/modules-scripting-using) in the Explore and Analyze section for a more accessible introduction.
-->

:::

# A brief painless walkthrough [painless-walkthrough]

To illustrate how Painless works, let’s load some hockey stats into an Elasticsearch index:
To illustrate how Painless works, let’s load some hockey stats into an {{es}} index:

```console
PUT hockey/_bulk?refresh
Expand Down Expand Up @@ -36,276 +48,10 @@ PUT hockey/_bulk?refresh
```
% TESTSETUP

## Accessing Doc Values from Painless [_accessing_doc_values_from_painless]

Document values can be accessed from a `Map` named `doc`.

For example, the following script calculates a player’s total goals. This example uses a strongly typed `int` and a `for` loop.

```console
GET hockey/_search
{
"query": {
"function_score": {
"script_score": {
"script": {
"lang": "painless",
"source": """
int total = 0;
for (int i = 0; i < doc['goals'].length; ++i) {
total += doc['goals'][i];
}
return total;
"""
}
}
}
}
}
```

Alternatively, you could do the same thing using a script field instead of a function score:

```console
GET hockey/_search
{
"query": {
"match_all": {}
},
"script_fields": {
"total_goals": {
"script": {
"lang": "painless",
"source": """
int total = 0;
for (int i = 0; i < doc['goals'].length; ++i) {
total += doc['goals'][i];
}
return total;
"""
}
}
}
}
```

The following example uses a Painless script to sort the players by their combined first and last names. The names are accessed using `doc['first'].value` and `doc['last'].value`.

```console
GET hockey/_search
{
"query": {
"match_all": {}
},
"sort": {
"_script": {
"type": "string",
"order": "asc",
"script": {
"lang": "painless",
"source": "doc['first.keyword'].value + ' ' + doc['last.keyword'].value"
}
}
}
}
```


## Missing keys [_missing_keys]

`doc['myfield'].value` throws an exception if the field is missing in a document.

For more dynamic index mappings, you may consider writing a catch equation

```
if (!doc.containsKey('myfield') || doc['myfield'].empty) { return "unavailable" } else { return doc['myfield'].value }
```


## Missing values [_missing_values]

To check if a document is missing a value, you can call `doc['myfield'].size() == 0`.


## Updating Fields with Painless [_updating_fields_with_painless]

You can also easily update fields. You access the original source for a field as `ctx._source.<field-name>`.

First, let’s look at the source data for a player by submitting the following request:

```console
GET hockey/_search
{
"query": {
"term": {
"_id": 1
}
}
}
```

To change player 1’s last name to `hockey`, simply set `ctx._source.last` to the new value:

```console
POST hockey/_update/1
{
"script": {
"lang": "painless",
"source": "ctx._source.last = params.last",
"params": {
"last": "hockey"
}
}
}
```

You can also add fields to a document. For example, this script adds a new field that contains the player’s nickname, *hockey*.

```console
POST hockey/_update/1
{
"script": {
"lang": "painless",
"source": """
ctx._source.last = params.last;
ctx._source.nick = params.nick
""",
"params": {
"last": "gaudreau",
"nick": "hockey"
}
}
}
```


## Dates [modules-scripting-painless-dates]

Date fields are exposed as `ZonedDateTime`, so they support methods like `getYear`, `getDayOfWeek` or e.g. getting milliseconds since epoch with `getMillis`. To use these in a script, leave out the `get` prefix and continue with lowercasing the rest of the method name. For example, the following returns every hockey player’s birth year:

```console
GET hockey/_search
{
"script_fields": {
"birth_year": {
"script": {
"source": "doc.born.value.year"
}
}
}
}
```


## Regular expressions [modules-scripting-painless-regex]

::::{note}
Regexes are enabled by default as the Setting `script.painless.regex.enabled` has a new option, `limited`, the default. This defaults to using regular expressions but limiting the complexity of the regular expressions. Innocuous looking regexes can have staggering performance and stack depth behavior. But still, they remain an amazingly powerful tool. In addition, to `limited`, the setting can be set to `true`, as before, which enables regular expressions without limiting them.To enable them yourself set `script.painless.regex.enabled: true` in `elasticsearch.yml`.
::::


Painless’s native support for regular expressions has syntax constructs:

* `/pattern/`: Pattern literals create patterns. This is the only way to create a pattern in painless. The pattern inside the `/’s are just [Java regular expressions](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.md). See [Pattern flags](/reference/scripting-languages/painless/painless-regexes.md#pattern-flags) for more.
* `=~`: The find operator return a `boolean`, `true` if a subsequence of the text matches, `false` otherwise.
* `==~`: The match operator returns a `boolean`, `true` if the text matches, `false` if it doesn’t.

Using the find operator (`=~`) you can update all hockey players with "b" in their last name:

```console
POST hockey/_update_by_query
{
"script": {
"lang": "painless",
"source": """
if (ctx._source.last =~ /b/) {
ctx._source.last += "matched";
} else {
ctx.op = "noop";
}
"""
}
}
```

Using the match operator (`==~`) you can update all the hockey players whose names start with a consonant and end with a vowel:

```console
POST hockey/_update_by_query
{
"script": {
"lang": "painless",
"source": """
if (ctx._source.last ==~ /[^aeiou].*[aeiou]/) {
ctx._source.last += "matched";
} else {
ctx.op = "noop";
}
"""
}
}
```

You can use the `Pattern.matcher` directly to get a `Matcher` instance and remove all of the vowels in all of their last names:

```console
POST hockey/_update_by_query
{
"script": {
"lang": "painless",
"source": "ctx._source.last = /[aeiou]/.matcher(ctx._source.last).replaceAll('')"
}
}
```

`Matcher.replaceAll` is just a call to Java’s `Matcher`'s [replaceAll](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.md#replaceAll-java.lang.String-) method so it supports `$1` and `\1` for replacements:

```console
POST hockey/_update_by_query
{
"script": {
"lang": "painless",
"source": "ctx._source.last = /n([aeiou])/.matcher(ctx._source.last).replaceAll('$1')"
}
}
```

If you need more control over replacements you can call `replaceAll` on a `CharSequence` with a `Function<Matcher, String>` that builds the replacement. This does not support `$1` or `\1` to access replacements because you already have a reference to the matcher and can get them with `m.group(1)`.

::::{important}
Calling `Matcher.find` inside of the function that builds the replacement is rude and will likely break the replacement process.
::::


This will make all of the vowels in the hockey player’s last names upper case:

```console
POST hockey/_update_by_query
{
"script": {
"lang": "painless",
"source": """
ctx._source.last = ctx._source.last.replaceAll(/[aeiou]/, m ->
m.group().toUpperCase(Locale.ROOT))
"""
}
}
```

Or you can use the `CharSequence.replaceFirst` to make the first vowel in their last names upper case:

```console
POST hockey/_update_by_query
{
"script": {
"lang": "painless",
"source": """
ctx._source.last = ctx._source.last.replaceFirst(/[aeiou]/, m ->
m.group().toUpperCase(Locale.ROOT))
"""
}
}
```

Note: all of the `_update_by_query` examples above could really do with a `query` to limit the data that they pull back. While you **could** use a [script query](/reference/query-languages/query-dsl/query-dsl-script-query.md) it wouldn’t be as efficient as using any other query because script queries aren’t able to use the inverted index to limit the documents that they have to check.
With the hockey data ingested, try out some basic operations that you can do in Painless:

- [](/reference/scripting-languages/painless/painless-walkthrough-access-doc-values.md)
- [](/reference/scripting-languages/painless/painless-walkthrough-missing-keys-or-values.md)
- [](/reference/scripting-languages/painless/painless-walkthrough-updating-fields.md)
- [](/reference/scripting-languages/painless/painless-walkthrough-dates.md)
- [](/reference/scripting-languages/painless/painless-walkthrough-regular-expressions.md)
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
---
mapped_pages:
- https://www.elastic.co/guide/en/elasticsearch/painless/current/modules-scripting-painless-dispatch.html
applies_to:
stack: ga
serverless: ga
products:
- id: painless
---

# How painless dispatches function [modules-scripting-painless-dispatch]
# Understanding method dispatching in Painless [modules-scripting-painless-dispatch]

Painless uses receiver, name, and [arity](https://en.wikipedia.org/wiki/Arity) for method dispatch. For example, `s.foo(a, b)` is resolved by first getting the class of `s` and then looking up the method `foo` with two parameters. This is different from Groovy which uses the [runtime types](https://en.wikipedia.org/wiki/Multiple_dispatch) of the parameters and Java which uses the compile time types of the parameters.
Painless uses a function dispatch mechanism based on the receiver, method name, and [arity](https://en.wikipedia.org/wiki/Arity) (number of parameters). This approach differs from Java, which dispatches based on compiled-time types, and Groovy, which uses [runtime types](https://en.wikipedia.org/wiki/Multiple_dispatch). Understanding this mechanism is fundamental when migrating scripts or interacting with Java standard library APIs from Painless, as it helps you avoid common errors and write more efficient, secure scripts.

The consequence of this that Painless doesn’t support overloaded methods like Java, leading to some trouble when it allows classes from the Java standard library. For example, in Java and Groovy, `Matcher` has two methods: `group(int)` and `group(String)`. Painless can’t allow both of these methods because they have the same name and the same number of parameters. So instead it has `group(int)` and `namedGroup(String)`.
## Key terms

Before diving into the dispatch process, here a brief definition of the main concepts:

* **Receiver:** The object or class on which the method is called. For example, in `s.foo(a, b)` the variable `s` is the receiver
* **Name:** The name of the method being invoked, such as `foo` in `s.foo(a, b)`
* **Arity:** The number of parameters the method accepts. In `s.foo(a, b)` the arity is 2
* **Dispatch:** The process of determining which method implementation to execute based on the receiver, name, and arity

:::{image} images/painless-method-dispatching.png
:alt: Flowchart showing five steps: s.foo, receiver, name, arity, and execute method
:width: 250px
:::

## Why method dispatch matters

This fundamental difference affects how you work with Java APIs in your scripts. When translating Java code to Painless, methods you expect from the standard library might have different names or behave differently. Understanding method dispatch helps you avoid common errors and write more efficient scripts, particularly when working with `def` types that benefit from this optimized resolution mechanism.

## Impact on Java standard library usage

The consequence of the different approach used by Painless is that Painless doesn’t support overload methods like Java, leading to some trouble when it allows classes from the Java standard library. For example, in Java and Groovy, `Matcher` has two methods:

* `group(int)`
* `group(string)`

Painless can’t allow both of these methods because they have the same name and the same number of parameters. Instead, it has `group(int)` and `namedGroup(String)`. If you try to call a method that is not exposed in Painless, you will get a compilation error.

This renaming pattern occurs throughout the Painless API when adapting Java standard library classes. Any methods that would conflict due to identical names and parameter counts receive distinct names in Painless to ensure unambiguous method resolution.

## Justification for this approach

We have a few justifications for this different way of dispatching methods:

1. It makes operating on `def` types simpler and, presumably, faster. Using receiver, name, and arity means that when Painless sees a call on a `def` object it can dispatch the appropriate method without having to do expensive comparisons of the types of the parameters. The same is true for invocations with `def` typed parameters.
2. It keeps things consistent. It would be genuinely weird for Painless to behave like Groovy if any `def` typed parameters were involved and Java otherwise. It’d be slow for it to behave like Groovy all the time.
3. It keeps Painless maintainable. Adding the Java or Groovy like method dispatch **feels** like it’d add a ton of complexity which’d make maintenance and other improvements much more difficult.
1. It makes operating on `def` types simpler and, presumably, faster. Using receiver, name, and arity means that when Painless sees a call on a `def` object it can dispatch the appropriate method without having to do expensive comparisons of the types of the parameters. The same is true for invocation with `def` typed parameters.
2. It keeps things consistent. It would be genuinely weird for Painless to behave like Groovy if any `def` typed parameters were involved and Java otherwise. It’d be slow for Painless to behave like Groovy all the time.
3. It keeps Painless maintainable. Adding the Java or Groovy like method dispatch feels like it’d add a lot of complexity, which would make maintenance and other improvements much more difficult.

## Next steps

For more details, view the [Painless language specification](/reference/scripting-languages/painless/painless-language-specification.md) and the [Painless API examples](/reference/scripting-languages/painless/painless-api-examples.md).

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading