diff --git a/docs/reference/aggregations/search-aggregations-bucket-frequent-item-sets-aggregation.md b/docs/reference/aggregations/search-aggregations-bucket-frequent-item-sets-aggregation.md index 38fd9a8a39d87..5ace2000a0cb4 100644 --- a/docs/reference/aggregations/search-aggregations-bucket-frequent-item-sets-aggregation.md +++ b/docs/reference/aggregations/search-aggregations-bucket-frequent-item-sets-aggregation.md @@ -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] diff --git a/docs/reference/scripting-languages/painless/brief-painless-walkthrough.md b/docs/reference/scripting-languages/painless/brief-painless-walkthrough.md index a711b4c7ad07a..a58578fe807d1 100644 --- a/docs/reference/scripting-languages/painless/brief-painless-walkthrough.md +++ b/docs/reference/scripting-languages/painless/brief-painless-walkthrough.md @@ -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. + + + +::: + # 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 @@ -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.`. - -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` 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) \ No newline at end of file diff --git a/docs/reference/scripting-languages/painless/how-painless-dispatches-function.md b/docs/reference/scripting-languages/painless/how-painless-dispatches-function.md index d146515cc76f4..d761d01ddce18 100644 --- a/docs/reference/scripting-languages/painless/how-painless-dispatches-function.md +++ b/docs/reference/scripting-languages/painless/how-painless-dispatches-function.md @@ -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). diff --git a/docs/reference/scripting-languages/painless/images/painless-compilation-process.png b/docs/reference/scripting-languages/painless/images/painless-compilation-process.png new file mode 100644 index 0000000000000..f7082e46af21d Binary files /dev/null and b/docs/reference/scripting-languages/painless/images/painless-compilation-process.png differ diff --git a/docs/reference/scripting-languages/painless/images/painless-integration-points.png b/docs/reference/scripting-languages/painless/images/painless-integration-points.png new file mode 100644 index 0000000000000..0c94972648adb Binary files /dev/null and b/docs/reference/scripting-languages/painless/images/painless-integration-points.png differ diff --git a/docs/reference/scripting-languages/painless/images/painless-method-dispatching.png b/docs/reference/scripting-languages/painless/images/painless-method-dispatching.png new file mode 100644 index 0000000000000..e366952453189 Binary files /dev/null and b/docs/reference/scripting-languages/painless/images/painless-method-dispatching.png differ diff --git a/docs/reference/scripting-languages/painless/images/painless-operator-categories.png b/docs/reference/scripting-languages/painless/images/painless-operator-categories.png new file mode 100644 index 0000000000000..7ace75d6d225c Binary files /dev/null and b/docs/reference/scripting-languages/painless/images/painless-operator-categories.png differ diff --git a/docs/reference/scripting-languages/painless/painless-analysis-predicate-context.md b/docs/reference/scripting-languages/painless/painless-analysis-predicate-context.md index cb630437806ac..a84290cf3567a 100644 --- a/docs/reference/scripting-languages/painless/painless-analysis-predicate-context.md +++ b/docs/reference/scripting-languages/painless/painless-analysis-predicate-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-analysis-predicate-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,11 @@ products: Use a painless script to determine whether or not the current token in an analysis chain matches a predicate. -**Variables** +:::{tip} +This is an advanced feature for customizing text analysis and token filtering. To learn more, refer to [Text analysis](docs-content://manage-data/data-store/text-analysis.md). +::: + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -38,12 +45,79 @@ Use a painless script to determine whether or not the current token in an analys `token.keyword` (`boolean`, read-only) : Whether or not the current token is marked as a keyword -**Return** +## Return `boolean` : Whether or not the current token matches the predicate -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +This example ensures that only properly formatted SKUs are processed. The analyzer checks that SKU codes start with "ZO" and are exactly thirteen characters long, filtering out malformed or invalid product codes that could break inventory systems or search functionality. + +```json +PUT sku_analysis +{ + "settings": { + "analysis": { + "analyzer": { + "sku_search_analyzer": { + "tokenizer": "whitespace", + "filter": [ + "sku_validator" + ] + } + }, + "filter": { + "sku_validator": { + "type": "predicate_token_filter", + "script": { + "source": """ + return token.term.length() == 12 && token.term.toString().startsWith('ZO'); + """ + } + } + } + } + } +} +``` + +Test it using the following request: + +```json +GET sku_analysis/_analyze +{ + "analyzer": "sku_search_analyzer", + "text": "ZO0240302403 ZO0236402364 INVALID123 SHORT AB1234567890123" +} +``` + +Result: + +```json +{ + "tokens": [ + { + "token": "ZO0240302403", + "start_offset": 0, + "end_offset": 12, + "type": "word", + "position": 0 + }, + { + "token": "ZO0236402364", + "start_offset": 13, + "end_offset": 25, + "type": "word", + "position": 1 + } + ] +} +``` + diff --git a/docs/reference/scripting-languages/painless/painless-api-examples.md b/docs/reference/scripting-languages/painless/painless-api-examples.md index 05a696c73cb3b..4e7d1d31ea011 100644 --- a/docs/reference/scripting-languages/painless/painless-api-examples.md +++ b/docs/reference/scripting-languages/painless/painless-api-examples.md @@ -1,19 +1,68 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-execute-api.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- -# Painless API examples [painless-execute-api] +# Painless execute API [painless-execute-api] ::::{warning} This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. :::: - The Painless execute API runs a script and returns a result. +## Description + +Use this API to build and test scripts, such as when defining a script for a [runtime field](docs-content://manage-data/data-store/mapping/runtime-fields.md). This API requires very few dependencies and is especially useful if you don’t have the required permissions to write documents to a cluster. + +## How it works + +The Painless execute API runs scripts in an isolated execution environment and returns results for development and testing purposes. The request body must include a `script` object, and additional parameters may be required depending on the selected context. For example, providing a `context_setup` parameter allows you to add a document to execute the script against. + +The API executes the script within the {{es}} Painless engine and does not perform write operations or modify indexed documents. + +## Supported contexts + +The API supports the following contexts for script execution: + +### Test context (`test`) + +Test scripts without additional parameters or context settings. Only the `params` variable is available, and results are always converted to a string. This is the default context when no other is specified. + +### Filter context (`filter`) + +Test scripts that return boolean values, such as those used in script queries for field comparisons or value checks. Scripts run as if inside a `script` query and have access to `_source`, stored fields, and doc values of the provided document. + +### Score context (`score`) + +Validate custom scoring functions for `function_score` queries, such as ranking calculations based on field values. Scripts run as if inside a `script_score` function and can access document fields for scoring calculations. + +### Field contexts + +Test field context scripts using the `emit` function for runtime fields: + +* `boolean_field`: Return `true` or `false` values for boolean field types. +* `date_field`: Process and emit date values as long timestamps. +* `double_field`: Return sorted lists of double numeric values. +* `geo_point_field`: Emit latitude and longitude coordinates for geo-point fields. +* `ip_fields`: Process and return IP addresses from text data. +* `keyword_field`: Return sorted lists of string values. +* `long_field`: Return sorted lists of long numeric values. +* `composite_field`: Return maps of values for composite runtime fields. + +## When to use the Painless execute API + +Use the Painless execute API in the following scenarios: + +* To test script syntax and logic before deploying to production. +* When you do not have write permissions on a live cluster. +* To validate script behavior using specific test data. + ## {{api-request-title}} [painless-execute-api-request] `POST /_scripts/painless/_execute` @@ -856,6 +905,3 @@ The response includes the values that the script emitted: } } ``` - - - diff --git a/docs/reference/scripting-languages/painless/painless-bucket-script-agg-context.md b/docs/reference/scripting-languages/painless/painless-bucket-script-agg-context.md index 54f79b2ef261c..ea8b842d71aa9 100644 --- a/docs/reference/scripting-languages/painless/painless-bucket-script-agg-context.md +++ b/docs/reference/scripting-languages/painless/painless-bucket-script-agg-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-bucket-script-agg-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -28,50 +31,38 @@ The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painle ## Example [_example] -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -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. +The following request is useful for identifying high-value markets by comparing average order values across countries. It groups orders by country and calculates metrics for each country, including `total_revenue` and `order_count`, then uses a bucket script to compute `revenue_per_order` for performance analysis. -This example takes the values from a min and max aggregation, calculates the difference, and adds the user-specified base_cost to the result: - -```painless -(params.max - params.min) + params.base_cost -``` - -Note that the values are extracted from the `params` map. In context, the aggregation looks like this: - -```console -GET /seats/_search +```json +GET kibana_sample_data_ecommerce/_search { "size": 0, "aggs": { - "theatres": { + "countries": { "terms": { - "field": "theatre", - "size": 10 + "field": "geoip.country_iso_code" }, "aggs": { - "min_cost": { - "min": { - "field": "cost" + "total_revenue": { + "sum": { + "field": "taxful_total_price" } }, - "max_cost": { - "max": { - "field": "cost" + "order_count": { + "value_count": { + "field": "order_id" } }, - "spread_plus_base": { + "revenue_per_order": { "bucket_script": { - "buckets_path": { <1> - "min": "min_cost", - "max": "max_cost" + "buckets_path": { + "revenue": "total_revenue", + "orders": "order_count" }, "script": { - "params": { - "base_cost": 5 <2> - }, - "source": "(params.max - params.min) + params.base_cost" + "source": "params.revenue / params.orders" } } } @@ -80,10 +71,3 @@ GET /seats/_search } } ``` -% TEST[setup:seats] - -1. The `buckets_path` points to two aggregations (`min_cost`, `max_cost`) and adds `min`/`max` variables to the `params` map -2. The user-specified `base_cost` is also added to the script’s `params` map - - - diff --git a/docs/reference/scripting-languages/painless/painless-bucket-selector-agg-context.md b/docs/reference/scripting-languages/painless/painless-bucket-selector-agg-context.md index 449366536f456..763259b313c8b 100644 --- a/docs/reference/scripting-languages/painless/painless-bucket-selector-agg-context.md +++ b/docs/reference/scripting-languages/painless/painless-bucket-selector-agg-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-bucket-selector-agg-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -28,46 +31,32 @@ The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painle ## Example [_example_2] -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -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. +The following request filters out low-performing manufacturers and focuses only on brands with significant sales volume. The query groups orders by manufacturer, counts total orders for each brand, then uses a bucket selector to show only manufacturers with 50 or more orders. -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`. - -```painless -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: - -```console -GET /seats/_search +```json +GET kibana_sample_data_ecommerce/_search { "size": 0, "aggs": { - "theatres": { + "manufacturers": { "terms": { - "field": "theatre", - "size": 10 + "field": "manufacturer.keyword" }, "aggs": { - "max_cost": { - "max": { - "field": "cost" + "total_orders": { + "value_count": { + "field": "order_id" } }, - "filtering_agg": { + "high_volume_filter": { "bucket_selector": { - "buckets_path": { <1> - "max": "max_cost" + "buckets_path": { + "order_count": "total_orders" }, "script": { - "params": { - "base_cost": 5 <2> - }, - "source": "params.max + params.base_cost > 10" + "source": "params.order_count >= 50" } } } @@ -76,10 +65,3 @@ GET /seats/_search } } ``` -% TEST[setup:seats] - -1. The `buckets_path` points to the max aggregations (`max_cost`) and adds `max` variables to the `params` map -2. The user-specified `base_cost` is also added to the `params` map - - - diff --git a/docs/reference/scripting-languages/painless/painless-casting.md b/docs/reference/scripting-languages/painless/painless-casting.md index 3a35626aeea3a..706afd8bdff63 100644 --- a/docs/reference/scripting-languages/painless/painless-casting.md +++ b/docs/reference/scripting-languages/painless/painless-casting.md @@ -1,13 +1,17 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-casting.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Casting [painless-casting] -A cast converts the value of an original type to the equivalent value of a target type. An implicit cast infers the target type and automatically occurs during certain [operations](/reference/scripting-languages/painless/painless-operators.md). An explicit cast specifies the target type and forcefully occurs as its own operation. Use the `cast operator '()'` to specify an explicit cast. +A cast converts the value of an original type to the equivalent value of a target type. An implicit cast infers the target type and automatically occurs during certain [operations](/reference/scripting-languages/painless/painless-operators.md). An explicit cast specifies the target type and forcefully occurs as its own operation. Use the `cast operator '()'` to specify an explicit cast. You can also check out the [type casting tutorial](docs-content://explore-analyze/scripting/modules-scripting-type-casting-tutorial.md) for related examples, and for help with troubleshooting refer to "type casting issues" in the Troubleshooting section. + Refer to the [cast table](#allowed-casts) for a quick reference on all allowed casts. @@ -24,7 +28,7 @@ cast: '(' TYPE ')' expression **Examples** -* Valid casts. +* Valid casts ```painless int i = (int)5L; <1> @@ -56,7 +60,7 @@ The allowed casts for values of each numeric type are shown as a row in the foll **Examples** -* Valid numeric type casts. +* Valid numeric type casts ```painless int a = 1; <1> @@ -70,7 +74,7 @@ The allowed casts for values of each numeric type are shown as a row in the foll 3. declare `short c`; load from `b` → `long 1`; explicit cast `long 1` to `short 1` → `short 1`; store `short 1` value to `c` 4. declare `double e`; load from `a` → `int 1`; explicit cast `int 1` to `double 1.0`; store `double 1.0` to `e`; (note the explicit cast is extraneous since an implicit cast is valid) -* Invalid numeric type casts resulting in errors. +* Invalid numeric type casts resulting in errors ```painless int a = 1.0; // error <1> @@ -90,7 +94,7 @@ A [reference type](/reference/scripting-languages/painless/painless-types.md#ref **Examples** -* Valid reference type casts. +* Valid reference type casts ```painless List x; <1> @@ -106,7 +110,7 @@ A [reference type](/reference/scripting-languages/painless/painless-types.md#ref 4. load from `x` → `List reference`; explicit cast `List reference` to `ArrayList reference` → `ArrayList reference`; store `ArrayList reference` to `y`; 5. load from `y` → `ArrayList reference`; explicit cast `ArrayList reference` to `List reference` → `List reference`; store `List reference` to `x`; (note the explicit cast is extraneous, and an implicit cast is valid) -* Invalid reference type casts resulting in errors. +* Invalid reference type casts resulting in errors ```painless List x = new ArrayList(); <1> @@ -130,7 +134,7 @@ An implicit or explicit cast from an original `def` type value to any target typ **Examples** -* Valid dynamic type casts with any original type to a target `def` type. +* Valid dynamic type casts with any original type to a target `def` type ```painless def d0 = 3; <1> @@ -146,7 +150,7 @@ An implicit or explicit cast from an original `def` type value to any target typ 4. declare `def d1`; load from `o` → `Object reference`; implicit cast `Object reference` to `def` → `def`; store `def` to `d1` 5. declare `int i`; load from `d1` → `def`; implicit cast `def` to `HashMap reference` → HashMap reference`; call `size` on `HashMap reference` → `int 0`; store `int 0` to `i`; (note `def` was implicit cast to `HashMap reference` since `HashMap` is the child-most descendant type value that the `def` type value represents) -* Valid dynamic type casts with an original `def` type to any target type. +* Valid dynamic type casts with an original `def` type to any target type ```painless def d = 1.0; <1> @@ -164,7 +168,7 @@ An implicit or explicit cast from an original `def` type value to any target typ 5. allocate `ArrayList` instance → `ArrayList reference`; store `ArrayList reference` to `d`; (note the switch in the type `d` represents from `int` to `ArrayList`) 6. declare `List l`; load from `d` → `def`; implicit cast `def` to `ArrayList reference` → `ArrayList reference`; implicit cast `ArrayList reference` to `List reference` → `List reference`; store `List reference` to `l` -* Invalid dynamic type casts resulting in errors. +* Invalid dynamic type casts resulting in errors ```painless def d = 1; <1> @@ -191,7 +195,7 @@ Use the cast operator to convert a [`String` type](/reference/scripting-language **Examples** -* Casting string literals into `char` type values. +* Casting string literals into `char` type values ```painless char c = (char)"C"; <1> @@ -201,7 +205,7 @@ Use the cast operator to convert a [`String` type](/reference/scripting-language 1. declare `char c`; explicit cast `String "C"` to `char C` → `char C`; store `char C` to `c` 2. explicit cast `String 'c'` to `char c` → `char c`; store `char c` to `c` -* Casting a `String` reference into a `char` type value. +* Casting a `String` reference into a `char` type value ```painless String s = "s"; <1> @@ -219,7 +223,7 @@ Use the cast operator to convert a [`char` type](/reference/scripting-languages/ **Examples** -* Casting a `String` reference into a `char` type value. +* Casting a `String` reference into a `char` type value ```painless char c = 65; <1> @@ -249,7 +253,7 @@ Explicit boxing/unboxing is not allowed. Use the reference type API to explicitl **Examples** -* Uses of implicit boxing/unboxing. +* Uses of implicit boxing/unboxing ```painless List l = new ArrayList(); <1> @@ -289,7 +293,7 @@ Promotion is when a single value is implicitly cast to a certain type or multipl **Examples** -* Uses of promotion. +* Uses of promotion ```painless double d = 2 + 2.0; <1> diff --git a/docs/reference/scripting-languages/painless/painless-comments.md b/docs/reference/scripting-languages/painless/painless-comments.md index 5bdb4fca94ae8..9909e9178fdcd 100644 --- a/docs/reference/scripting-languages/painless/painless-comments.md +++ b/docs/reference/scripting-languages/painless/painless-comments.md @@ -1,52 +1,77 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-comments.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Comments [painless-comments] -Use a comment to annotate or explain code within a script. Use the `//` token anywhere on a line to specify a single-line comment. All characters from the `//` token to the end of the line are ignored. Use an opening `/*` token and a closing `*/` token to specify a multi-line comment. Multi-line comments can start anywhere on a line, and all characters in between the `/*` token and `*/` token are ignored. A comment is included anywhere within a script. +Comments are used to explain or annotate code and make it more readable. They are ignored when the script is executed, allowing you to add notes or temporarily disable code sections without affecting the functionality. -**Grammar** +Painless supports two types of comments: single-line comments and multi-line comments. -```text +## Single-line comments + +Single-line comments start with `//` and continue to the end of the line. Everything after `//` on that line is ignored by the compiler. + +### Grammar + +``` SINGLE_LINE_COMMENT: '//' .*? [\n\r]; -MULTI_LINE_COMMENT: '/*' .*? '*/'; ``` -**Examples** +### Example -* Single-line comments. +``` +// This is a single-line comment +int value = 10; +int price = 100; // Comment at the end of a line +``` - ```painless - // single-line comment +## Multi-line comments - int value; // single-line comment - ``` +Multi-line comments start with `/*` and end with `*/`. Everything between these markers is ignored, even if it spans multiple lines. -* Multi-line comments. +### Grammar - ```painless - /* multi- - line - comment */ +``` +MULTI_LINE_COMMENT: '/*' .*? '*/'; +``` - int value; /* multi- - line - comment */ value = 0; +### Example + +``` +/* This is a + multi-line comment + spanning several lines */ + +int total = price * quantity; - int value; /* multi-line - comment */ +/* You can also use multi-line comments + to temporarily disable code blocks: + +int debugValue = 0; +debugValue = calculateDebug(); +*/ + +int result = /* inline comment */ calculateTotal(); + +``` - /* multi-line - comment */ int value; +## Best practices - int value; /* multi-line - comment */ value = 0; +Use comments to: - int value; /* multi-line comment */ value = 0; - ``` +* Explain complex logic or business rules +* Document function parameters and return values +* Provide context data transformations +* Temporarily disable code during development +:::{tip} +Good comments explain _why_ something is done, not just _what_ is being done. The code itself should be clear enough to show what it does. +::: diff --git a/docs/reference/scripting-languages/painless/painless-context-examples.md b/docs/reference/scripting-languages/painless/painless-context-examples.md index f345d96b68d9c..1e6bedad91d3c 100644 --- a/docs/reference/scripting-languages/painless/painless-context-examples.md +++ b/docs/reference/scripting-languages/painless/painless-context-examples.md @@ -1,118 +1,223 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-context-examples.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- -# Context example data [painless-context-examples] - -Complete the following steps to index the `seat` sample data into {{es}}. You can run any of the context examples against this sample data after you configure it. - -Each document in the `seat` data contains the following fields: - -`theatre` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)) -: The name of the theater the play is in. - -`play` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)) -: The name of the play. - -`actors` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)) -: A list of actors in the play. - -`date` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)) -: The date of the play as a keyword. - -`time` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)) -: The time of the play as a keyword. - -`cost` ([`long`](/reference/elasticsearch/mapping-reference/number.md)) -: The cost of the ticket for the seat. - -`row` ([`long`](/reference/elasticsearch/mapping-reference/number.md)) -: The row of the seat. - -`number` ([`long`](/reference/elasticsearch/mapping-reference/number.md)) -: The number of the seat within a row. - -`sold` ([`boolean`](/reference/elasticsearch/mapping-reference/boolean.md)) -: Whether or not the seat is sold. - -`datetime` ([`date`](/reference/elasticsearch/mapping-reference/date.md)) -: The date and time of the play as a date object. - -## Prerequisites [_prerequisites] - -Start an [{{es}} instance](docs-content://deploy-manage/deploy/self-managed/installing-elasticsearch.md), and then access the [Console](docs-content://explore-analyze/query-filter/tools/console.md) in {{kib}}. - - -## Configure the `seat` sample data [_configure_the_seat_sample_data] - -1. From the {{kib}} Console, create [mappings](docs-content://manage-data/data-store/mapping.md) for the sample data: - - ```console - PUT /seats - { - "mappings": { - "properties": { - "theatre": { "type": "keyword" }, - "play": { "type": "keyword" }, - "actors": { "type": "keyword" }, - "date": { "type": "keyword" }, - "time": { "type": "keyword" }, - "cost": { "type": "double" }, - "row": { "type": "integer" }, - "number": { "type": "integer" }, - "sold": { "type": "boolean" }, - "datetime": { "type": "date" } - } - } +# Context example data (eCommerce) [painless-context-examples] + +Complete the following installation steps to index the sample eCommerce orders data into {{es}}. You can run any of the context examples against this sample data after you've configured it. + +Each document in this dataset represents a complete eCommerce order. Every order contains complete transaction data including product details, pricing information, customer data, and geographic location. Orders might include multiple products, with product-specific information stored as values within individual fields. + +## Install the eCommerce sample data [painless-sample-data-install] + +1. Go to **Integrations** and search for **Sample Data**. +2. On the **Sample data** page, expand the **Other sample data sets**. + +3. Click **Add data** to install **Sample eCommerce orders**. + + +4. **Verify Installation:** Navigate to **Analytics \> Discover** and select the `kibana_sample_data_ecommerce` data view. You should see eCommerce order documents with complete customer, product, and transaction information. + + +## Sample document structure + +Here’s an example of a complete eCommerce order document with two products (basic T-shirt and boots): + +```json +{ + "order_id": 578286, + "order_date": "2025-08-13T16:53:46+00:00", + "type": "order", + "currency": "EUR", + "customer_id": 39, + "customer_full_name": "Kamal Brock", + "customer_first_name": "Kamal", + "customer_last_name": "Brock", + "customer_gender": "MALE", + "customer_phone": "", + "email": "kamal@brock-family.zzz", + "user": "kamal", + "day_of_week": "Wednesday", + "day_of_week_i": 2, + "geoip": { + "city_name": "Istanbul", + "region_name": "Istanbul", + "country_iso_code": "TR", + "continent_name": "Asia", + "location": { + "lat": 41, + "lon": 29 } - ``` - -2. Configure a script ingest processor that parses each document as {{es}} ingests the `seat` data. The following ingest script processes the `date` and `time` fields and stores the result in a `datetime` field: - - ```console - PUT /_ingest/pipeline/seats + }, + "category": [ + "Men's Clothing", + "Men's Shoes" + ], + "manufacturer": [ + "Elitelligence", + "Oceanavigations" + ], + "sku": [ + "ZO0548305483", + "ZO0256702567" + ], + "products": [ { - "description": "update datetime for seats", - "processors": [ - { - "script": { - "source": "String[] dateSplit = ctx.date.splitOnToken('-'); 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 = ctx.time.substring(0, ctx.time.length() - 2).splitOnToken(':'); 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;" - } - } - ] + "_id": "sold_product_578286_15939", + "product_id": 15939, + "product_name": "Basic T-shirt - khaki", + "category": "Men's Clothing", + "manufacturer": "Elitelligence", + "sku": "ZO0548305483", + "base_price": 7.99, + "base_unit_price": 7.99, + "price": 7.99, + "taxful_price": 7.99, + "taxless_price": 7.99, + "quantity": 1, + "discount_amount": 0, + "discount_percentage": 0, + "tax_amount": 0, + "created_on": "2016-12-21T16:53:46+00:00" + }, + { + "_id": "sold_product_578286_1844", + "product_id": 1844, + "product_name": "Boots - beige", + "category": "Men's Shoes", + "manufacturer": "Oceanavigations", + "sku": "ZO0256702567", + "base_price": 84.99, + "base_unit_price": 84.99, + "price": 84.99, + "taxful_price": 84.99, + "taxless_price": 84.99, + "quantity": 1, + "discount_amount": 0, + "discount_percentage": 0, + "tax_amount": 0, + "created_on": "2016-12-21T16:53:46+00:00" } - ``` - -3. Ingest some sample data using the `seats` ingest pipeline that you defined in the previous step. - - ```console - POST seats/_bulk?pipeline=seats&refresh=true - {"create":{"_index":"seats","_id":"1"}} - {"theatre":"Skyline","play":"Rent","actors":["James Holland","Krissy Smith","Joe Muir","Ryan Earns"],"date":"2021-4-1","time":"3:00PM","cost":37,"row":1,"number":7,"sold":false} - {"create":{"_index":"seats","_id":"2"}} - {"theatre":"Graye","play":"Rent","actors":["Dave Christmas"],"date":"2021-4-1","time":"3:00PM","cost":30,"row":3,"number":5,"sold":false} - {"create":{"_index":"seats","_id":"3"}} - {"theatre":"Graye","play":"Rented","actors":["Dave Christmas"],"date":"2021-4-1","time":"3:00PM","cost":33,"row":2,"number":6,"sold":false} - {"create":{"_index":"seats","_id":"4"}} - {"theatre":"Skyline","play":"Rented","actors":["James Holland","Krissy Smith","Joe Muir","Ryan Earns"],"date":"2021-4-1","time":"3:00PM","cost":20,"row":5,"number":2,"sold":false} - {"create":{"_index":"seats","_id":"5"}} - {"theatre":"Down Port","play":"Pick It Up","actors":["Joel Madigan","Jessica Brown","Baz Knight","Jo Hangum","Rachel Grass","Phoebe Miller"],"date":"2018-4-2","time":"8:00PM","cost":27.5,"row":3,"number":2,"sold":false} - {"create":{"_index":"seats","_id":"6"}} - {"theatre":"Down Port","play":"Harriot","actors":["Phoebe Miller","Sarah Notch","Brayden Green","Joshua Iller","Jon Hittle","Rob Kettleman","Laura Conrad","Simon Hower","Nora Blue","Mike Candlestick","Jacey Bell"],"date":"2018-8-7","time":"8:00PM","cost":30,"row":1,"number":10,"sold":false} - {"create":{"_index":"seats","_id":"7"}} - {"theatre":"Skyline","play":"Auntie Jo","actors":["Jo Hangum","Jon Hittle","Rob Kettleman","Laura Conrad","Simon Hower","Nora Blue"],"date":"2018-10-2","time":"5:40PM","cost":22.5,"row":7,"number":10,"sold":false} - {"create":{"_index":"seats","_id":"8"}} - {"theatre":"Skyline","play":"Test Run","actors":["Joe Muir","Ryan Earns","Joel Madigan","Jessica Brown"],"date":"2018-8-5","time":"7:30PM","cost":17.5,"row":11,"number":12,"sold":true} - {"create":{"_index":"seats","_id":"9"}} - {"theatre":"Skyline","play":"Sunnyside Down","actors":["Krissy Smith","Joe Muir","Ryan Earns","Nora Blue","Mike Candlestick","Jacey Bell"],"date":"2018-6-12","time":"4:00PM","cost":21.25,"row":8,"number":15,"sold":true} - {"create":{"_index":"seats","_id":"10"}} - {"theatre":"Graye","play":"Line and Single","actors":["Nora Blue","Mike Candlestick"],"date":"2018-6-5","time":"2:00PM","cost":30,"row":1,"number":2,"sold":false} - {"create":{"_index":"seats","_id":"11"}} - {"theatre":"Graye","play":"Hamilton","actors":["Lin-Manuel Miranda","Leslie Odom Jr."],"date":"2018-6-5","time":"2:00PM","cost":5000,"row":1,"number":20,"sold":true} - ``` - - + ], + "taxful_total_price": 92.98, + "taxless_total_price": 92.98, + "total_quantity": 2, + "total_unique_products": 2, + "event": { + "dataset": "sample_ecommerce" + } +} +``` + +## Field Reference + +* **Order information** + + `order_id` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Unique identifier for each order. + + `order_date` ([`date`](/reference/elasticsearch/mapping-reference/date.md)): Timestamp when the order was placed. + + `type` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Document type (always "order"). + + `currency` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Transaction currency (EUR, USD, etc.). + +* **Customer information** + + `customer_id` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Unique customer identifier. + + `customer_full_name` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Complete customer name with keyword subfield. + + `customer_first_name` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Customer's first name with keyword subfield. + + `customer_last_name` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Customer's last name with keyword subfield. + + `customer_gender` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Customer gender (MALE, FEMALE). + + `email` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Customer email address. + + `user` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Username derived from customer name. + +* **Geographic Information** + + `geoip.city_name` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): City where the order was placed. + + `geoip.continent_name` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Continent of order origin. + + `geoip.country_iso_code` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Two-letter country code. + + `geoip.location` ([`geo_point`](/reference/elasticsearch/mapping-reference/geo-point.md)): Geographic coordinates of order location. + + `geoip.region_name` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): State or region name. + +* **Order timing** + + `day_of_week` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Day when the order was placed (Monday, Tuesday, and so on). + + `day_of_week_i` ([`integer`](/reference/elasticsearch/mapping-reference/number.md)): Numeric day of week (0-6). + +* **Product information** + + `category` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Primary product categories with keyword subfield. + + `manufacturer` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Manufacturer names with keyword subfield. + + `sku` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Stock Keeping Unit codes. + + `products.product_name` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Product names (comma-separated for multiple products). + + `products.product_id` ([`long`](/reference/elasticsearch/mapping-reference/number.md)): Product identifiers. + + `products._id` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Internal product identifiers with keyword subfield. + + `products.sku` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Product-specific SKU codes. + + `products.category` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Individual product categories with keyword subfield. + + `products.manufacturer` ([`text`](/reference/elasticsearch/mapping-reference/text.md)): Product-specific manufacturers with keyword subfield. + + `products.created_on` ([`date`](/reference/elasticsearch/mapping-reference/date.md)): Product catalog creation dates. + + `products.quantity` ([`integer`](/reference/elasticsearch/mapping-reference/number.md)): Quantity of each product ordered. + +* **Pricing and financial information** + + `products.base_price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Original product prices before discounts. + + `products.base_unit_price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Base price per unit. + + `products.price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Final product prices. + + `products.min_price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Minimum price thresholds. + + `products.discount_amount` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Discount amounts applied. + + `products.discount_percentage` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)) : Percentage discounts applied. + + `products.unit_discount_amount` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Discount amount per unit. + + `products.tax_amount` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Tax amounts for products. + + `products.taxful_price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Product prices including tax. + + `products.taxless_price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Product prices excluding tax. + + `taxful_total_price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Total order amount including tax. + + `taxless_total_price` ([`half_float`](/reference/elasticsearch/mapping-reference/number.md)): Total order amount excluding tax. + +* **Order summary** + + `total_quantity` ([`integer`](/reference/elasticsearch/mapping-reference/number.md)): Total items in the order. + + `total_unique_products` ([`integer`](/reference/elasticsearch/mapping-reference/number.md)): Number of different products in the order. + +* **Metadata** + + `event.dataset` ([`keyword`](/reference/elasticsearch/mapping-reference/keyword.md)): Dataset identifier ("sample\_ecommerce"). diff --git a/docs/reference/scripting-languages/painless/painless-contexts.md b/docs/reference/scripting-languages/painless/painless-contexts.md index 8ddad4c956d3b..5887919232591 100644 --- a/docs/reference/scripting-languages/painless/painless-contexts.md +++ b/docs/reference/scripting-languages/painless/painless-contexts.md @@ -1,43 +1,95 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-contexts.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Painless contexts [painless-contexts] -A Painless script is evaluated within a context. Each context has values that are available as local variables, an allowlist that controls the available classes, and the methods and fields within those classes (API), and if and what type of value is returned. +In Painless, a context defines where and how your script runs in {{es}}. Each context determines three key aspects: which variables are available to your script (such as `doc`, `ctx`, or `_source`), which Java classes and methods your script can access for security, and what type of value your script should return. -Painless scripts typically run within one of the contexts in the following table. Before using a Painless context, [configure the example data](/reference/scripting-languages/painless/painless-context-examples.md). Each context example is configured to operate on this data. +## What are contexts? -| Name | Painless Documentation | Elasticsearch Documentation | -| --- | --- | --- | -| Runtime field | [Painless Documentation](/reference/scripting-languages/painless/painless-runtime-fields-context.md) | [Elasticsearch Documentation](docs-content://manage-data/data-store/mapping/runtime-fields.md) | -| Ingest processor | [Painless Documentation](/reference/scripting-languages/painless/painless-ingest-processor-context.md) | [Elasticsearch Documentation](/reference/enrich-processor/script-processor.md) | -| Update | [Painless Documentation](/reference/scripting-languages/painless/painless-update-context.md) | [Elasticsearch Documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update) | -| Update by query | [Painless Documentation](/reference/scripting-languages/painless/painless-update-by-query-context.md) | [Elasticsearch Documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update-by-query) | -| Reindex | [Painless Documentation](/reference/scripting-languages/painless/painless-reindex-context.md) | [Elasticsearch Documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex) | -| Sort | [Painless Documentation](/reference/scripting-languages/painless/painless-sort-context.md) | [Elasticsearch Documentation](/reference/elasticsearch/rest-apis/sort-search-results.md) | -| Similarity | [Painless Documentation](/reference/scripting-languages/painless/painless-similarity-context.md) | [Elasticsearch Documentation](/reference/elasticsearch/index-settings/similarity.md) | -| Weight | [Painless Documentation](/reference/scripting-languages/painless/painless-weight-context.md) | [Elasticsearch Documentation](/reference/elasticsearch/index-settings/similarity.md) | -| Score | [Painless Documentation](/reference/scripting-languages/painless/painless-score-context.md) | [Elasticsearch Documentation](/reference/query-languages/query-dsl/query-dsl-function-score-query.md) | -| Field | [Painless Documentation](/reference/scripting-languages/painless/painless-field-context.md) | [Elasticsearch Documentation](/reference/elasticsearch/rest-apis/retrieve-selected-fields.md#script-fields) | -| Filter | [Painless Documentation](/reference/scripting-languages/painless/painless-filter-context.md) | [Elasticsearch Documentation](/reference/query-languages/query-dsl/query-dsl-script-query.md) | -| Minimum should match | [Painless Documentation](/reference/scripting-languages/painless/painless-min-should-match-context.md) | [Elasticsearch Documentation](/reference/query-languages/query-dsl/query-dsl-terms-set-query.md) | -| Metric aggregation initialization | [Painless Documentation](/reference/scripting-languages/painless/painless-metric-agg-init-context.md) | [Elasticsearch Documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | -| Metric aggregation map | [Painless Documentation](/reference/scripting-languages/painless/painless-metric-agg-map-context.md) | [Elasticsearch Documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | -| Metric aggregation combine | [Painless Documentation](/reference/scripting-languages/painless/painless-metric-agg-combine-context.md) | [Elasticsearch Documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | -| Metric aggregation reduce | [Painless Documentation](/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md) | [Elasticsearch Documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | -| Bucket script aggregation | [Painless Documentation](/reference/scripting-languages/painless/painless-bucket-script-agg-context.md) | [Elasticsearch Documentation](/reference/aggregations/search-aggregations-pipeline-bucket-script-aggregation.md) | -| Bucket selector aggregation | [Painless Documentation](/reference/scripting-languages/painless/painless-bucket-selector-agg-context.md) | [Elasticsearch Documentation](/reference/aggregations/search-aggregations-pipeline-bucket-selector-aggregation.md) | -| Watcher condition | [Painless Documentation](/reference/scripting-languages/painless/painless-watcher-condition-context.md) | [Elasticsearch Documentation](docs-content://explore-analyze/alerts-cases/watcher/condition-script.md) | -| Watcher transform | [Painless Documentation](/reference/scripting-languages/painless/painless-watcher-transform-context.md) | [Elasticsearch Documentation](docs-content://explore-analyze/alerts-cases/watcher/transform-script.md) | +Contexts are runtime environments that determine script behavior within specific {{es}} operations. Unlike traditional scripting languages, where code runs consistently everywhere, Painless scripts are context-aware: the same syntax can access different variables and produce different results depending on the {{es}} operation that calls it. +## How contexts work +Painless supports scripting across numerous {{es}} operations, from search scoring to document processing to aggregation calculations. Each operation requires different capabilities; search scripts need read-only field access, while ingest scripts need document modification abilities. This diversity of requirements creates the need for specialized contexts. +Contexts ensure appropriate access and capabilities for each {{es}} operation while maintaining security and performance by providing exactly the inputs and APIs needed for a specific task through fine-grained allowlists and optimized data access patterns. +For example, search scoring contexts provide document fields as input and generate numerical scores as output, while ingest contexts provide documents as input for modification and generate transformed documents as output. Even if some contexts appear to be similar, each has tools and restrictions designed for a specific purpose. +## Context and data access + +Contexts determine how you can access document data in Painless scripts: + +**`doc` values:** +: Read-only field access using columnar storage for search, aggregation, and sorting context + +**`ctx` access:** +: Document modification capabilities in update, ingest, and reindex context + +**`_source` access:** +: Complete document JSON for runtime fields and transformations + +**`params`** +: User-defined parameters passed into scripts, available across all contexts + +For detailed data access patterns and examples, refer to [Painless syntax-context bridge](docs-content://explore-analyze/scripting/painless-syntax-context-bridge.md). + +## Context Categories overview + +While Painless supports more than twenty specific contexts, they operate within four categories. + +**Document Processing** +: Transform and extract data from existing documents without modifying the original content. Use runtime fields, ingest processors, and field scripts to create computed fields or parse log data. + +**Document Modification** +: Change or update document context permanently in the index. Use update scripts, update by query, and reindex scripts to modify fields or restructure documents. + +**Search Enhancement** +: Customize search behavior, scoring, or sorting without changing the indexed documents. Use filter scripts, and sort scripts for custom relevance or dynamic filtering. + +**Advanced Operations** +: Implement specialized functionality such as custom aggregations or monitoring conditions. Use metric aggregation, bucket aggregation, and Watcher scripts for complex calculations and alerting. + +## Painless context reference + +| Name | Painless documentation | {{es}} documentation | +| :---- | :---- | :---- | +| Runtime field | [Painless documentation](/reference/scripting-languages/painless/painless-runtime-fields-context.md) | [{{es}} documentation](docs-content://manage-data/data-store/mapping/runtime-fields.md) | +| Field | [Painless documentation](/reference/scripting-languages/painless/painless-field-context.md) | [{{es}} documentation](/reference/elasticsearch/rest-apis/retrieve-selected-fields.md#script-fields) | +| Ingest processor | [Painless documentation](/reference/scripting-languages/painless/painless-ingest-processor-context.md) | [{{es}} documentation](/reference/enrich-processor/script-processor.md) | +| Filter | [Painless documentation](/reference/scripting-languages/painless/painless-filter-context.md) | [{{es}} documentation](/reference/query-languages/query-dsl/query-dsl-script-query.md) | +| Score | [Painless documentation](/reference/scripting-languages/painless/painless-score-context.md) | [{{es}} documentation](/reference/query-languages/query-dsl/query-dsl-function-score-query.md) | +| Sort | [Painless documentation](/reference/scripting-languages/painless/painless-sort-context.md) | [{{es}} documentation](/reference/elasticsearch/rest-apis/sort-search-results.md) | +| Update | [Painless documentation](/reference/scripting-languages/painless/painless-update-context.md) | [{{es}} documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update) | +| Update by query | [Painless documentation](/reference/scripting-languages/painless/painless-update-by-query-context.md) | [{{es}} documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update-by-query) | +| Reindex | [Painless documentation](/reference/scripting-languages/painless/painless-reindex-context.md) | [{{es}} documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex) | +| Similarity | [Painless documentation](/reference/scripting-languages/painless/painless-similarity-context.md) | [{{es}} documentation](/reference/elasticsearch/index-settings/similarity.md) | +| Weight | [Painless documentation](/reference/scripting-languages/painless/painless-weight-context.md) | [{{es}} documentation](/reference/elasticsearch/index-settings/similarity.md) | +| Minimum should match | [Painless documentation](/reference/scripting-languages/painless/painless-min-should-match-context.md) | [{{es}} documentation](/reference/query-languages/query-dsl/query-dsl-terms-set-query.md) | +| Metric aggregation initialization | [Painless documentation](/reference/scripting-languages/painless/painless-metric-agg-init-context.md) | [{{es}} documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | +| Metric aggregation map | [Painless documentation](/reference/scripting-languages/painless/painless-metric-agg-map-context.md) | [{{es}} documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | +| Metric aggregation combine | [Painless documentation](/reference/scripting-languages/painless/painless-metric-agg-combine-context.md) | [{{es}} documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | +| Metric aggregation reduce | [Painless documentation](/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md) | [{{es}} documentation](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) | +| Bucket script aggregation | [Painless documentation](/reference/scripting-languages/painless/painless-bucket-script-agg-context.md) | [{{es}} documentation](/reference/aggregations/search-aggregations-pipeline-bucket-script-aggregation.md) | +| Bucket selector aggregation | [Painless documentation](/reference/scripting-languages/painless/painless-bucket-selector-agg-context.md) | [{{es}} documentation](/reference/aggregations/search-aggregations-pipeline-bucket-selector-aggregation.md) | +| Watcher condition | [Painless documentation](/reference/scripting-languages/painless/painless-watcher-condition-context.md) | [{{es}} documentation](docs-content://explore-analyze/alerts-cases/watcher/condition-script.md) | +| Watcher transform | [Painless documentation](/reference/scripting-languages/painless/painless-watcher-transform-context.md) | [{{es}} documentation](docs-content://explore-analyze/alerts-cases/watcher/transform-script.md) | + +## Next steps + +* **Most common context:** Start with [Field context](/reference/scripting-languages/painless/painless-field-context.md) or [Runtime field context](/reference/scripting-languages/painless/painless-runtime-fields-context.md) for data extraction and transformation. +* **Data access patterns:** Review Painless syntax-context bridge for `doc`, `ctx`, and `_source` usage examples. +* **Step-by-step tutorials**: refer to [How to write Painless scripts](docs-content://explore-analyze/scripting/modules-scripting-using.md) and our [Painless tutorials](docs-content://explore-analyze/scripting/common-script-uses.md) in the Explore and Analyze section. + +Before using a Painless context, [configure the example data](/reference/scripting-languages/painless/painless-context-examples.md). Each context example is configured to operate on this data. diff --git a/docs/reference/scripting-languages/painless/painless-datetime-comparison.md b/docs/reference/scripting-languages/painless/painless-datetime-comparison.md new file mode 100644 index 0000000000000..444eb45698679 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-comparison.md @@ -0,0 +1,63 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime comparison [_datetime_comparison] + +Use either two numeric datetimes or two complex datetimes to do a datetime comparison. Use standard [comparison operators](/reference/scripting-languages/painless/painless-operators-boolean.md) to compare two numeric datetimes of the same time unit such as milliseconds. For complex datetimes there is often a method or another complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) available to do the comparison. + +## Datetime comparison examples [_datetime_comparison_examples] + +* Perform a `greater than` comparison of two numeric datetimes in milliseconds: + + ```painless + long timestamp1 = 434931327000L; + long timestamp2 = 434931330000L; + + if (timestamp1 > timestamp2) { + // handle condition + } + ``` + +* Perform an `equality` comparison of two complex datetimes: + + ```painless + ZonedDateTime zdt1 = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + ZonedDateTime zdt2 = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + + if (zdt1.equals(zdt2)) { + // handle condition + } + ``` + +* Perform a `less than` comparison of two complex datetimes: + + ```painless + ZonedDateTime zdt1 = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + ZonedDateTime zdt2 = + ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); + + if (zdt1.isBefore(zdt2)) { + // handle condition + } + ``` + +* Perform a `greater than` comparison of two complex datetimes + + ```painless + ZonedDateTime zdt1 = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + ZonedDateTime zdt2 = + ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); + + if (zdt1.isAfter(zdt2)) { + // handle condition + } + ``` diff --git a/docs/reference/scripting-languages/painless/painless-datetime-conversion.md b/docs/reference/scripting-languages/painless/painless-datetime-conversion.md new file mode 100644 index 0000000000000..6ecc12df8e122 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-conversion.md @@ -0,0 +1,31 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime conversion [_datetime_conversion] + +Datetime conversion is a switch from a numeric datetime to a complex datetime and the reverse. + +## Datetime conversion examples [_datetime_conversion_examples] + +* Convert a date from milliseconds: + + ```painless + long milliSinceEpoch = 434931330000L; + Instant instant = Instant.ofEpochMilli(milliSinceEpoch); + ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); + ``` + +* Convert a date to milliseconds: + + ```painless + ZonedDateTime zdt = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + long milliSinceEpoch = zdt.toInstant().toEpochMilli(); + ``` + + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-difference.md b/docs/reference/scripting-languages/painless/painless-datetime-difference.md new file mode 100644 index 0000000000000..81b0084520b98 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-difference.md @@ -0,0 +1,44 @@ +--- +navigation_title: Datetime difference +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime difference (elapsed time) [_datetime_difference_elapsed_time] + +Use either two numeric datetimes or two complex datetimes to calculate the difference (elapsed time) between two different datetimes. Use [subtraction](/reference/scripting-languages/painless/painless-operators-numeric.md#subtraction-operator) to calculate the difference between two numeric datetimes of the same time unit such as milliseconds. For complex datetimes there is often a method or another complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) available to calculate the difference. Use [ChronoUnit](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time-temporal.html#painless-api-reference-shared-ChronoUnit) to calculate the difference between two complex datetimes if supported. + +## Datetime difference examples [_datetime_difference_examples] + +* Calculate the difference in milliseconds between two numeric datetimes: + + ```painless + long startTimestamp = 434931327000L; + long endTimestamp = 434931330000L; + long differenceInMillis = endTimestamp - startTimestamp; + ``` + +* Calculate the difference in milliseconds between two complex datetimes: + + ```painless + ZonedDateTime zdt1 = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 11000000, ZoneId.of('Z')); + ZonedDateTime zdt2 = + ZonedDateTime.of(1983, 10, 13, 22, 15, 35, 0, ZoneId.of('Z')); + long differenceInMillis = ChronoUnit.MILLIS.between(zdt1, zdt2); + ``` + +* Calculate the difference in days between two complex datetimes: + + ```painless + ZonedDateTime zdt1 = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 11000000, ZoneId.of('Z')); + ZonedDateTime zdt2 = + ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); + long differenceInDays = ChronoUnit.DAYS.between(zdt1, zdt2); + ``` + + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-examples-in-contexts.md b/docs/reference/scripting-languages/painless/painless-datetime-examples-in-contexts.md new file mode 100644 index 0000000000000..86f60f480be5d --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-examples-in-contexts.md @@ -0,0 +1,167 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime examples in contexts [_datetime_examples_in_contexts] + +Try out these Painless datetime examples that include real world contexts. + +## Load the example data [_load_the_example_data] + +Run the following curl commands to load the data necessary for the context examples into an {{es}} cluster: + +1. Create [mappings](docs-content://manage-data/data-store/mapping.md) for the sample data. + + ```console + PUT /messages + { + "mappings": { + "properties": { + "priority": { + "type": "integer" + }, + "datetime": { + "type": "date" + }, + "message": { + "type": "text" + } + } + } + } + ``` + +2. Load the sample data. + + ```console + POST /_bulk + { "index" : { "_index" : "messages", "_id" : "1" } } + { "priority": 1, "datetime": "2019-07-17T12:13:14Z", "message": "m1" } + { "index" : { "_index" : "messages", "_id" : "2" } } + { "priority": 1, "datetime": "2019-07-24T01:14:59Z", "message": "m2" } + { "index" : { "_index" : "messages", "_id" : "3" } } + { "priority": 2, "datetime": "1983-10-14T00:36:42Z", "message": "m3" } + { "index" : { "_index" : "messages", "_id" : "4" } } + { "priority": 3, "datetime": "1983-10-10T02:15:15Z", "message": "m4" } + { "index" : { "_index" : "messages", "_id" : "5" } } + { "priority": 3, "datetime": "1983-10-10T17:18:19Z", "message": "m5" } + { "index" : { "_index" : "messages", "_id" : "6" } } + { "priority": 1, "datetime": "2019-08-03T17:19:31Z", "message": "m6" } + { "index" : { "_index" : "messages", "_id" : "7" } } + { "priority": 3, "datetime": "2019-08-04T17:20:00Z", "message": "m7" } + { "index" : { "_index" : "messages", "_id" : "8" } } + { "priority": 2, "datetime": "2019-08-04T18:01:01Z", "message": "m8" } + { "index" : { "_index" : "messages", "_id" : "9" } } + { "priority": 3, "datetime": "1983-10-10T19:00:45Z", "message": "m9" } + { "index" : { "_index" : "messages", "_id" : "10" } } + { "priority": 2, "datetime": "2019-07-23T23:39:54Z", "message": "m10" } + ``` + % TEST[continued] + + +## Day-of-the-week bucket aggregation example [_day_of_the_week_bucket_aggregation_example] + +The following example uses a [terms aggregation](/reference/aggregations/search-aggregations-bucket-terms-aggregation.md#search-aggregations-bucket-terms-aggregation-script) as part of the [bucket script aggregation context](/reference/scripting-languages/painless/painless-bucket-script-agg-context.md) to display the number of messages from each day-of-the-week. + +```console +GET /messages/_search?pretty=true +{ + "aggs": { + "day-of-week-count": { + "terms": { + "script": "return doc[\"datetime\"].value.getDayOfWeekEnum();" + } + } + } +} +``` +% TEST[continued] + +## Morning/evening bucket aggregation example [_morningevening_bucket_aggregation_example] + +The following example uses a [terms aggregation](/reference/aggregations/search-aggregations-bucket-terms-aggregation.md#search-aggregations-bucket-terms-aggregation-script) as part of the [bucket script aggregation context](/reference/scripting-languages/painless/painless-bucket-script-agg-context.md) to display the number of messages received in the morning versus the evening. + +```console +GET /messages/_search?pretty=true +{ + "aggs": { + "am-pm-count": { + "terms": { + "script": "return doc[\"datetime\"].value.getHour() < 12 ? \"AM\" : \"PM\";" + } + } + } +} +``` +% TEST[continued] + +## Age of a message script field example [_age_of_a_message_script_field_example] + +The following example uses a [script field](/reference/elasticsearch/rest-apis/retrieve-selected-fields.md#script-fields) as part of the [field context](/reference/scripting-languages/painless/painless-field-context.md) to display the elapsed time between "now" and when a message was received. + +```console +GET /_search?pretty=true +{ + "query": { + "match_all": {} + }, + "script_fields": { + "message_age": { + "script": { + "source": "ZonedDateTime now = ZonedDateTime.ofInstant(Instant.ofEpochMilli(params[\"now\"]), ZoneId.of(\"Z\")); ZonedDateTime mdt = doc[\"datetime\"].value; String age; long years = mdt.until(now, ChronoUnit.YEARS); age = years + \"Y \"; mdt = mdt.plusYears(years); long months = mdt.until(now, ChronoUnit.MONTHS); age += months + \"M \"; mdt = mdt.plusMonths(months); long days = mdt.until(now, ChronoUnit.DAYS); age += days + \"D \"; mdt = mdt.plusDays(days); long hours = mdt.until(now, ChronoUnit.HOURS); age += hours + \"h \"; mdt = mdt.plusHours(hours); long minutes = mdt.until(now, ChronoUnit.MINUTES); age += minutes + \"m \"; mdt = mdt.plusMinutes(minutes); long seconds = mdt.until(now, ChronoUnit.SECONDS); age += hours + \"s\"; return age;", + "params": { + "now": 1574005645830 + } + } + } + } +} +``` +% TEST[continued] + +The following shows the script broken into multiple lines: + +```painless +ZonedDateTime now = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(params['now']), ZoneId.of('Z')); <1> +ZonedDateTime mdt = doc['datetime'].value; <2> + +String age; + +long years = mdt.until(now, ChronoUnit.YEARS); <3> +age = years + 'Y '; <4> +mdt = mdt.plusYears(years); <5> + +long months = mdt.until(now, ChronoUnit.MONTHS); +age += months + 'M '; +mdt = mdt.plusMonths(months); + +long days = mdt.until(now, ChronoUnit.DAYS); +age += days + 'D '; +mdt = mdt.plusDays(days); + +long hours = mdt.until(now, ChronoUnit.HOURS); +age += hours + 'h '; +mdt = mdt.plusHours(hours); + +long minutes = mdt.until(now, ChronoUnit.MINUTES); +age += minutes + 'm '; +mdt = mdt.plusMinutes(minutes); + +long seconds = mdt.until(now, ChronoUnit.SECONDS); +age += hours + 's'; + +return age; <6> +``` + +1. Parse the datetime "now" as input from the user-defined params. +2. Store the datetime the message was received as a `ZonedDateTime`. +3. Find the difference in years between "now" and the datetime the message was received. +4. Add the difference in years later returned in the format `Y ...` for the age of a message. +5. Add the years so only the remainder of the months, days, etc. remain as the difference between "now" and the datetime the message was received. Repeat this pattern until the desired granularity is reached (seconds in this example). +6. Return the age of the message in the format `Y M D h m s `. + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-input.md b/docs/reference/scripting-languages/painless/painless-datetime-input.md new file mode 100644 index 0000000000000..8458c5b6fe808 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-input.md @@ -0,0 +1,212 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime Input [_datetime_input] + +There are several common ways datetimes are used as input for a script determined by the [Painless context](/reference/scripting-languages/painless/painless-contexts.md). Typically, datetime input will be accessed from parameters specified by the user, from an original source document, or from an indexed document. + +## Datetime input from user parameters [_datetime_input_from_user_parameters] + +Use the [params section](docs-content://explore-analyze/scripting/modules-scripting-using.md) during script specification to pass in a numeric datetime or string datetime as a script input. Access to user-defined parameters within a script is dependent on the Painless context, though, the parameters are most commonly accessible through an input called `params`. + +**Examples** + +* Parse a numeric datetime from user parameters to a complex datetime + + * Input: + + ```JSON + ... + "script": { + ... + "params": { + "input_datetime": 434931327000 + } + } + ... + ``` + + * Script: + + ```painless + long inputDateTime = params['input_datetime']; + Instant instant = Instant.ofEpochMilli(inputDateTime); + ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); + ``` + +* Parse a string datetime from user parameters to a complex datetime + + * Input: + + ```JSON + ... + "script": { + ... + "params": { + "input_datetime": "custom y 1983 m 10 d 13 22:15:30 Z" + } + } + ... + ``` + + * Script: + + ```painless + String datetime = params['input_datetime']; + DateTimeFormatter dtf = DateTimeFormatter.ofPattern( + "'custom' 'y' yyyy 'm' MM 'd' dd HH:mm:ss VV"); + ZonedDateTime zdt = ZonedDateTime.parse(datetime, dtf); <1> + ``` + + 1. This uses a custom `DateTimeFormatter`. + + + +## Datetime input from a source document [_datetime_input_from_a_source_document] + +Use an original [source](/reference/elasticsearch/mapping-reference/mapping-source-field.md) document as a script input to access a numeric datetime or string datetime for a specific field within that document. Access to an original source document within a script is dependent on the Painless context and is not always available. An original source document is most commonly accessible through an input called `ctx['_source']` or `params['_source']`. + +**Examples** + +* Parse a numeric datetime from a sourced document to a complex datetime + + * Input: + + ```JSON + { + ... + "input_datetime": 434931327000 + ... + } + ``` + + * Script: + + ```painless + long inputDateTime = ctx['_source']['input_datetime']; <1> + Instant instant = Instant.ofEpochMilli(inputDateTime); + ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); + ``` + + 1. Access to `_source` is dependent on the Painless context. + +* Parse a string datetime from a sourced document to a complex datetime + + * Input: + + ```JSON + { + ... + "input_datetime": "1983-10-13T22:15:30Z" + ... + } + ``` + + * Script: + + ```painless + String datetime = params['_source']['input_datetime']; <1> + ZonedDateTime zdt = ZonedDateTime.parse(datetime); <2> + ``` + + 1. Access to `_source` is dependent on the Painless context. + 2. The parse method uses ISO 8601 by default. + + + +## Datetime input from an indexed document [_datetime_input_from_an_indexed_document] + +Use an indexed document as a script input to access a complex datetime for a specific field within that document where the field is mapped as a [standard date](/reference/elasticsearch/mapping-reference/date.md) or a [nanosecond date](/reference/elasticsearch/mapping-reference/date_nanos.md). Numeric datetime fields mapped as [numeric](/reference/elasticsearch/mapping-reference/number.md) and string datetime fields mapped as [keyword](/reference/elasticsearch/mapping-reference/keyword.md) are accessible through an indexed document as well. Access to an indexed document within a script is dependent on the Painless context and is not always available. An indexed document is most commonly accessible through an input called `doc`. + +**Examples** + +* Format a complex datetime from an indexed document to a string datetime + + * Assumptions: + + * The field `input_datetime` exists in all indexes as part of the query + * All indexed documents contain the field `input_datetime` + + * Mappings: + + ```JSON + { + "mappings": { + ... + "properties": { + ... + "input_datetime": { + "type": "date" + } + ... + } + ... + } + } + ``` + + * Script: + + ```painless + ZonedDateTime input = doc['input_datetime'].value; + String output = input.format(DateTimeFormatter.ISO_INSTANT); <1> + ``` + + 1. This uses a built-in `DateTimeFormatter`. + +* Find the difference between two complex datetimes from an indexed document + + * Assumptions: + + * The fields `start` and `end` may **not** exist in all indexes as part of the query + * The fields `start` and `end` may **not** have values in all indexed documents + + * Mappings: + + ```JSON + { + "mappings": { + ... + "properties": { + ... + "start": { + "type": "date" + }, + "end": { + "type": "date" + } + ... + } + ... + } + } + ``` + + * Script: + + ```painless + if (doc.containsKey('start') && doc.containsKey('end')) { <1> + + if (doc['start'].size() > 0 && doc['end'].size() > 0) { <2> + + ZonedDateTime start = doc['start'].value; + ZonedDateTime end = doc['end'].value; + long differenceInMillis = ChronoUnit.MILLIS.between(start, end); + + // handle difference in times + } else { + // handle fields without values + } + } else { + // handle index with missing fields + } + ``` + + 1. When a query’s results span multiple indexes, some indexes may not contain a specific field. Use the `containsKey` method call on the `doc` input to ensure a field exists as part of the index for the current document. + 2. Some fields within a document may have no values. Use the `size` method call on a field within the `doc` input to ensure that field has at least one value for the current document. + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-modification.md b/docs/reference/scripting-languages/painless/painless-datetime-modification.md new file mode 100644 index 0000000000000..59f452dacdd9b --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-modification.md @@ -0,0 +1,44 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime Modification [_datetime_modification] + +Use either a numeric datetime or a complex datetime to do modifications such as adding several seconds to a datetime or subtracting several days from a datetime. Use standard [numeric operators](/reference/scripting-languages/painless/painless-operators-numeric.md) to modify a numeric datetime. Use [methods](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZonedDateTime) (or fields) to modify a complex datetime. Many complex datetimes are immutable, so upon modification a new complex datetime is created that requires [assignment](/reference/scripting-languages/painless/painless-variables.md#variable-assignment) or immediate use. + +## Datetime modification examples [_datetime_modification_examples] + +* Subtract three seconds from a numeric datetime in milliseconds: + + ```painless + long milliSinceEpoch = 434931330000L; + milliSinceEpoch = milliSinceEpoch - 1000L*3L; + ``` + +* Add three days to a complex datetime: + + ```painless + ZonedDateTime zdt = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + ZonedDateTime updatedZdt = zdt.plusDays(3); + ``` + +* Subtract 125 minutes from a complex datetime: + + ```painless + ZonedDateTime zdt = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + ZonedDateTime updatedZdt = zdt.minusMinutes(125); + ``` + +* Set the year on a complex datetime: + + ```painless + ZonedDateTime zdt = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + ZonedDateTime updatedZdt = zdt.withYear(1976); + ``` diff --git a/docs/reference/scripting-languages/painless/painless-datetime-now.md b/docs/reference/scripting-languages/painless/painless-datetime-now.md new file mode 100644 index 0000000000000..2b8de2f07b997 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-now.md @@ -0,0 +1,119 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime now [_datetime_now] + +Under most Painless contexts the current datetime, `now`, is not supported. There are two primary reasons for this. The first is that scripts are often run once per document, so each time the script is run a different `now` is returned. The second is that scripts are often run in a distributed fashion without a way to appropriately synchronize `now`. Instead, pass in a user-defined parameter with either a string datetime or numeric datetime for `now`. A numeric datetime is preferred as there is no need to parse it for comparison. + +## Datetime now examples [_datetime_now_examples] + +* Use a numeric datetime as `now` + + * Assumptions: + + * The field `input_datetime` exists in all indexes as part of the query + * All indexed documents contain the field `input_datetime` + + * Mappings: + + ```JSON + { + "mappings": { + ... + "properties": { + ... + "input_datetime": { + "type": "date" + } + ... + } + ... + } + } + ``` + % NOTCONSOLE + + * Input: + + ```JSON + ... + "script": { + ... + "params": { + "now": + } + } + ... + ``` + % NOTCONSOLE + + * Script: + + ```painless + long now = params['now']; + ZonedDateTime inputDateTime = doc['input_datetime']; + long millisDateTime = inputDateTime.toInstant().toEpochMilli(); + long elapsedTime = now - millisDateTime; + ``` + % NOTCONSOLE + +* Use a string datetime as `now` + + * Assumptions: + + * The field `input_datetime` exists in all indexes as part of the query + * All indexed documents contain the field `input_datetime` + + * Mappings: + + ```JSON + { + "mappings": { + ... + "properties": { + ... + "input_datetime": { + "type": "date" + } + ... + } + ... + } + } + ``` + % NOTCONSOLE + + * Input: + + ```JSON + ... + "script": { + ... + "params": { + "now": "" + } + } + ... + ``` + % NOTCONSOLE + + * Script: + + ```painless + String nowString = params['now']; + ZonedDateTime nowZdt = ZonedDateTime.parse(nowString); <1> + long now = ZonedDateTime.toInstant().toEpochMilli(); + ZonedDateTime inputDateTime = doc['input_datetime']; + long millisDateTime = zdt.toInstant().toEpochMilli(); + long elapsedTime = now - millisDateTime; + ``` + % NOTCONSOLE + + 1. This parses the same string datetime every time the script runs. Use a numeric datetime to avoid a significant performance hit. + + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-parsing-and-formatting.md b/docs/reference/scripting-languages/painless/painless-datetime-parsing-and-formatting.md new file mode 100644 index 0000000000000..e9cfe16c8d48a --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-parsing-and-formatting.md @@ -0,0 +1,81 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime parsing and formatting [_datetime_parsing_and_formatting] + +Datetime parsing is a switch from a string datetime to a complex datetime, and datetime formatting is a switch from a complex datetime to a string datetime. + +A [DateTimeFormatter](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time-format.html#painless-api-reference-shared-DateTimeFormatter) is a complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) that defines the allowed sequence of characters for a string datetime. Datetime parsing and formatting often require a DateTimeFormatter. For more information about how to use a DateTimeFormatter see the [Java documentation](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html). + +## Datetime parsing examples [_datetime_parsing_examples] + +* Parse from milliseconds: + + ```painless + String milliSinceEpochString = "434931330000"; + long milliSinceEpoch = Long.parseLong(milliSinceEpochString); + Instant instant = Instant.ofEpochMilli(milliSinceEpoch); + ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); + ``` + +* Parse from ISO 8601: + + ```painless + String datetime = '1983-10-13T22:15:30Z'; + ZonedDateTime zdt = ZonedDateTime.parse(datetime); <1> + ``` + + 1. The parse method uses ISO 8601 by default. + +* Parse from RFC 1123: + + ```painless + String datetime = 'Thu, 13 Oct 1983 22:15:30 GMT'; + ZonedDateTime zdt = ZonedDateTime.parse(datetime, + DateTimeFormatter.RFC_1123_DATE_TIME); <1> + ``` + + 1. This uses a built-in `DateTimeFormatter`. + +* Parse from a custom format: + + ```painless + String datetime = 'custom y 1983 m 10 d 13 22:15:30 Z'; + DateTimeFormatter dtf = DateTimeFormatter.ofPattern( + "'custom' 'y' yyyy 'm' MM 'd' dd HH:mm:ss VV"); + ZonedDateTime zdt = ZonedDateTime.parse(datetime, dtf); <1> + ``` + + 1. This uses a custom `DateTimeFormatter`. + + + +## Datetime formatting examples [_datetime_formatting_examples] + +* Format to ISO 8601: + + ```painless + ZonedDateTime zdt = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + String datetime = zdt.format(DateTimeFormatter.ISO_INSTANT); <1> + ``` + + 1. This uses a built-in `DateTimeFormatter`. + +* Format to a custom format: + + ```painless + ZonedDateTime zdt = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + DateTimeFormatter dtf = DateTimeFormatter.ofPattern( + "'date:' yyyy/MM/dd 'time:' HH:mm:ss"); + String datetime = zdt.format(dtf); <1> + ``` + + 1. This uses a custom `DateTimeFormatter`. + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-pieces.md b/docs/reference/scripting-languages/painless/painless-datetime-pieces.md new file mode 100644 index 0000000000000..eee74a4c612ce --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-pieces.md @@ -0,0 +1,42 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime Pieces [_datetime_pieces] + +Datetime representations often contain the data to extract individual datetime pieces such as year, hour, timezone, and so on. Use individual pieces of a datetime to create a complex datetime, and use a complex datetime to extract individual pieces. + +## Datetime Pieces Examples [_datetime_pieces_examples] + +* Create a complex datetime from pieces: + + ```painless + int year = 1983; + int month = 10; + int day = 13; + int hour = 22; + int minutes = 15; + int seconds = 30; + int nanos = 0; + ZonedDateTime zdt = ZonedDateTime.of( + year, month, day, hour, minutes, seconds, nanos, ZoneId.of('Z')); + ``` + +* Extract pieces from a complex datetime: + + ```painless + ZonedDateTime zdt = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 100, ZoneId.of(tz)); + int year = zdt.getYear(); + int month = zdt.getMonthValue(); + int day = zdt.getDayOfMonth(); + int hour = zdt.getHour(); + int minutes = zdt.getMinute(); + int seconds = zdt.getSecond(); + int nanos = zdt.getNano(); + ``` + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-representation.md b/docs/reference/scripting-languages/painless/painless-datetime-representation.md new file mode 100644 index 0000000000000..cbeb265238cca --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-representation.md @@ -0,0 +1,23 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime representation [_datetime_representation] + +Datetimes in Painless are most commonly represented as a numeric value, a string value, or a complex value. + +numeric +: A datetime representation as a number from a starting offset called an epoch; in Painless this is typically a [long](/reference/scripting-languages/painless/painless-types.md#primitive-types) as milliseconds since an epoch of `1970-01-01 00:00:00` Zulu Time. + +string +: A datetime representation as a sequence of characters defined by a standard format or a custom format; in Painless this is typically a [String](/reference/scripting-languages/painless/painless-types.md#string-type) of the standard format [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). + +complex +: A datetime representation as a complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) that abstracts away internal details of how the datetime is stored and often provides utilities for modification and comparison; in Painless this is typically a [ZonedDateTime](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZonedDateTime). + +Switching between different representations of datetimes is often necessary to achieve a script’s objectives. A typical pattern in a script is to switch a numeric or string datetime to a complex datetime, modify or compare the complex datetime, and then switch it back to a numeric or string datetime for storage or to return a result. + diff --git a/docs/reference/scripting-languages/painless/painless-datetime-zone.md b/docs/reference/scripting-languages/painless/painless-datetime-zone.md new file mode 100644 index 0000000000000..59a0ff3bd0267 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-datetime-zone.md @@ -0,0 +1,36 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Datetime zone [_datetime_zone] + +Both string datetimes and complex datetimes have a timezone with a default of `UTC`. Numeric datetimes do not have enough explicit information to have a timezone, so `UTC` is always assumed. Use [methods](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZonedDateTime) (or fields) in conjunction with a [ZoneId](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZoneId) to change the timezone for a complex datetime. Parse a string datetime into a complex datetime to change the timezone, and then format the complex datetime back into a desired string datetime. Many complex datetimes are immutable, so upon modification a new complex datetime is created that requires [assignment](/reference/scripting-languages/painless/painless-variables.md#variable-assignment) or immediate use. + +## Datetime zone examples [_datetime_zone_examples] + +* Modify the timezone for a complex datetime: + + ```painless + ZonedDateTime utc = + ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); + ZonedDateTime pst = utc.withZoneSameInstant(ZoneId.of('America/Los_Angeles')); + ``` + +* Modify the timezone for a string datetime: + + ```painless + String gmtString = 'Thu, 13 Oct 1983 22:15:30 GMT'; + ZonedDateTime gmtZdt = ZonedDateTime.parse(gmtString, + DateTimeFormatter.RFC_1123_DATE_TIME); <1> + ZonedDateTime pstZdt = + gmtZdt.withZoneSameInstant(ZoneId.of('America/Los_Angeles')); + String pstString = pstZdt.format(DateTimeFormatter.RFC_1123_DATE_TIME); + ``` + + 1. This uses a built-in `DateTimeFormatter`. + + diff --git a/docs/reference/scripting-languages/painless/painless-debugging.md b/docs/reference/scripting-languages/painless/painless-debugging.md index 70e89cdf3e72a..e85ab4623fae0 100644 --- a/docs/reference/scripting-languages/painless/painless-debugging.md +++ b/docs/reference/scripting-languages/painless/painless-debugging.md @@ -1,82 +1,341 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-debugging.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Painless debugging [painless-debugging] -## Debug.Explain [_debug_explain] +## Why debugging in Painless is different -Painless doesn’t have a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) and while it’d be nice for it to have one day, it wouldn’t tell you the whole story around debugging painless scripts embedded in Elasticsearch because the data that the scripts have access to or "context" is so important. For now the best way to debug embedded scripts is by throwing exceptions at choice places. While you can throw your own exceptions (`throw new Exception('whatever')`), Painless’s sandbox prevents you from accessing useful information like the type of an object. So Painless has a utility method, `Debug.explain` which throws the exception for you. For example, you can use [`_explain`](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-explain) to explore the context available to a [script query](/reference/query-languages/query-dsl/query-dsl-script-query.md). +Painless scripts run within specific {{es}} contexts, not as isolated code. Unlike languages with interactive environments, Painless doesn’t provide a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) because script behavior depends entirely on the execution context and available data structures. -```console -PUT /hockey/_doc/1?refresh -{"first":"johnny","last":"gaudreau","goals":[9,27,1],"assists":[17,46,0],"gp":[26,82,1]} +The context determines available variables, API restrictions, and expected return types. A debugging approach that works in one context might not be directly applied to another because each context provides different capabilities and data access patterns. Refer to [Painless contexts](https://www.elastic.co/docs/reference/scripting-languages/painless/painless-contexts) to understand what variables and methods are available in each context. -POST /hockey/_explain/1 +## Context matters for debugging + +The secure, sandboxed approach used in Painless prevents access to standard Java debugging information. The sandbox restricts scripts from accessing information like object types, which has the effect of preventing traditional Java debugging methods. + +Scripts cannot access: + +* Object type information through standard Java methods (like [`get_Class()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#getClass--) or [reflection](https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html)) +* Stack traces beyond Painless script boundaries +* Runtime class details for security-sensitive operations + +## Debug methods available + +Painless scripts do not provide interactive debugging tools or a REPL. The only official method for inspecting objects and their types during script execution is by means of the`Debug.explain(object)`. This method throws an informative exception that reveals the object's type and value. As a result, your script does not complete normally; instead, you an error message is included in the response. + +You can use custom exceptions (for example, `throw new RuntimeException(...)`) to signal specific conditions or control execution flow but do not provide detailed object inspection. For comprehensive debugging, always use `Debug.explain()`. + +## Debugging walkthrough + +This section demonstrates a common debugging scenario using a script that formats the total price as "TOTAL: X" in uppercase. The example shows how to identify an error, debug it using `Debug.explain()`, and apply the fix. + +### Step 1: Run the failing script + +The following script attempts to create a formatted price string from the [ecommerce dataset](/reference/scripting-languages/painless/painless-context-examples.md). The script appears logical but fails: + +```json +GET /kibana_sample_data_ecommerce/_search { + "size": 1, "query": { - "script": { - "script": "Debug.explain(doc.goals)" + "match_all": {} + }, + "script_fields": { + "formatted_total": { + "script": { + "source": """ + // This will fail - trying to call toUpperCase() on a number + return "TOTAL: " + doc['taxful_total_price'].toUpperCase(); + """ + } } } } ``` -% TEST[s/_explain\/1/_explain\/1?error_trace=false/ catch:/painless_explain_error/] -% The test system sends error_trace=true by default for easier debugging so -% we have to override it to get a normal shaped response -Which shows that the class of `doc.first` is `org.elasticsearch.index.fielddata.ScriptDocValues.Longs` by responding with: +### Step 2: Analyze the error -```console-result +The script fails with this error message: + +```json { - "error": { - "type": "script_exception", - "to_string": "[1, 9, 27]", - "painless_class": "org.elasticsearch.index.fielddata.ScriptDocValues.Longs", - "java_class": "org.elasticsearch.index.fielddata.ScriptDocValues$Longs", - ... - }, - "status": 400 + "took": 8, + "timed_out": false, + "_shards": { + "total": 3, + "successful": 2, + "skipped": 0, + "failed": 1, + "failures": [ + { + "shard": 0, + "index": "kibana_sample_data_ecommerce", + "node": "Rcc7GqOuTta0MV28FZXE_A", + "reason": { + "type": "script_exception", + "reason": "runtime error", + "script_stack": [ + """return "TOTAL: " + doc['taxful_total_price'].toUpperCase(); + """, + " ^---- HERE" + ], + "script": " ...", + "lang": "painless", + "position": { + "offset": 62, + "start": 14, + "end": 90 + }, + "caused_by": { + "type": "illegal_argument_exception", + "reason": "dynamic method [java.lang.Double, toUpperCase/0] not found" + } + } + } + ] + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": 1, + "hits": [] + } } ``` -% TESTRESPONSE[s/\.\.\./"script_stack": $body.error.script_stack, "script": $body.error.script, "lang": $body.error.lang, "position": $body.error.position, "caused_by": $body.error.caused_by, "root_cause": $body.error.root_cause, "reason": $body.error.reason/] -You can use the same trick to see that `_source` is a `LinkedHashMap` in the `_update` API: +The error indicates that the script attempts to call `toUpperCase()` on a `Double` object, but this method does not exist for numeric types. While the error message provides information about the problem; use `Debug.explain()` to gain additional clarity about the data type. -```console -POST /hockey/_update/1 +### Step 3: Use Debug.explain() to understand the problem + +Use `Debug.explain()` to inspect the data type: + +```json +GET /kibana_sample_data_ecommerce/_search +{ + "size": 1, + "query": { + "match_all": {} + }, + "script_fields": { + "debug_price_type": { + "script": { + "source": """ + Debug.explain(doc['taxful_total_price']); + return "Check the error for debugging information."; + """ + } + } + } +} +``` + +The debugging output reveals the data structure: + +```json { - "script": "Debug.explain(ctx._source)" + "took": 9, + "timed_out": false, + "_shards": { + "total": 3, + "successful": 2, + "skipped": 0, + "failed": 1, + "failures": [ + { + "shard": 0, + "index": "kibana_sample_data_ecommerce", + "node": "Rcc7GqOuTta0MV28FZXE_A", + "reason": { + "type": "script_exception", + "reason": "runtime error", + "painless_class": "org.elasticsearch.index.fielddata.ScriptDocValues$Doubles", + "to_string": "[46.96875]", + "java_class": "org.elasticsearch.index.fielddata.ScriptDocValues$Doubles", + "script_stack": [ + """Debug.explain(doc['taxful_total_price']); + """, + " ^---- HERE" + ], + "script": " ...", + "lang": "painless", + "position": { + "offset": 46, + "start": 11, + "end": 62 + }, + "caused_by": { + "type": "painless_explain_error", + "reason": null + } + } + } + ] + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": 1, + "hits": [] + } } ``` -% TEST[continued s/_update\/1/_update\/1?error_trace=false/ catch:/painless_explain_error/] -The response looks like: +The output shows that `doc['taxful_total_price']` is an `org.elasticsearch.index.fielddata.ScriptDocValues$Doubles` object with the value `[46.96875]`. This is an {{es}} class for handling numeric field values. +To access the actual double value, use `doc['taxful_total_price'].value`. + +Check the actual value: -```console-result +```json +GET /kibana_sample_data_ecommerce/_search { - "error" : { - "root_cause": ..., - "type": "illegal_argument_exception", - "reason": "failed to execute script", - "caused_by": { - "type": "script_exception", - "to_string": "{gp=[26, 82, 1], last=gaudreau, assists=[17, 46, 0], first=johnny, goals=[9, 27, 1]}", - "painless_class": "java.util.LinkedHashMap", - "java_class": "java.util.LinkedHashMap", - ... + "size": 1, + "query": { + "match_all": {} + }, + "script_fields": { + "debug_price_value": { + "script": { + "source": """ + Debug.explain(doc['taxful_total_price'].value); + return "Check the error for debugging information."; + """ + } } + } +} +``` + +This produces the following output: + +```json +{ + "took": 5, + "timed_out": false, + "_shards": { + "total": 3, + "successful": 2, + "skipped": 0, + "failed": 1, + "failures": [ + { + "shard": 0, + "index": "kibana_sample_data_ecommerce", + "node": "Rcc7GqOuTta0MV28FZXE_A", + "reason": { + "type": "script_exception", + "reason": "runtime error", + "painless_class": "java.lang.Double", + "to_string": "46.96875", + "java_class": "java.lang.Double", + "script_stack": [ + """Debug.explain(doc['taxful_total_price'].value); + """, + " ^---- HERE" + ], + "script": " ...", + "lang": "painless", + "position": { + "offset": 46, + "start": 11, + "end": 62 + }, + "caused_by": { + "type": "painless_explain_error", + "reason": null + } + } + } + ] + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": 1, + "hits": [] + } +} +``` + +The output confirms that `doc['taxful_total_price'].value` is a `java.lang.Double` with the value of `46.96875`. + +This demonstrates the problem: the script attempts to call `toUpperCase()` on a number, which is not supported. + +### Step 4: Fix the script + +Apply the fix by converting the number to a string first: + +```json +GET /kibana_sample_data_ecommerce/_search +{ + "size": 1, + "query": { + "match_all": {} }, - "status": 400 + "script_fields": { + "formatted_total": { + "script": { + "source": """ + // Convert the price to String before applying toUpperCase() + return "TOTAL: " + String.valueOf(doc['taxful_total_price'].value).toUpperCase(); + """ + } + } + } } ``` -% TESTRESPONSE[s/"root_cause": \.\.\./"root_cause": $body.error.root_cause/] -% TESTRESPONSE[s/\.\.\./"script_stack": $body.error.caused_by.script_stack, "script": $body.error.caused_by.script, "lang": $body.error.caused_by.lang, "position": $body.error.caused_by.position, "caused_by": $body.error.caused_by.caused_by, "reason": $body.error.caused_by.reason/] -% TESTRESPONSE[s/"to_string": ".+"/"to_string": $body.error.caused_by.to_string/] -Once you have a class you can go to [*Painless API Reference*](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference.html) to see a list of available methods. +### Step 5: Verify the solution works + +The corrected script now executes successfully and returns the expected result: + +```json +{ + "took": 14, + "timed_out": false, + "_shards": { + "total": 3, + "successful": 3, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": 1, + "hits": [ + { + "_index": "kibana_sample_data_ecommerce", + "_id": "z_vDyZgBvpJrRKrKcvig", + "_score": 1, + "fields": { + "formatted_total": [ + "TOTAL: 46.96875" + ] + } + } + ] + } +} +``` +## Notes +* **Data types in Painless**: Numeric fields in {{es}} are represented as Java numeric types (`Double`, `Integer`), not as `String`. +* **Field access in Painless**: When accessing a single-valued numeric field like `taxful_total_price`, use `.value` to get the actual numeric value. +* **Debug.explain() reveals object details**: Shows both the type of object (`java.lang.Double`) and its actual value (`46.96875`), which is useful for understanding how to work with it. +* **Type conversion**: Convert data types appropriately before applying type-specific methods. diff --git a/docs/reference/scripting-languages/painless/painless-field-context.md b/docs/reference/scripting-languages/painless/painless-field-context.md index ac103db733433..f423e2502f589 100644 --- a/docs/reference/scripting-languages/painless/painless-field-context.md +++ b/docs/reference/scripting-languages/painless/painless-field-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-field-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,11 @@ products: Use a Painless script to create a [script field](/reference/elasticsearch/rest-apis/retrieve-selected-fields.md#script-fields) to return a customized value for each document in the results of a query. -**Variables** +:::{tip} +To create dynamic fields with more capabilities, consider using [runtime fields](docs-content://manage-data/data-store/mapping/runtime-fields.md) instead. Runtime fields can be used in the query phase to select documents and in aggregations, while script fields only work during the fetch phase to decorate already selected results. +::: + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -20,101 +27,106 @@ Use a Painless script to create a [script field](/reference/elasticsearch/rest-a [`params['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) (`Map`, read-only) : Contains extracted JSON in a `Map` and `List` structure for the fields existing in a stored document. -**Return** +## Return `Object` : The customized value for each document. -**API** - -Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and [Specialized Field API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-field.html) are available. +## API -**Example** +Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and specialized [Field API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-field.html) are available. -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +## Example -You can then use these two example scripts to compute custom information for each search hit and output it to two new fields. +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -The first script gets the doc value for the `datetime` field and calls the `getDayOfWeekEnum` function to determine the corresponding day of the week. +The first script extracts the day index from the `day_of_week_i` field to determine whether the order was placed on a weekday or during the weekend: -```painless -doc['datetime'].value.getDayOfWeekEnum().getDisplayName(TextStyle.FULL, Locale.ROOT) +```java +doc['day_of_week_i'].value >= 5 ? 'Weekend' : 'Weekday' ``` -The second script calculates the number of actors. Actors' names are stored as a keyword array in the `actors` field. +The second script evaluates the `total_quantity` field to classify the order size: -```painless -doc['actors'].size() <1> -``` +```java +long tq = doc['total_quantity'].value; -1. By default, doc values are not available for `text` fields. If `actors` was a `text` field, you could still calculate the number of actors by extracting values from `_source` with `params['_source']['actors'].size()`. +if (tq == 1) return 'Single Item'; +if (tq <= 3) return 'Small Order'; +if (tq <= 6) return 'Medium Order'; +return 'Large Order'; +``` -The following request returns the calculated day of week and the number of actors that appear in each play: +The following request uses both scripts to categorize each order by the day it was placed and by its size: -```console -GET seats/_search +```json +GET kibana_sample_data_ecommerce/_search { - "size": 2, + "size": 1, "query": { "match_all": {} }, "script_fields": { - "day-of-week": { + "is_weekend_shopper": { "script": { - "source": "doc['datetime'].value.getDayOfWeekEnum().getDisplayName(TextStyle.FULL, Locale.ENGLISH)" + "source": "doc['day_of_week_i'].value >= 5 ? 'Weekend' : 'Weekday'" } }, - "number-of-actors": { + "order_size_category": { "script": { - "source": "doc['actors'].size()" + "source": """ + long qty = doc['total_quantity'].value; + + if (qty == 1) return 'Single Item'; + if (qty <= 3) return 'Small Order'; + if (qty <= 6) return 'Medium Order'; + + return 'Large Order'; + """ } } - } + }, + "fields": ["day_of_week_i", "total_quantity"] } ``` -% TEST[setup:seats] -```console-result -{ - "took" : 68, - "timed_out" : false, - "_shards" : { - "total" : 1, - "successful" : 1, - "skipped" : 0, - "failed" : 0 +Response: + +```json + + { + "took": 0, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 }, - "hits" : { - "total" : { - "value" : 11, - "relation" : "eq" + "hits": { + "total": { + "value": 4675, + "relation": "eq" }, - "max_score" : 1.0, - "hits" : [ + "max_score": 1, + "hits": [ { - "_index" : "seats", - "_id" : "1", - "_score" : 1.0, - "fields" : { - "day-of-week" : [ - "Thursday" + "_index": "kibana_sample_data_ecommerce", + "_id": "ZkUZjJgBMSQotAoT_Jcg", + "_score": 1, + "fields": { + "order_size_category": [ + "Small Order" + ], + "is_weekend_shopper": [ + "Weekday" ], - "number-of-actors" : [ + "day_of_week_i": [ 4 - ] - } - }, - { - "_index" : "seats", - "_id" : "2", - "_score" : 1.0, - "fields" : { - "day-of-week" : [ - "Thursday" ], - "number-of-actors" : [ - 1 + "total_quantity": [ + 2 ] } } @@ -122,5 +134,4 @@ GET seats/_search } } ``` -% TESTRESPONSE[s/"took" : 68/"took" : "$body.took"/] diff --git a/docs/reference/scripting-languages/painless/painless-filter-context.md b/docs/reference/scripting-languages/painless/painless-filter-context.md index 4a8868afce090..462b025a7c676 100644 --- a/docs/reference/scripting-languages/painless/painless-filter-context.md +++ b/docs/reference/scripting-languages/painless/painless-filter-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-filter-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,7 @@ products: Use a Painless script as a [filter](/reference/query-languages/query-dsl/query-dsl-script-query.md) in a query to include and exclude documents. -**Variables** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -17,38 +20,38 @@ Use a Painless script as a [filter](/reference/query-languages/query-dsl/query-d `doc` (`Map`, read-only) : Contains the fields of the current document where each field is a `List` of values. -**Return** +## Return `boolean` : Return `true` if the current document should be returned as a result of the query, and `false` otherwise. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. -**Example** +## Example -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -This script finds all unsold documents that cost less than $25. +This example filters documents where the average price per item in an order exceeds a minimum threshold of 30\. -```painless -doc['sold'].value == false && doc['cost'].value < 25 -``` - -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 $25. +:::{tip} +Filters answer the question “Does this document match?” with a simple “Yes or No” response. Use filter context for all conditions that don’t need scoring. Refer to [Query and filter context](/reference/query-languages/query-dsl/query-filter-context.md) to learn more. +::: -```console -GET seats/_search +```json +GET kibana_sample_data_ecommerce/_search { "query": { "bool": { "filter": { "script": { "script": { - "source": "doc['sold'].value == false && doc['cost'].value < params.cost", + "source": """ + (doc['taxful_total_price'].value / doc['total_quantity'].value) > params.min_avg_price + """, "params": { - "cost": 25 + "min_avg_price": 30 } } } @@ -57,4 +60,3 @@ GET seats/_search } } ``` -% TEST[setup:seats] diff --git a/docs/reference/scripting-languages/painless/painless-functions.md b/docs/reference/scripting-languages/painless/painless-functions.md index b88023c39db46..2e4619f1f3c81 100644 --- a/docs/reference/scripting-languages/painless/painless-functions.md +++ b/docs/reference/scripting-languages/painless/painless-functions.md @@ -1,21 +1,72 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-functions.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Functions [painless-functions] -A function is a named piece of code comprised of one-to-many statements to perform a specific task. A function is called multiple times in a single script to repeat its specific task. A parameter is a named type value available as a [variable](/reference/scripting-languages/painless/painless-variables.md) within the statement(s) of a function. A function specifies zero-to-many parameters, and when a function is called a value is specified per parameter. An argument is a value passed into a function at the point of call. A function specifies a return type value, though if the type is [void](/reference/scripting-languages/painless/painless-types.md#void-type) then no value is returned. Any non-void type return value is available for use within an [operation](/reference/scripting-languages/painless/painless-operators.md) or is discarded otherwise. +A function is a reusable block of code that performs a specific task and can be called multiple times throughout your script. Functions help organize your code, reduce repetition, and make complex scripts more maintainable and readable. -You can declare functions at the beginning of a Painless script, for example: +Functions in Painless work similarly to Java functions, allowing you to encapsulate logic, accept input parameters, and return calculated results. You can define custom functions to handle common operations, calculations, or data transformations that your script needs to perform repeatedly. -```painless -boolean isNegative(def x) { x < 0 } -... -if (isNegative(someVar)) { - ... -} -``` +## Function structure +A function is a named piece of code composed of one-to-many statements to perform a specific task. A function is called multiple times in a single script to repeat its specific task. A parameter is a named type value available as a [variable](/reference/scripting-languages/painless/painless-variables.md) within the statement(s) of a function. A function specifies zero-to-many parameters, and when a function is called, a value is specified per parameter. An argument is a value passed into a function at the point of call. A function specifies a type value, though if the type is [void](/reference/scripting-languages/painless/painless-types.md#void-type) then no value is returned. Any non-void type return value is available for use within an [operation](/reference/scripting-languages/painless/painless-operators.md) or is discarded otherwise. + +## Function declaration + +You can declare functions at the beginning of a Painless script. Functions must be declared before they are used in your script. + +### Examples + +* Function with return value: + + This code calculates the VAT tax on an `amount = 100` + + ```java + double calculateVAT(double amount) { + return amount * 0.19; // return double type + } + + return calculateVAT(100); + ``` + +* Function with void return type: + + This snippet uses a function called `addName` to add the name “Elyssa” to a list of names. + + ```java + void addName(List l, String n) { + l.add(n); + } + + List names = new ArrayList(); + addName(names, "Elyssa"); + ``` + +* Simple boolean function: + + This code uses a boolean function to evaluate the `if` statement. If the customer is prime, the code inside the statement is run; otherwise, it continues with the next part of the program. + + ```java + boolean hasFreeShipping(def customer) { customer.isPrime } + ... + if (hasFreeShipping(client)) { + ... + } + ``` + +## Best practices + +When you write functions in Painless: + +* Use descriptive names that clearly indicate what the function does. +* Define appropriate parameter types to ensure type safety. +* Keep functions focused on a single, well-defined task. +* Return consistent types to make functions predictable. +* Declare functions at the beginning of your script for clarity. diff --git a/docs/reference/scripting-languages/painless/painless-identifiers.md b/docs/reference/scripting-languages/painless/painless-identifiers.md index bdf8c22c489b5..17fd9cc684d66 100644 --- a/docs/reference/scripting-languages/painless/painless-identifiers.md +++ b/docs/reference/scripting-languages/painless/painless-identifiers.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-identifiers.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,29 +12,62 @@ products: Use an identifier as a named token to specify a [variable](/reference/scripting-languages/painless/painless-variables.md), [type](/reference/scripting-languages/painless/painless-types.md), [field](/reference/scripting-languages/painless/painless-operators-reference.md#field-access-operator), [method](/reference/scripting-languages/painless/painless-operators-reference.md#method-call-operator), or [function](/reference/scripting-languages/painless/painless-functions.md). -**Errors** +:::{important} +Identifiers cannot be [keywords](/reference/scripting-languages/painless/painless-keywords.md). Using a keyword as an identifier will result in a compilation error. +::: -If a [keyword](/reference/scripting-languages/painless/painless-keywords.md) is used as an identifier. +Identifiers are the names you give to elements in your code. They must follow specific naming rules and provide meaningful names that clearly describe their purpose. -**Grammar** +## Grammar -```text +```java ID: [_a-zA-Z] [_a-zA-Z-0-9]*; ``` -**Examples** +## Naming rules -* Variations of identifiers. +* Must start with a letter (a-z, A-Z) or underscore (\_) +* Can contain letters, numbers (0-9), and underscores +* Case sensitive (`myVar` and `MyVar` are different) +* Cannot be a [keyword](/reference/scripting-languages/painless/painless-keywords.md) - ```painless - a - Z - id - list - list0 - MAP25 - _map25 - Map_25 +### Examples + +* Variations of identifiers + + ```text + a <1> + Z <2> + id <3> + list <4> + list0 <5> + MAP25 <6> + _map25 <7> + Map_25 <8> ``` + 1. int a = 10; + 2. String Z = "Z"; + 3. String id = "user_123"; + 4. boolean list = true; + 5. float list0 = 3.14f; + 6. long MAP25 = 123456789L; + 7. double _map25 = 99.99; + 8. byte Map_25 = 25; + +## Best practices + +Choose meaningful identifier names that clearly describe their purpose: +``` +// Good: descriptive and clear +String customerEmail = "user@example.com"; +int totalPrice = calculateTotal(); +boolean isProductAvailable = checkInventory(); + +// Avoid: unclear or too short +String s = "user@example.com"; +int x = calculateTotal(); +boolean b = checkInventory(); + +``` diff --git a/docs/reference/scripting-languages/painless/painless-ingest-processor-context.md b/docs/reference/scripting-languages/painless/painless-ingest-processor-context.md index 5990fc9674236..ae04042ed7f12 100644 --- a/docs/reference/scripting-languages/painless/painless-ingest-processor-context.md +++ b/docs/reference/scripting-languages/painless/painless-ingest-processor-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-ingest-processor-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,23 @@ products: Use a Painless script in an [ingest processor](/reference/enrich-processor/script-processor.md) to modify documents upon insertion. -**Variables** +The ingest processor context enables document transformation during the indexing process, allowing you to enrich, modify, or restructure data before it’s stored in {{es}}. +Painless scripts run as script processors within ingest pipelines that support script execution. + +Ingest pipelines consist of multiple processors that can transform documents sequentially. The script processor allows Painless scripts to access and modify document fields using the `ctx` variable during this transformation process. + +## Processing workflow + +The pipelines processing proceeds through four steps. + +* Documents enter the ingest pipeline. +* Each processor transforms the document according to its configuration. +* Script processors execute Painless code to perform custom transformations. +* Modified documents are indexed into {{es}}. + +For more information refer to [{{es}} ingest pipelines](docs-content://manage-data/ingest/transform-enrich/ingest-pipelines.md). You can also check the troubleshooting guide for help with ingest pipelines failures. + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -20,7 +39,7 @@ Use a Painless script in an [ingest processor](/reference/enrich-processor/scrip `ctx` (`Map`) : Contains extracted JSON in a `Map` and `List` structure for the fields that are part of the document. -**Side Effects** +## Side Effects [`ctx['_index']`](/reference/elasticsearch/mapping-reference/mapping-index-field.md) : Modify this to change the destination index for the current document. @@ -28,94 +47,44 @@ Use a Painless script in an [ingest processor](/reference/enrich-processor/scrip `ctx` (`Map`) : Modify the values in the `Map/List` structure to add, modify, or delete the fields of a document. -**Return** +## Return void : No expected return value. -**API** - -Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and [Specialized Ingest API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-ingest.html) are available. - -**Example** - -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). - -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` or `PM`. - -The following ingest script processes the date and time `Strings` and stores the result in a `datetime` field. - -```painless -String[] dateSplit = ctx.date.splitOnToken("-"); <1> -String year = dateSplit[0].trim(); -String month = dateSplit[1].trim(); +## API -if (month.length() == 1) { <2> - month = "0" + month; -} - -String day = dateSplit[2].trim(); +Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and specialized [Field API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-field.html) are available. -if (day.length() == 1) { <3> - day = "0" + day; -} +## Example -boolean pm = ctx.time.substring(ctx.time.length() - 2).equals("PM"); <4> -String[] timeSplit = ctx.time.substring(0, - ctx.time.length() - 2).splitOnToken(":"); <5> -int hours = Integer.parseInt(timeSplit[0].trim()); -int minutes = Integer.parseInt(timeSplit[1].trim()); +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -if (pm) { <6> - hours += 12; -} +The following example demonstrates how to use a script inside an ingest pipeline to create a new field named `custom_region_code`. This field combines the `geoip.country_iso_code` and the first two uppercase letters of `geoip.continent_name`. -String dts = year + "-" + month + "-" + day + "T" + - (hours < 10 ? "0" + hours : "" + hours) + ":" + - (minutes < 10 ? "0" + minutes : "" + minutes) + - ":00+08:00"; <7> - -ZonedDateTime dt = ZonedDateTime.parse( - dts, DateTimeFormatter.ISO_OFFSET_DATE_TIME); <8> -ctx.datetime = dt.getLong(ChronoField.INSTANT_SECONDS)*1000L; <9> +```java +String country = ctx.geoip.country_iso_code; + +ctx.custom_region_code = country + '_' + ctx.geoip.continent_name.substring(0,2).toUpperCase(); ``` -1. Uses the `splitOnToken` function to separate the date `String` from the seat data into year, month, and day `Strings`. - NOTE : The use of the `ctx` ingest processor context variable to retrieve the data from the `date` field. - -2. Appends the [string literal](/reference/scripting-languages/painless/painless-literals.md#string-literals) `"0"` value to a single digit month since the format of the seat data allows for this case. -3. Appends the [string literal](/reference/scripting-languages/painless/painless-literals.md#string-literals) `"0"` value to a single digit day since the format of the seat data allows for this case. -4. Sets the [`boolean type`](/reference/scripting-languages/painless/painless-types.md#primitive-types) [variable](/reference/scripting-languages/painless/painless-variables.md) to `true` if the time `String` is a time in the afternoon or evening. - NOTE: The use of the `ctx` ingest processor context variable to retrieve the data from the `time` field. - -5. Uses the `splitOnToken` function to separate the time `String` from the seat data into hours and minutes `Strings`. - NOTE: The use of the `substring` method to remove the `AM` or `PM` portion of the time `String`. The use of the `ctx` ingest processor context variable to retrieve the data from the `date` field. +The following request runs during ingestion time and uses the `ctx` object to access and modify the document fields. -6. If the time `String` is an afternoon or evening value adds the [integer literal](/reference/scripting-languages/painless/painless-literals.md#integer-literals) `12` to the existing hours to move to a 24-hour based time. -7. Builds a new time `String` that is parsable using existing API methods. -8. Creates a `ZonedDateTime` [reference type](/reference/scripting-languages/painless/painless-types.md#reference-types) value by using the API method `parse` to parse the new time `String`. -9. Sets the datetime field `datetime` to the number of milliseconds retrieved from the API method `getLong`. - NOTEL The use of the `ctx` ingest processor context variable to set the field `datetime`. Manipulate each document’s fields with the `ctx` variable as each document is indexed. - - - - -Submit the following request: - -```console -PUT /_ingest/pipeline/seats +```json +PUT /_ingest/pipeline/kibana_sample_data_ecommerce-custom_region_code { - "description": "update datetime for seats", + "description": "generate region code from country and continent name for kibana_sample_data_ecommerce dataset", "processors": [ { "script": { - "source": "String[] dateSplit = ctx.date.splitOnToken('-'); 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 = ctx.time.substring(0, ctx.time.length() - 2).splitOnToken(':'); 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;" + "lang": "painless", + "source": """ + String country = ctx.geoip.country_iso_code; + + ctx.custom_region_code = country + '_' + ctx.geoip.continent_name.substring(0,2).toUpperCase(); + """ } } ] } ``` - diff --git a/docs/reference/scripting-languages/painless/painless-keywords.md b/docs/reference/scripting-languages/painless/painless-keywords.md index 3b5e6c85c2568..57985f8cc033b 100644 --- a/docs/reference/scripting-languages/painless/painless-keywords.md +++ b/docs/reference/scripting-languages/painless/painless-keywords.md @@ -1,23 +1,62 @@ --- +navigation_title: Keywords mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-keywords.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- -# Keywords [painless-keywords] +# Keywords (reserved terms) [painless-keywords] -Keywords are reserved tokens for built-in language features. +Keywords are reserved tokens for built-in language features in Painless. These special words have predefined meanings and cannot be used as [identifiers](/reference/scripting-languages/painless/painless-identifiers.md), such as variable names, function names, or field names. -**Errors** +:::{important} +In Painless documentation, "keywords" refers to reserved words in the scripting language itself. They are different from the {{es}} [`keyword`](/reference/elasticsearch/mapping-reference/keyword.md#keyword-field-type) field type, which is used for exact-value searches and aggregations in your data mappings. +::: -* If a keyword is used as an [identifier](/reference/scripting-languages/painless/painless-identifiers.md). +When you write Painless scripts, keywords provide the fundamental building blocks for creating logic, defining data types, and controlling program flow. Since these words have special significance to the Painless compiler, attempting to use them for other purposes results in compilation errors. -**Keywords** +### List of keywords: -| | | | | | -| --- | --- | --- | --- | --- | | if | else | while | do | for | +| :---- | :---- | :---- | :---- | :---- | | in | continue | break | return | new | | try | catch | throw | this | instanceof | +## Understanding keyword restrictions + +Examples of restricted terms include `if`, which tells the compiler to create a conditional statement, and `int`, which declares an integer variable type. + +### Examples of valid keyword usage: + +``` +// Keywords used correctly for their intended purpose +int count = 0; // `int' declares integer type +boolean isActive = true; // 'boolean' declares boolean type, 'true' is literal +if (count > 0) { // 'if' creates conditional logic + return count; // 'return' exits with value +} + +``` + +### Errors + +If a keyword is used as an identifier Painless generates a compilation error: + +``` +// These will cause compilation errors +int if = 10; // Cannot use 'if' as variable name +String return = "value"; // Cannot use 'return' as variable name +boolean int = false; // Cannot use 'int' as variable name + +// Use descriptive names instead +int count = 10; +String result = "value"; +boolean isEnabled = false; + +``` + +These restrictions ensure that your scripts remain readable and that the Painless compiler can correctly parse your code without ambiguity. diff --git a/docs/reference/scripting-languages/painless/painless-lambdas.md b/docs/reference/scripting-languages/painless/painless-lambdas.md index fc8226b680ab0..8757a423e1e85 100644 --- a/docs/reference/scripting-languages/painless/painless-lambdas.md +++ b/docs/reference/scripting-languages/painless/painless-lambdas.md @@ -1,21 +1,54 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-lambdas.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Lambdas [painless-lambdas] -Lambda expressions and method references work the same as in [Java](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.md). +Lambda expressions are anonymous functions that provide a concise way to write short, inline functions without declaring them explicitly. Lambdas are particularly useful for functional programming operations such as filtering, mapping, and sorting collections. -```painless -list.removeIf(item -> item == 2); -list.removeIf((int item) -> item == 2); -list.removeIf((int item) -> { item == 2 }); -list.sort((x, y) -> x - y); -list.sort(Integer::compare); +Lambda expressions and method references work the same as in [Java](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html), providing familiar syntax for developers. They allow you to write more compact and expressive code when working with collections and functional interfaces. + +## Lambda syntax + +Lambdas use the arrow syntax (`->`) to separate parameters from the function body. You can use lambdas with or without explicit type declarations. + +### Examples + +Basic lambda expressions: + +```java +// Removes all elements equal to 2 +list.removeIf(item -> item == 2); +list.removeIf((int item) -> item == 2); +list.removeIf((int item) -> { item == 2 }); + +// Sorts list in ascending order +list.sort((x, y) -> x - y); +// Sorts list in ascending order using method reference +list.sort(Integer::compare); ``` -You can make method references to functions within the script with `this`, for example `list.sort(this::mycompare)`. +## Method references + +You can make method references to functions within the script with `this`, for example `list.sort(this::mycompare)`. Method references provide a shorthand notation for lambdas that call a specific method. + +## Common use cases + +Lambdas are commonly used for: + +* **Filtering collections:** Remove elements that meet specific criteria. +* **Sorting data:** Define custom comparison logic. +* **Transforming values:** Apply operations to collection elements. +* **Functional operations:** Work with streams and functional interfaces. + + + + + diff --git a/docs/reference/scripting-languages/painless/painless-language-specification.md b/docs/reference/scripting-languages/painless/painless-language-specification.md index 7074a79421721..ed10c6c76547d 100644 --- a/docs/reference/scripting-languages/painless/painless-language-specification.md +++ b/docs/reference/scripting-languages/painless/painless-language-specification.md @@ -1,31 +1,104 @@ --- +navigation_title: Painless language specification mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-lang-spec.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- -# Painless language specification [painless-lang-spec] +# Painless language specification (syntax) [painless-lang-spec] -Painless is a scripting language designed for security and performance. Painless syntax is similar to Java syntax along with some additional features such as dynamic typing, Map and List accessor shortcuts, and array initializers. As a direct comparison to Java, there are some important differences, especially related to the casting model. For more detailed conceptual information about the basic constructs that Painless and Java share, refer to the corresponding topics in the [Java Language Specification](https://docs.oracle.com/javase/specs/jls/se8/html/index.md). +Painless is a scripting language designed for security and performance in {{es}}. +Painless syntax closely resembles Java syntax while providing additional scripting-focused features: -Painless scripts are parsed and compiled using the [ANTLR4](https://www.antlr.org/) and [ASM](https://asm.ow2.org/) libraries. Scripts are compiled directly into Java Virtual Machine (JVM) byte code and executed against a standard JVM. This specification uses ANTLR4 grammar notation to describe the allowed syntax. However, the actual Painless grammar is more compact than what is shown here. +* Dynamic typing +* Map and list accessor shortcuts +* Array Initializers +* Object simplified object manipulation +Built on the Java Virtual Machine (JVM), Painless compiles directly into bytecode and runs in a controlled sandbox environment optimized for {{es}} scripting requirements. +For information about basic constructs that Painless and Java share, refer to corresponding topics in the [Java Language Specification](https://docs.oracle.com/javase/specs/jls/se8/html/index.md). However, understanding the differences in Painless is essential for effective script development. +## Compilation process +Painless scripts are parsed and compiled using the [ANTLR4](https://www.antlr.org/) and [ASM](https://asm.ow2.org/) libraries. Scripts are compiled directly into Java Virtual Machine (JVM) bytecode and executed against a standard JVM. +:::{image} images/painless-compilation-process.png +:alt: Painless compilation process +::: +### Step breakdown: +1. **Script input:** Painless code is embedded in JSON queries. +2. **Compilation:** ANTLR4 and ASM libraries parse and compile the script into JVM bytecode. +3. **Execution:** Bytecode runs on the standard Java Virtual Machine. + +This documentation presents the Painless language syntax in an educational format, optimized for developer understanding. The underlying grammar, however, is implemented using more concise ANTLR4 rules for efficient parsing. +## Context-aware syntax +Painless syntax availability and behavior vary by execution context, ensuring scripts are safe within their intended environment. Each context directly affects script functionality since they provide a set of variables, API restrictions, and the expected data types a script will return. +This context-aware design allows Painless to optimize performance and security for each use case. For comprehensive information about available contexts and their specific capabilities, refer to [Painless contexts](/reference/scripting-languages/painless/painless-contexts.md). +### Each context defines: +* **Available variables:** Context-specific variables such as `doc`, `ctx`, `_source`, or specialized objects depending on the execution context +* **API allowlist:** Permitted classes, methods, and fields from Java and {{es}} APIs +* **Return type expectations:** Expected script output format and type constraints +Understanding context-syntax relationships is essential for effective Painless development. For detailed information about context-syntax patterns and practical examples, refer to [Painless syntax-context bridge](docs-content://explore-analyze/scripting/painless-syntax-context-bridge.md). +:::{image} images/painless-integration-points.png +:alt: Painless integration-points +::: +Double-click to expand the image. +### Where to write Painless scripts: +* [**Dev tools console**](docs-content://explore-analyze/query-filter/tools/console.md)**:** Interactive script development and testing +* [**Ingest pipelines:**](docs-content://manage-data/ingest/transform-enrich/ingest-pipelines.md) Document processing during indexing +* [**Update API:**](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update) Single and bulk document modifications +* [**Search queries:**](docs-content://solutions/search/querying-for-search.md) Custom scoring, filtering, and field generation +* [**Runtime fields:**](docs-content://manage-data/data-store/mapping/runtime-fields.md) Dynamic field computation at query time +* [**Watchers:**](docs-content://explore-analyze/alerts-cases/watcher.md) Alert conditions and notification actions +* [**Reindex API:**](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex) Data transformation during index migration +* [**Aggregations:**](docs-content://explore-analyze/query-filter/aggregations.md) Custom calculations and bucket processing +Each integration point corresponds to a specific Painless context with distinct capabilities and variable access patterns. +## Technical differences from Java + +Painless has implemented key differences from Java in order to optimize security and scripting performance: + +* **Dynamic typing with `def`:** Runtime type determination for flexible variable handling +* **Enhanced collection access:** Direct syntax shortcuts for Maps (`map.key`) and Lists (`list[index]`) +* **Stricter casting model:** Explicit type conversions prevent runtime errors +* **Reference vs content equality:** `==` calls `.equals()`method, `===` for reference comparison +* **Security restrictions:** No reflection APIs, controlled class access through allowlists +* **Automatic safety controls:** Loop iteration limits and recursion prevention + +These differences ensure safe execution while maintaining familiar Java-like syntax for developers. + +## JavaScript in Painless + +Painless integrates with Lucene’s expression language, enabling JavaScript syntax for high-performance mathematical operations and custom functions within {{es}}. + +### Key capabilities: + +* **Performance optimization:** Compiles directly to bytecode for native execution speed +* **Mathematical functions:** Access to specialized mathematical functions for scoring calculations +* **Field access:** Streamlined `doc\['field'].value` syntax for document field operations + +### Limitations: + +* Restricted to mathematical expressions and field access operations +* No complex control flow or custom function definitions +* Limited to numeric and boolean data types + +JavaScript expressions through Lucene provide a specialized, high-performance option designed specifically for custom ranking and sorting functions. For comprehensive information about Javascript expression capabilities, syntax examples, and usage patterns, refer to [Lucene Expressions Language](docs-content://explore-analyze/scripting/modules-scripting-expression.md). diff --git a/docs/reference/scripting-languages/painless/painless-literals.md b/docs/reference/scripting-languages/painless/painless-literals.md index 4c655869aa4d8..7b1c10e1cc6df 100644 --- a/docs/reference/scripting-languages/painless/painless-literals.md +++ b/docs/reference/scripting-languages/painless/painless-literals.md @@ -1,69 +1,69 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-literals.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Literals [painless-literals] -Use a literal to specify a value directly in an [operation](/reference/scripting-languages/painless/painless-operators.md). +Literals represent fixed values in your code that are directly written into your script. Unlike variables, literals have constant values that cannot be changed during script runtime. The three supported types of literals are: integers, floats, and strings. ## Integers [integer-literals] -Use an integer literal to specify an integer type value in decimal, octal, or hex notation of a [primitive type](/reference/scripting-languages/painless/painless-types.md#primitive-types) `int`, `long`, `float`, or `double`. Use the following single letter designations to specify the primitive type: `l` or `L` for `long`, `f` or `F` for `float`, and `d` or `D` for `double`. If not specified, the type defaults to `int`. Use `0` as a prefix to specify an integer literal as octal, and use `0x` or `0X` as a prefix to specify an integer literal as hex. +Use an integer literal to specify an integer-type value in decimal, octal, or hex notation of a [primitive type](https://www.elastic.co/docs/reference/scripting-languages/painless/painless-types#primitive-types) `int`, `long`, `float`, or `double`. Use the following single-letter designations to specify the primitive type: `l` or `L` for `long`, `f` or `F` for `float`, and `d` or `D` for `double`. If not specified, the type defaults to `int`. Use `0` as a prefix to specify an integer literal as octal, and use `0x` or `0X` as a prefix to specify an integer literal as hex. -**Grammar** +### Grammar -```text +```java INTEGER: '-'? ( '0' | [1-9] [0-9]* ) [lLfFdD]?; OCTAL: '-'? '0' [0-7]+ [lL]?; HEX: '-'? '0' [xX] [0-9a-fA-F]+ [lL]?; ``` -**Examples** +### Examples -* Integer literals. +* Integer literals - ```painless - 0 <1> - 0D <2> - 1234L <3> - -90f <4> - -022 <5> - 0xF2A <6> + ```java + int i = 0; <1> + double d = 0D; <2> + long l = 1234L; <3> + float f = -90f; <4> + int e = -022; <5> + int x = 0xF2A; <6> ``` - 1. `int 0` 2. `double 0.0` 3. `long 1234` - 4. `float -90.0` - 5. `int -18` in octal - 6. `int 3882` in hex - - + 4. `float -90.0` + 5. `int -18 in octal` + 6. `int 3882 in hex` ## Floats [float-literals] -Use a floating point literal to specify a floating point type value of a [primitive type](/reference/scripting-languages/painless/painless-types.md#primitive-types) `float` or `double`. Use the following single letter designations to specify the primitive type: `f` or `F` for `float` and `d` or `D` for `double`. If not specified, the type defaults to `double`. +Use a floating-point literal to specify a floating-point-type value of a [primitive type](https://www.elastic.co/docs/reference/scripting-languages/painless/painless-types#primitive-types) `float` or `double`. Use the following single-letter designations to specify the primitive type: `f` or `F` for `float` and `d` or `D` for `double`. If not specified, the type defaults to `double`. -**Grammar** +### Grammar -```text +```java DECIMAL: '-'? ( '0' | [1-9] [0-9]* ) (DOT [0-9]+)? EXPONENT? [fFdD]?; EXPONENT: ( [eE] [+\-]? [0-9]+ ); ``` -**Examples** +### Examples -* Floating point literals. +* Floating point literals - ```painless - 0.0 <1> - 1E6 <2> - 0.977777 <3> - -126.34 <4> - 89.9F <5> + ```java + double b = 0.0; <1> + double d = 1E6; <2> + double c = 0.977777; <3> + double n = -126.34; <4> + float f = 89.9F; <5> ``` 1. `double 0.0` @@ -72,41 +72,38 @@ EXPONENT: ( [eE] [+\-]? [0-9]+ ); 4. `double -126.34` 5. `float 89.9` - - ## Strings [string-literals] -Use a string literal to specify a [`String` type](/reference/scripting-languages/painless/painless-types.md#string-type) value with either single-quotes or double-quotes. Use a `\"` token to include a double-quote as part of a double-quoted string literal. Use a `\'` token to include a single-quote as part of a single-quoted string literal. Use a `\\` token to include a backslash as part of any string literal. +Use a string literal to specify a [string type](/reference/scripting-languages/painless/painless-types.md#string-type) value with either single-quotes or double-quotes. Use a `"` token to include a double-quote as part of a double-quoted string literal. Use a `'` token to include a single-quote as part of a single-quoted string literal. Use a `\\` token to include a backslash as part of any string literal. -**Grammar** +### Grammar -```text +```java STRING: ( '"' ( '\\"' | '\\\\' | ~[\\"] )*? '"' ) | ( '\'' ( '\\\'' | '\\\\' | ~[\\'] )*? '\'' ); ``` -**Examples** +### Examples -* String literals using single-quotes. +* String literals using single-quotes - ```painless - 'single-quoted string literal' - '\'single-quoted with escaped single-quotes\' and backslash \\' - 'single-quoted with non-escaped "double-quotes"' + ```java + String a = 'single-quoted string literal'; + String b = '\'single-quoted with escaped single-quotes\' and backslash \\'; + String c = 'single-quoted with non-escaped "double-quotes"'; ``` -* String literals using double-quotes. +* String literals using double-quotes - ```painless - "double-quoted string literal" - "\"double-quoted with escaped double-quotes\" and backslash: \\" - "double-quoted with non-escaped 'single-quotes'" + ```java + String a = "double-quoted string literal"; + String b = "\"double-quoted with escaped double-quotes\" and backslash: \\"; + String c = "double-quoted with non-escaped 'single-quotes'"; ``` - - ## Characters [character-literals] -Character literals are not specified directly. Instead, use the [cast operator](/reference/scripting-languages/painless/painless-casting.md#string-character-casting) to convert a `String` type value into a `char` type value. +Character literals are not specified directly in Painless. Instead, use the [cast operator](/reference/scripting-languages/painless/painless-casting.md#string-character-casting) to convert `string` type value into a `char` type value. +For detailed information about character types, casting, and usage, refer to [Character type usage](/reference/scripting-languages/painless/painless-types.md#character-type-usage). diff --git a/docs/reference/scripting-languages/painless/painless-metric-agg-combine-context.md b/docs/reference/scripting-languages/painless/painless-metric-agg-combine-context.md index 964d9acd1b93a..52612504fad41 100644 --- a/docs/reference/scripting-languages/painless/painless-metric-agg-combine-context.md +++ b/docs/reference/scripting-languages/painless/painless-metric-agg-combine-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-metric-agg-combine-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,11 @@ products: Use a Painless script to [combine](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) values for use in a scripted metric aggregation. A combine script is run once per shard following a [map script](/reference/scripting-languages/painless/painless-metric-agg-map-context.md) and is optional as part of a full metric aggregation. -**Variables** +:::{warning} +`scripted_metric` is not available in {{serverless-full}}. +::: + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -17,12 +24,50 @@ Use a Painless script to [combine](/reference/aggregations/search-aggregations-m `state` (`Map`) : `Map` with values available from the prior map script. -**Return** +## Return `List`, `Map`, `String`, or primitive : A value collected for use in a [reduce script](/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md). If no reduce script is specified, the value is used as part of the result. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +:::{tip}You are viewing Phase 3 of 4 in the scripted metric aggregation pipeline. This combined script runs once per shard to prepare results for the final reduce phase. This is the same complete example shown across all metric aggregation contexts. +::: + +In the following example, we build a query that analyzes the data to calculate the total number of products sold across all orders, using the map-reduce pattern where each shard processes documents locally and results are combined into a final total. + +**Initialization phase (sets up data structures):** +The first code snippet is part of the `init_script` that initializes an empty array to collect quantity values from each document. It runs once per shard. + +```java +state.quantities = [] +``` + +**Map phase (processes each document):** +The code in the `map_script` section runs for each document. It extracts the total quantity of products in each order and adds it to the shard's collection array. + +```java +state.quantities.add(doc['total_quantity'].value) +``` + +**>Combine phase (this context - returns shard results):** +The `combine_script` processes all the quantities collected in this shard by iterating through the array and summing all values. This reduces the data sent to the reduce phase from an array of individual quantities to a single total per shard. + +```java +int shardTotal = 0; + +for (qty in state.quantities) { + shardTotal += qty; +} + +return shardTotal; +``` + +**Reduce phase (merges all shard results):** +Finally, the `reduce_script` merges results from all shards by iterating through each shard's total, and adds the results together to get the grand total of products sold across the entire dataset. diff --git a/docs/reference/scripting-languages/painless/painless-metric-agg-init-context.md b/docs/reference/scripting-languages/painless/painless-metric-agg-init-context.md index 222f822e5b366..4a0ab9d4fd19f 100644 --- a/docs/reference/scripting-languages/painless/painless-metric-agg-init-context.md +++ b/docs/reference/scripting-languages/painless/painless-metric-agg-init-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-metric-agg-init-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,7 @@ products: Use a Painless script to [initialize](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) 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** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -17,17 +20,99 @@ Use a Painless script to [initialize](/reference/aggregations/search-aggregation `state` (`Map`) : Empty `Map` used to add values for use in a [map script](/reference/scripting-languages/painless/painless-metric-agg-map-context.md). -**Side Effects** +## Side Effects `state` (`Map`) : Add values to this `Map` to for use in a map. Additional values must be of the type `Map`, `List`, `String` or primitive. -**Return** +## Return `void` : No expected return value. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +:::{tip} +This example demonstrates a complete scripted metric aggregation pipeline that works across all four metric aggregation contexts (initialization -> map -> combine -> reduce). You’ll find this same example in the other metric aggregation contexts, with each highlighting its specific phase. You are viewing Phase 1 of 4 in the scripted metric aggregation pipeline. +::: + +In the following example, we build a query that analyzes the data to calculate the total number of products sold across all orders, using the map-reduce pattern where each shard processes documents locally and results are combined into a final total. + +**>Initialization phase (this context \- sets up data structures):** +The first code snippet is part of the `init_script` that initializes an empty array to collect quantity values from each document. It runs once per shard. + +```java +state.quantities = [] +``` + +**Map phase (processes each document):** +The code in the `map_script` section runs for each document. It extracts the total quantity of products in each order and adds it to the shard's collection array. + +```java +state.quantities.add(doc['total_quantity'].value) +``` + +**Combine phase (returns shard results):** +The `combine_script` processes all the quantities collected in this shard by iterating through the array and summing all values. This reduces the data sent to the reduce phase from an array of individual quantities to a single total per shard. + +```java +int shardTotal = 0; +for (qty in state.quantities) { + shardTotal += qty; +} +return shardTotal; +``` + +**Reduce phase (merges all shard results):** +Finally, the `reduce_script` merges results from all shards by iterating through each shard's total, and adds the results together to get the grand total of products sold across the entire dataset. + +```java +int grandTotal = 0; + +for (shardTotal in states) { + grandTotal += shardTotal; +} + +return grandTotal; +``` + +The complete request looks like this: + +```json +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "total_quantity_sold": { + "scripted_metric": { + "init_script": "state.quantities = []", + "map_script": "state.quantities.add(doc['total_quantity'].value)", + "combine_script": """ + int shardTotal = 0; + + for (qty in state.quantities) { + shardTotal += qty; + } + + return shardTotal; + """, + "reduce_script": """ + int grandTotal = 0; + + for (shardTotal in states) { + grandTotal += shardTotal; + } + + return grandTotal; + """ + } + } + } +} +``` diff --git a/docs/reference/scripting-languages/painless/painless-metric-agg-map-context.md b/docs/reference/scripting-languages/painless/painless-metric-agg-map-context.md index 15e3a1afe6b26..d01ad51c46e87 100644 --- a/docs/reference/scripting-languages/painless/painless-metric-agg-map-context.md +++ b/docs/reference/scripting-languages/painless/painless-metric-agg-map-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-metric-agg-map-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,12 @@ products: Use a Painless script to [map](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) values for use in a scripted metric aggregation. A map script is run once per collected document following an optional [initialization script](/reference/scripting-languages/painless/painless-metric-agg-init-context.md) and is required as part of a full metric aggregation. -**Variables** +:::{warning} +`scripted_metric` is not available in {{serverless-full}}. +::: + + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -23,17 +31,101 @@ Use a Painless script to [map](/reference/aggregations/search-aggregations-metri `_score` (`double` read-only) : The similarity score of the current document. -**Side Effects** +## Side Effects `state` (`Map`) : Use this `Map` to add values for processing in a combine script. Additional values must be of the type `Map`, `List`, `String` or primitive. The same `state` `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 in `state` in a usable form. If no combine script and no [reduce script](/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md) are specified, the `state` values are used as the result. -**Return** +## Return `void` : No expected return value. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +:::{tip} +You are viewing Phase 2 of 4 in the scripted metric aggregation pipeline. This combined script runs once per shard to prepare results for the final reduce phase. This is the same complete example shown across all metric aggregation contexts. +::: + +In the following example, we build a query that analyzes the data to calculate the total number of products sold across all orders, using the map-reduce pattern where each shard processes documents locally and results are combined into a final total. + +**Initialization phase (sets up data structures):** +The first code snippet is part of the `init_script` that initializes an empty array to collect quantity values from each document. It runs once per shard. + +```java +state.quantities = [] +``` + +**>Map phase (this context - processes each document):** +The code in the `map_script` section runs for each document. It extracts the total quantity of products in each order and adds it to the shard's collection array. + +```java +state.quantities.add(doc['total_quantity'].value) +``` + +**Combine phase (returns shard results):** +The `combine_script` processes all the quantities collected in this shard by iterating through the array and summing all values. This reduces the data sent to the reduce phase from an array of individual quantities to a single total per shard. + +```java +int shardTotal = 0; + +for (qty in state.quantities) { + shardTotal += qty; +} + +return shardTotal; +``` + +**Reduce phase (merges all shard results):** +Finally, the `reduce_script` merges results from all shards by iterating through each shard's total, and adds the results together to get the grand total of products sold across the entire dataset. + +```java +int grandTotal = 0; + +for (shardTotal in states) { + grandTotal += shardTotal; +} + +return grandTotal; +``` + +The complete request looks like this: + +```json +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "total_quantity_sold": { + "scripted_metric": { + "init_script": "state.quantities = []", + "map_script": "state.quantities.add(doc['total_quantity'].value)", + "combine_script": """ + int shardTotal = 0; + + for (qty in state.quantities) { + shardTotal += qty; + } + + return shardTotal; + """, + "reduce_script": """ + int grandTotal = 0; + + for (shardTotal in states) { + grandTotal += shardTotal; + } + + return grandTotal; + """ + } + } + } +} +``` diff --git a/docs/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md b/docs/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md index 55d1a76a8a99d..d9bfac19a8ec4 100644 --- a/docs/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md +++ b/docs/reference/scripting-languages/painless/painless-metric-agg-reduce-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-metric-agg-reduce-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,11 @@ products: Use a Painless script to [reduce](/reference/aggregations/search-aggregations-metrics-scripted-metric-aggregation.md) values to produce the result of a scripted metric aggregation. A reduce script is run once on the coordinating node following a [combine script](/reference/scripting-languages/painless/painless-metric-agg-combine-context.md) (or a [map script](/reference/scripting-languages/painless/painless-metric-agg-map-context.md) if no combine script is specified) and is optional as part of a full metric aggregation. -**Variables** +:::{warning} +`scripted_metric` is not available in {{serverless-full}}. +::: + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -17,12 +24,97 @@ Use a Painless script to [reduce](/reference/aggregations/search-aggregations-me `states` (`Map`) : `Map` with values available from the prior combine script (or a map script if no combine script is specified). -**Return** +## Return `List`, `Map`, `String`, or primitive : A value used as the result. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +:::{tip} +You are viewing Phase 4 of 4 in the scripted metric aggregation pipeline. This reduces script runs once on the coordinating node to produce the final result from all shard data. This is the same complete example shown across all metric aggregation contexts. +::: + +In the following example, we build a query that analyzes the data to calculate the total number of products sold across all orders, using the map-reduce pattern where each shard processes documents locally and results are combined into a final total. + +**Initialization phase (sets up data structures):** +The first code snippet is part of the `init_script` that initializes an empty array to collect quantity values from each document. It runs once per shard. + +```java +state.quantities = [] +``` + +**Map phase (processes each document):** +The code in the `map_script` section runs for each document. It extracts the total quantity of products in each order and adds it to the shard's collection array. + +```java +state.quantities.add(doc['total_quantity'].value) +``` + +**Combine phase (returns shard results):** +The `combine_script` processes all the quantities collected in this shard by iterating through the array and summing all values. This reduces the data sent to the reduce phase from an array of individual quantities to a single total per shard. + +```java +int shardTotal = 0; + +for (qty in state.quantities) { + shardTotal += qty; +} + +return shardTotal; +``` + +**>Reduce phase (this context - merges all shard results):** +Finally, the `reduce_script` merges results from all shards by iterating through each shard's total, and adds the results together to get the grand total of products sold across the entire dataset. + +```java +int grandTotal = 0; + +for (shardTotal in states) { + grandTotal += shardTotal; +} + +return grandTotal; +``` + +The complete request looks like this: + +```json +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "total_quantity_sold": { + "scripted_metric": { + "init_script": "state.quantities = []", + "map_script": "state.quantities.add(doc['total_quantity'].value)", + "combine_script": """ + int shardTotal = 0; + + for (qty in state.quantities) { + shardTotal += qty; + } + + return shardTotal; + """, + "reduce_script": """ + int grandTotal = 0; + + for (shardTotal in states) { + grandTotal += shardTotal; + } + + return grandTotal; + """ + } + } + } +} +``` + diff --git a/docs/reference/scripting-languages/painless/painless-min-should-match-context.md b/docs/reference/scripting-languages/painless/painless-min-should-match-context.md index 05d29ba3b5b98..08ea4b514ba3b 100644 --- a/docs/reference/scripting-languages/painless/painless-min-should-match-context.md +++ b/docs/reference/scripting-languages/painless/painless-min-should-match-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-min-should-match-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,7 @@ products: Use a Painless script to specify the [minimum](/reference/query-languages/query-dsl/query-dsl-terms-set-query.md) number of terms that a specified field needs to match with for a document to be part of the query results. -**Variables** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -20,52 +23,57 @@ Use a Painless script to specify the [minimum](/reference/query-languages/query- `doc` (`Map`, read-only) : Contains the fields of the current document where each field is a `List` of values. -**Return** +## Return `int` : The minimum number of terms required to match the current document. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. -**Example** +## Example -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -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. +This example shows conditional matching requirements. The script checks if a document contains both “Men’s Clothing” and “Men’s Shoes” categories. If this combination exists, only two terms need to match. Otherwise, three terms must match. This creates different qualification thresholds based on document content. -To achieve this result, use a `terms_set` query with `minimum_should_match_script`. 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_terms` to get the number of actors in the list and `Math.min` to get the lesser of the two. - -```painless -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. - -```console -GET seats/_search +```json +GET kibana_sample_data_ecommerce/_search { "query": { "terms_set": { - "actors": { + "category.keyword": { "terms": [ - "smith", - "earns", - "black" + "Men's Clothing", + "Men's Shoes", + "Men's Accessories", + "Women's Clothing" ], "minimum_should_match_script": { - "source": "Math.min(params['num_terms'], params['min_actors_to_see'])", - "params": { - "min_actors_to_see": 2 - } + "source": """ + boolean hasMensClothing = false; + boolean hasMensShoes = false; + + for (def category : doc['category.keyword']) { + if (category.equals("Men's Clothing")) { + hasMensClothing = true; + } + + if (category.equals("Men's Shoes")) { + hasMensShoes = true; + } + } + + if (hasMensClothing && hasMensShoes) { + return 2; + } + + return 3; + """ } } } } } ``` -% TEST[setup:seats] - diff --git a/docs/reference/scripting-languages/painless/painless-operators-array.md b/docs/reference/scripting-languages/painless/painless-operators-array.md index 0be6b68046d6c..85fa79a31b26f 100644 --- a/docs/reference/scripting-languages/painless/painless-operators-array.md +++ b/docs/reference/scripting-languages/painless/painless-operators-array.md @@ -1,16 +1,21 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators-array.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Operators: Array [painless-operators-array] -## Array Initialization [array-initialization-operator] +## Array initialization [array-initialization-operator] Use the `array initialization operator '[] {}'` to allocate a single-dimensional [array type](/reference/scripting-languages/painless/painless-types.md#array-type) instance to the heap with a set of pre-defined elements. Each value used to initialize an element in the array type instance is cast to the specified element type value upon insertion. The order of specified values is maintained. +% For help with troubleshooting, refer to [Array/List manipulation errors](docs-content://troubleshoot/elasticsearch/painless-array-list-manipulation-errors.md). + **Errors** * If a value is not castable to the specified type value. @@ -25,7 +30,7 @@ expression_list: expression (',' expression); **Example:** -* Array initialization with static values. +* Array initialization with static values ```painless int[] x = new int[] {1, 2, 3}; <1> @@ -53,7 +58,7 @@ expression_list: expression (',' expression); -## Array Access [array-access-operator] +## Array access [array-access-operator] Use the `array access operator '[]'` to store a value to or load a value from an [array type](/reference/scripting-languages/painless/painless-types.md#array-type) value. Each element of an array type value is accessed with an `int` type value to specify the index to store/load. The range of elements within an array that are accessible is `[0, size)` where size is the number of elements specified at the time of allocation. Use a negative `int` type value as an index to access an element in reverse from the end of an array type value within a range of `[-size, -1]`. @@ -70,7 +75,7 @@ brace_access: '[' expression ']' **Examples** -* Array access with a single-dimensional array. +* Array access with a single-dimensional array ```painless int[] x = new int[2]; <1> @@ -88,7 +93,7 @@ brace_access: '[' expression ']' 5. declare `int z`; store `int 1` to `z`; 6. declare `int i`; load from `x` → `1-d int array reference`; load from `z` → `int 1`; load from `index [1]` of `1-d int array reference` → `int 5`; store `int 5` to `i`; -* Array access with the `def` type. +* Array access with the `def` type ```painless def d = new int[2]; <1> @@ -106,7 +111,7 @@ brace_access: '[' expression ']' 5. declare `def y`; implicit cast `int 1` to `def` → `def`; store `def` to `y`; 6. declare `int i`; load from `d` → `def` implicit cast `def` to `1-d int array reference` → `1-d int array reference`; load from `y` → `def`; implicit cast `def` to `int 1` → `int 1`; load from `index [1]` of `1-d int array reference` → `int 5`; implicit cast `int 5` to `def`; store `def` to `z`; -* Array access with a multi-dimensional array. +* Array access with a multi-dimensional array ```painless int[][][] ia3 = new int[2][3][4]; <1> @@ -120,13 +125,13 @@ brace_access: '[' expression ']' -## Array Length [array-length-operator] +## Array length [array-length-operator] An array type value contains a read-only member field named `length`. The `length` field stores the size of the array as an `int` type value where size is the number of elements specified at the time of allocation. Use the [field access operator](/reference/scripting-languages/painless/painless-operators-reference.md#field-access-operator) to load the field `length` from an array type value. **Examples** -* Access the `length` field. +* Access the `length` field ```painless int[] x = new int[10]; <1> @@ -138,7 +143,7 @@ An array type value contains a read-only member field named `length`. The `lengt -## New Array [new-array-operator] +## New array [new-array-operator] Use the `new array operator 'new []'` to allocate an array type instance to the heap. Specify the element type following the `new` token. Specify each dimension with the `[` and `]` tokens following the element type name. The size of each dimension is specified by an `int` type value in between each set of `[` and `]` tokens. @@ -154,7 +159,7 @@ new_array: 'new' TYPE ('[' expression ']')+; **Examples** -* Allocation of different array types. +* Allocation of different array types ```painless int[] x = new int[5]; <1> diff --git a/docs/reference/scripting-languages/painless/painless-operators-boolean.md b/docs/reference/scripting-languages/painless/painless-operators-boolean.md index cfa22297fd90f..7cd6a1fac4d18 100644 --- a/docs/reference/scripting-languages/painless/painless-operators-boolean.md +++ b/docs/reference/scripting-languages/painless/painless-operators-boolean.md @@ -1,13 +1,16 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators-boolean.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Operators: Boolean [painless-operators-boolean] -## Boolean Not [boolean-not-operator] +## Boolean not [boolean-not-operator] Use the `boolean not operator '!'` to NOT a `boolean` type value where `true` is flipped to `false` and `false` is flipped to `true`. @@ -30,7 +33,7 @@ boolean_not: '!' expression; **Examples** -* Boolean not with the `boolean` type. +* Boolean not with the `boolean` type ```painless boolean x = !false; <1> @@ -40,7 +43,7 @@ boolean_not: '!' expression; 1. declare `boolean x`; boolean not `boolean false` → `boolean true`; store `boolean true` to `x` 2. declare `boolean y`; load from `x` → `boolean true`; boolean not `boolean true` → `boolean false`; store `boolean false` to `y` -* Boolean not with the `def` type. +* Boolean not with the `def` type ```painless def y = true; <1> @@ -52,7 +55,7 @@ boolean_not: '!' expression; -## Greater Than [greater-than-operator] +## Greater than [greater-than-operator] Use the `greater than operator '>'` to COMPARE two numeric type values where a resultant `boolean` type value is `true` if the left-hand side value is greater than to the right-hand side value and `false` otherwise. @@ -82,7 +85,7 @@ greater_than: expression '>' expression; **Examples** -* Greater than with different numeric types. +* Greater than with different numeric types ```painless boolean x = 5 > 4; <1> @@ -94,7 +97,7 @@ greater_than: expression '>' expression; 2. declare `double y`; store `double 6.0` to `y`; 3. load from `y` → `double 6.0 @0`; promote `int 6` and `double 6.0`: result `double`; implicit cast `int 6` to `double 6.0 @1` → `double 6.0 @1`; greater than `double 6.0 @1` and `double 6.0 @0` → `boolean false`; store `boolean false` to `x` -* Greater than with `def` type. +* Greater than with `def` type ```painless int x = 5; <1> @@ -110,7 +113,7 @@ greater_than: expression '>' expression; -## Greater Than Or Equal [greater-than-or-equal-operator] +## Greater than or equal [greater-than-or-equal-operator] Use the `greater than or equal operator '>='` to COMPARE two numeric type values where a resultant `boolean` type value is `true` if the left-hand side value is greater than or equal to the right-hand side value and `false` otherwise. @@ -140,7 +143,7 @@ greater_than_or_equal: expression '>=' expression; **Examples** -* Greater than or equal with different numeric types. +* Greater than or equal with different numeric types ```painless boolean x = 5 >= 4; <1> @@ -168,7 +171,7 @@ greater_than_or_equal: expression '>=' expression; -## Less Than [less-than-operator] +## Less than [less-than-operator] Use the `less than operator '<'` to COMPARE two numeric type values where a resultant `boolean` type value is `true` if the left-hand side value is less than to the right-hand side value and `false` otherwise. @@ -198,7 +201,7 @@ less_than: expression '<' expression; **Examples** -* Less than with different numeric types. +* Less than with different numeric types ```painless boolean x = 5 < 4; <1> @@ -226,7 +229,7 @@ less_than: expression '<' expression; -## Less Than Or Equal [less-than-or-equal-operator] +## Less than or equal [less-than-or-equal-operator] Use the `less than or equal operator '<='` to COMPARE two numeric type values where a resultant `boolean` type value is `true` if the left-hand side value is less than or equal to the right-hand side value and `false` otherwise. @@ -256,7 +259,7 @@ greater_than_or_equal: expression '<=' expression; **Examples** -* Less than or equal with different numeric types. +* Less than or equal with different numeric types ```painless boolean x = 5 <= 4; <1> @@ -300,7 +303,7 @@ instance_of: ID 'instanceof' TYPE; **Examples** -* Instance of with different reference types. +* Instance of with different reference types ```painless Map m = new HashMap(); <1> @@ -326,7 +329,7 @@ instance_of: ID 'instanceof' TYPE; -## Equality Equals [equality-equals-operator] +## Equality equals [equality-equals-operator] Use the `equality equals operator '=='` to COMPARE two values where a resultant `boolean` type value is `true` if the two values are equal and `false` otherwise. The member method, `equals`, is implicitly called when the values are reference type values where the first value is the target of the call and the second value is the argument. This operation is null-safe where if both values are `null` the resultant `boolean` type value is `true`, and if only one value is `null` the resultant `boolean` type value is `false`. A valid comparison is between `boolean` type values, numeric type values, or reference type values. @@ -359,7 +362,7 @@ equality_equals: expression '==' expression; **Examples** -* Equality equals with the `boolean` type. +* Equality equals with the `boolean` type ```painless boolean a = true; <1> @@ -373,7 +376,7 @@ equality_equals: expression '==' expression; 3. load from `a` → `boolean true`; equality equals `boolean true` and `boolean false` → `boolean false`; store `boolean false` to `a` 4. load from `a` → `boolean false @0`; load from `b` → `boolean false @1`; equality equals `boolean false @0` and `boolean false @1` → `boolean false`; store `boolean false` to `b` -* Equality equals with primitive types. +* Equality equals with primitive types ```painless int a = 1; <1> @@ -387,7 +390,7 @@ equality_equals: expression '==' expression; 3. declare `boolean c`; load from `a` → `int 1`; load from `b` → `double 2.0`; promote `int 1` and `double 2.0`: result `double`; implicit cast `int 1` to `double 1.0` → `double `1.0`; equality equals `double 1.0` and `double 2.0` → `boolean false`; store `boolean false` to `c` 4. load from `a` → `int 1 @1`; equality equals `int 1 @0` and `int 1 @1` → `boolean true`; store `boolean true` to `c` -* Equal equals with reference types. +* Equal equals with reference types ```painless List a = new ArrayList(); <1> @@ -405,7 +408,7 @@ equality_equals: expression '==' expression; 5. load from `b` → `List reference`; call `add` on `List reference` with arguments (`int 1`) 6. load from `a` → `List reference @0`; load from `b` → `List reference @1`; call `equals` on `List reference @0` with arguments (`List reference @1`) → `boolean true`; store `boolean true` to `c` -* Equality equals with `null`. +* Equality equals with `null` ```painless Object a = null; <1> @@ -423,7 +426,7 @@ equality_equals: expression '==' expression; 5. allocate `Object` instance → `Object reference`; store `Object reference` to `b` 6. load from `a` → `Object reference`; load from `b` → `null`; call `equals` on `Object reference` with arguments (`null`) → `boolean false`; store `boolean false` to `c` -* Equality equals with the `def` type. +* Equality equals with the `def` type ```painless def a = 0; <1> @@ -443,7 +446,7 @@ equality_equals: expression '==' expression; -## Equality Not Equals [equality-not-equals-operator] +## Equality not equals [equality-not-equals-operator] Use the `equality not equals operator '!='` to COMPARE two values where a resultant `boolean` type value is `true` if the two values are NOT equal and `false` otherwise. The member method, `equals`, is implicitly called when the values are reference type values where the first value is the target of the call and the second value is the argument with the resultant `boolean` type value flipped. This operation is `null-safe` where if both values are `null` the resultant `boolean` type value is `false`, and if only one value is `null` the resultant `boolean` type value is `true`. A valid comparison is between boolean type values, numeric type values, or reference type values. @@ -476,7 +479,7 @@ equality_not_equals: expression '!=' expression; **Examples** -* Equality not equals with the `boolean` type. +* Equality not equals with the `boolean` type ```painless boolean a = true; <1> @@ -490,7 +493,7 @@ equality_not_equals: expression '!=' expression; 3. load from `a` → `boolean true`; equality not equals `boolean true` and `boolean false` → `boolean true`; store `boolean true` to `a` 4. load from `a` → `boolean true`; load from `b` → `boolean false`; equality not equals `boolean true` and `boolean false` → `boolean true`; store `boolean true` to `b` -* Equality not equals with primitive types. +* Equality not equals with primitive types ```painless int a = 1; <1> @@ -504,7 +507,7 @@ equality_not_equals: expression '!=' expression; 3. declare `boolean c`; load from `a` → `int 1`; load from `b` → `double 2.0`; promote `int 1` and `double 2.0`: result `double`; implicit cast `int 1` to `double 1.0` → `double `1.0`; equality not equals `double 1.0` and `double 2.0` → `boolean true`; store `boolean true` to `c` 4. load from `a` → `int 1 @1`; equality not equals `int 1 @0` and `int 1 @1` → `boolean false`; store `boolean false` to `c` -* Equality not equals with reference types. +* Equality not equals with reference types ```painless List a = new ArrayList(); <1> @@ -522,7 +525,7 @@ equality_not_equals: expression '!=' expression; 5. load from `b` → `List reference`; call `add` on `List reference` with arguments (`int 1`) 6. load from `a` → `List reference @0`; load from `b` → `List reference @1`; call `equals` on `List reference @0` with arguments (`List reference @1`) → `boolean true`; boolean not `boolean true` → `boolean false`; store `boolean false` to `c` -* Equality not equals with `null`. +* Equality not equals with `null` ```painless Object a = null; <1> @@ -540,7 +543,7 @@ equality_not_equals: expression '!=' expression; 5. allocate `Object` instance → `Object reference`; store `Object reference` to `b` 6. load from `a` → `Object reference`; load from `b` → `null`; call `equals` on `Object reference` with arguments (`null`) → `boolean false`; boolean not `boolean false` → `boolean true`; store `boolean true` to `c` -* Equality not equals with the `def` type. +* Equality not equals with the `def` type ```painless def a = 0; <1> @@ -560,7 +563,7 @@ equality_not_equals: expression '!=' expression; -## Identity Equals [identity-equals-operator] +## Identity equals [identity-equals-operator] Use the `identity equals operator '==='` to COMPARE two values where a resultant `boolean` type value is `true` if the two values are equal and `false` otherwise. A reference type value is equal to another reference type value if both values refer to same instance on the heap or if both values are `null`. A valid comparison is between `boolean` type values, numeric type values, or reference type values. @@ -593,7 +596,7 @@ identity_equals: expression '===' expression; **Examples** -* Identity equals with reference types. +* Identity equals with reference types ```painless List a = new ArrayList(); <1> @@ -609,7 +612,7 @@ identity_equals: expression '===' expression; 4. declare `boolean c`; load from `a` → `List reference @0`; load from `b` → `List reference @1`; identity equals `List reference @0` and `List reference @1` → `boolean false` store `boolean false` to `c` 5. load from `a` → `List reference @0`; load from `c` → `List reference @1`; identity equals `List reference @0` and `List reference @1` → `boolean true` store `boolean true` to `c` (note `List reference @0` and `List reference @1` refer to the same instance) -* Identity equals with `null`. +* Identity equals with `null` ```painless Object a = null; <1> @@ -627,7 +630,7 @@ identity_equals: expression '===' expression; 5. allocate `Object` instance → `Object reference`; store `Object reference` to `b` 6. load from `a` → `Object reference`; load from `b` → `null`; identity equals `Object reference` and `null` → `boolean false`; store `boolean false` to `c` -* Identity equals with the `def` type. +* Identity equals with the `def` type ```painless def a = new HashMap(); <1> @@ -645,7 +648,7 @@ identity_equals: expression '===' expression; -## Identity Not Equals [identity-not-equals-operator] +## Identity not equals [identity-not-equals-operator] Use the `identity not equals operator '!=='` to COMPARE two values where a resultant `boolean` type value is `true` if the two values are NOT equal and `false` otherwise. A reference type value is not equal to another reference type value if both values refer to different instances on the heap or if one value is `null` and the other is not. A valid comparison is between `boolean` type values, numeric type values, or reference type values. @@ -678,7 +681,7 @@ identity_not_equals: expression '!==' expression; **Examples** -* Identity not equals with reference type values. +* Identity not equals with reference type values ```painless List a = new ArrayList(); <1> @@ -694,7 +697,7 @@ identity_not_equals: expression '!==' expression; 4. declare `boolean c`; load from `a` → `List reference @0`; load from `b` → `List reference @1`; identity not equals `List reference @0` and `List reference @1` → `boolean true` store `boolean true` to `c` 5. load from `a` → `List reference @0`; load from `c` → `List reference @1`; identity not equals `List reference @0` and `List reference @1` → `boolean false` store `boolean false` to `c` (note `List reference @0` and `List reference @1` refer to the same instance) -* Identity not equals with `null`. +* Identity not equals with `null` ```painless Object a = null; <1> @@ -712,7 +715,7 @@ identity_not_equals: expression '!==' expression; 5. allocate `Object` instance → `Object reference`; store `Object reference` to `b` 6. load from `a` → `Object reference`; load from `b` → `null`; identity not equals `Object reference` and `null` → `boolean true`; store `boolean true` to `c` -* Identity not equals with the `def` type. +* Identity not equals with the `def` type ```painless def a = new HashMap(); <1> @@ -730,7 +733,7 @@ identity_not_equals: expression '!==' expression; -## Boolean Xor [boolean-xor-operator] +## Boolean xor [boolean-xor-operator] Use the `boolean xor operator '^'` to XOR together two `boolean` type values where if one `boolean` type value is `true` and the other is `false` the resultant `boolean` type value is `true` and `false` otherwise. @@ -754,7 +757,7 @@ boolean_xor: expression '^' expression; **Examples** -* Boolean xor with the `boolean` type. +* Boolean xor with the `boolean` type ```painless boolean x = false; <1> @@ -766,7 +769,7 @@ boolean_xor: expression '^' expression; 2. declare `boolean y`; load from `x` → `boolean false` boolean xor `boolean false` and `boolean true` → `boolean true`; store `boolean true` to `y` 3. load from `y` → `boolean true @0`; load from `x` → `boolean true @1`; boolean xor `boolean true @0` and `boolean true @1` → `boolean false`; store `boolean false` to `y` -* Boolean xor with the `def` type. +* Boolean xor with the `def` type ```painless def x = false; <1> @@ -780,7 +783,7 @@ boolean_xor: expression '^' expression; -## Boolean And [boolean-and-operator] +## Boolean and [boolean-and-operator] Use the `boolean and operator '&&'` to AND together two `boolean` type values where if both `boolean` type values are `true` the resultant `boolean` type value is `true` and `false` otherwise. @@ -804,7 +807,7 @@ boolean_and: expression '&&' expression; **Examples** -* Boolean and with the `boolean` type. +* Boolean and with the `boolean` type ```painless boolean x = true; <1> @@ -818,7 +821,7 @@ boolean_and: expression '&&' expression; 3. store `boolean false` to `x` 4. load from `y` → `boolean true`; load from `x` → `boolean false`; boolean and `boolean true` and `boolean false` → `boolean false`; store `boolean false` to `y` -* Boolean and with the `def` type. +* Boolean and with the `def` type ```painless def x = true; <1> @@ -834,7 +837,7 @@ boolean_and: expression '&&' expression; -## Boolean Or [boolean-or-operator] +## Boolean or [boolean-or-operator] Use the `boolean or operator '||'` to OR together two `boolean` type values where if either one of the `boolean` type values is `true` the resultant `boolean` type value is `true` and `false` otherwise. @@ -858,7 +861,7 @@ boolean_and: expression '||' expression; **Examples** -* Boolean or with the `boolean` type. +* Boolean or with the `boolean` type ```painless boolean x = false; <1> @@ -872,7 +875,7 @@ boolean_and: expression '||' expression; 3. store `boolean false` to `y` 4. load from `y` → `boolean false @0`; load from `x` → `boolean false @1`; boolean or `boolean false @0` and `boolean false @1` → `boolean false`; store `boolean false` to `y` -* Boolean or with the `def` type. +* Boolean or with the `def` type ```painless def x = false; <1> diff --git a/docs/reference/scripting-languages/painless/painless-operators-general.md b/docs/reference/scripting-languages/painless/painless-operators-general.md index ecbb17d2fd27d..e990b7b3861f6 100644 --- a/docs/reference/scripting-languages/painless/painless-operators-general.md +++ b/docs/reference/scripting-languages/painless/painless-operators-general.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators-general.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -19,7 +22,7 @@ precedence: '(' expression ')'; **Examples** -* Precedence with numeric operators. +* Precedence with numeric operators ```painless int x = (5+4)*6; <1> @@ -31,7 +34,7 @@ precedence: '(' expression ')'; -## Function Call [function-call-operator] +## Function call [function-call-operator] Use the `function call operator ()` to call an existing function. A [function call](/reference/scripting-languages/painless/painless-functions.md) is defined within a script. @@ -43,7 +46,7 @@ function_call: ID '(' ( expression (',' expression)* )? ')''; **Examples** -* A function call. +* A function call ```painless int add(int x, int y) { <1> @@ -97,21 +100,28 @@ conditional: expression '?' expression ':' expression; **Examples** -* Evaluation of conditionals. +* Evaluation of conditionals - ```painless - boolean b = true; <1> - int x = b ? 1 : 2; <2> - List y = x > 1 ? new ArrayList() : null; <3> - def z = x < 2 ? x : 2.0; <4> + ```java + boolean b = true; + int x = b ? 1 : 2; <1> ``` - 1. declare `boolean b`; store `boolean true` to `b` - 2. declare `int x`; load from `b` → `boolean true` evaluate 1st expression: `int 1` → `int 1`; store `int 1` to `x` - 3. declare `List y`; load from `x` → `int 1`; `int 1` greater than `int 1` → `boolean false`; evaluate 2nd expression: `null` → `null`; store `null` to `y`; - 4. declare `def z`; load from `x` → `int 1`; `int 1` less than `int 2` → `boolean true`; evaluate 1st expression: load from `x` → `int 1`; promote `int 1` and `double 2.0`: result `double`; implicit cast `int 1` to `double 1.0` → `double 1.0`; implicit cast `double 1.0` to `def` → `def`; store `def` to `z`; + 1. declare `int x`; load from `b` → `boolean true` evaluate 1st expression: `int 1` → `int 1`; store `int 1` to `x` + + ```java + int x = 1; + List y = x > 1 ? new ArrayList() : null; <1> + ``` + 1. declare `List y`; load from `x` → `int 1`; `int 1` greater than `int 1` → `boolean false`; evaluate 2nd expression: `null` → `null`; store `null` to `y` + + ```java + int x = 1; + def z = x < 2 ? x : 2.0; <1> + ``` + 1. declare `def z`; load from `x` → `int 1`; `int 1` less than `int 2` → `boolean true`; evaluate 1st expression: load from `x` → `int 1`; promote `int 1` and `double 2.0`: result `double`; implicit cast `int 1` to `double 1.0` → `double 1.0`; implicit cast `double 1.0` to `def` → `def`; store `def` to `z`; ## Assignment [assignment-operator] @@ -143,7 +153,7 @@ non-static member fields: * List z ``` -* Field assignments of different type values. +* Field assignments of different type values ```painless Example example = new Example(); <1> @@ -157,7 +167,7 @@ non-static member fields: 3. load from `example` → `Example reference`; implicit cast `double 2.0` to `def` → `def`; store `def` to `y` of `Example reference` 4. load from `example` → `Example reference`; allocate `ArrayList` instance → `ArrayList reference`; implicit cast `ArrayList reference` to `List reference` → `List reference`; store `List reference` to `z` of `Example reference` -* A field assignment from a field access. +* A field assignment from a field access ```painless Example example = new Example(); <1> @@ -171,7 +181,7 @@ non-static member fields: -## Compound Assignment [compound-assignment-operator] +## Compound assignment [compound-assignment-operator] Use the `compound assignment operator '$='` as a shortcut for an assignment where a binary operation would occur between the variable/field as the left-hand side expression and a separate right-hand side expression. @@ -214,11 +224,11 @@ The table below shows the available operators for use in a compound assignment. compound_assignment: ( ID | field ) '$=' expression; ``` -Note the use of the `$=` represents the use of any of the possible binary operators. +The use of the `$=` represents the use of any of the possible binary operators. **Examples** -* Compound assignment for each numeric operator. +* Compound assignment for each numeric operator ```painless int i = 10; <1> @@ -248,7 +258,7 @@ Note the use of the `$=` represents the use of any of the possible binary operat 11. load from `i` → `int 1`; bitwise xor `int 1` and `int 12` → `int 13`; store `int 13` to `i`; (note this is equivalent to `i = i^2`) 12. load from `i` → `int 13`; bitwise or `int 13` and `int 2` → `int 15`; store `int 15` to `i`; (note this is equivalent to `i = i|2`) -* Compound assignment for each boolean operator. +* Compound assignment for each boolean operator ```painless boolean b = true; <1> @@ -262,7 +272,7 @@ Note the use of the `$=` represents the use of any of the possible binary operat 3. load from `b` → `boolean false`; boolean xor `boolean false` and `boolean false` → `boolean false`; store `boolean false` to `b`; (note this is equivalent to `b = b ^ false`) 4. load from `b` → `boolean true`; boolean or `boolean false` and `boolean true` → `boolean true`; store `boolean true` to `b`; (note this is equivalent to `b = b || true`) -* A compound assignment with the string concatenation operator. +* A compound assignment with the string concatenation operator ```painless String s = 'compound'; <1> @@ -272,7 +282,7 @@ Note the use of the `$=` represents the use of any of the possible binary operat 1. declare `String s`; store `String 'compound'` to `s`; 2. load from `s` → `String 'compound'`; string concat `String 'compound'` and `String ' assignment''` → `String 'compound assignment'`; store `String 'compound assignment'` to `s`; (note this is equivalent to `s = s + ' assignment'`) -* A compound assignment with the `def` type. +* A compound assignment with the `def` type ```painless def x = 1; <1> @@ -282,7 +292,7 @@ Note the use of the `$=` represents the use of any of the possible binary operat 1. declare `def x`; implicit cast `int 1` to `def`; store `def` to `x`; 2. load from `x` → `def`; implicit cast `def` to `int 1` → `int 1`; add `int 1` and `int 2` → `int 3`; implicit cast `int 3` to `def` → `def`; store `def` to `x`; (note this is equivalent to `x = x+2`) -* A compound assignment with an extra implicit cast. +* A compound assignment with an extra implicit cast ```painless byte b = 1; <1> diff --git a/docs/reference/scripting-languages/painless/painless-operators-numeric.md b/docs/reference/scripting-languages/painless/painless-operators-numeric.md index 87b2637d03e72..c558d656f1192 100644 --- a/docs/reference/scripting-languages/painless/painless-operators-numeric.md +++ b/docs/reference/scripting-languages/painless/painless-operators-numeric.md @@ -1,13 +1,16 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators-numeric.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Operators: Numeric [painless-operators-numeric] -## Post Increment [post-increment-operator] +## Post increment [post-increment-operator] Use the `post increment operator '++'` to INCREASE the value of a numeric type variable/field by `1`. An extra implicit cast is necessary to return the promoted numeric type value to the original numeric type value of the variable/field for the following types: `byte`, `short`, and `char`. If a variable/field is read as part of an expression the value is loaded prior to the increment. @@ -36,7 +39,7 @@ post_increment: ( variable | field ) '++'; **Examples** -* Post increment with different numeric types. +* Post increment with different numeric types ```painless short i = 0; <1> @@ -52,7 +55,7 @@ post_increment: ( variable | field ) '++'; 4. declare `long k`; store default `long 0` to `k` 5. load from `j` → `long 1`; store `long 1` to `k`; add `long 1` and `long 1` → `long 2`; store `long 2` to `j` -* Post increment with the `def` type. +* Post increment with the `def` type ```painless def x = 1; <1> @@ -64,7 +67,7 @@ post_increment: ( variable | field ) '++'; -## Post Decrement [post-decrement-operator] +## Post decrement [post-decrement-operator] Use the `post decrement operator '--'` to DECREASE the value of a numeric type variable/field by `1`. An extra implicit cast is necessary to return the promoted numeric type value to the original numeric type value of the variable/field for the following types: `byte`, `short`, and `char`. If a variable/field is read as part of an expression the value is loaded prior to the decrement. @@ -93,7 +96,7 @@ post_decrement: ( variable | field ) '--'; **Examples** -* Post decrement with different numeric types. +* Post decrement with different numeric types ```painless short i = 0; <1> @@ -109,7 +112,7 @@ post_decrement: ( variable | field ) '--'; 4. declare `long k`; store default `long 0` to `k` 5. load from `j` → `long 1`; store `long 1` to `k`; subtract `long 1` from `long 1` → `long 0`; store `long 0` to `j` -* Post decrement with the `def` type. +* Post decrement with the `def` type ```painless def x = 1; <1> @@ -121,7 +124,7 @@ post_decrement: ( variable | field ) '--'; -## Pre Increment [pre-increment-operator] +## Pre increment [pre-increment-operator] Use the `pre increment operator '++'` to INCREASE the value of a numeric type variable/field by `1`. An extra implicit cast is necessary to return the promoted numeric type value to the original numeric type value of the variable/field for the following types: `byte`, `short`, and `char`. If a variable/field is read as part of an expression the value is loaded after the increment. @@ -150,7 +153,7 @@ pre_increment: '++' ( variable | field ); **Examples** -* Pre increment with different numeric types. +* Pre increment with different numeric types ```painless short i = 0; <1> @@ -166,7 +169,7 @@ pre_increment: '++' ( variable | field ); 4. declare `long k`; store default `long 0` to `k` 5. load from `j` → `long 1`; add `long 1` and `long 1` → `long 2`; store `long 2` to `j`; store `long 2` to `k` -* Pre increment with the `def` type. +* Pre increment with the `def` type ```painless def x = 1; <1> @@ -178,7 +181,7 @@ pre_increment: '++' ( variable | field ); -## Pre Decrement [pre-decrement-operator] +## Pre decrement [pre-decrement-operator] Use the `pre decrement operator '--'` to DECREASE the value of a numeric type variable/field by `1`. An extra implicit cast is necessary to return the promoted numeric type value to the original numeric type value of the variable/field for the following types: `byte`, `short`, and `char`. If a variable/field is read as part of an expression the value is loaded after the decrement. @@ -207,7 +210,7 @@ pre_decrement: '--' ( variable | field ); **Examples** -* Pre decrement with different numeric types. +* Pre decrement with different numeric types ```painless short i = 0; <1> @@ -223,7 +226,7 @@ pre_decrement: '--' ( variable | field ); 4. declare `long k`; store default `long 0` to `k` 5. load from `j` → `long 1`; subtract `long 1` from `long 1` → `long 0`; store `long 0` to `j` store `long 0` to `k`; -* Pre decrement operator with the `def` type. +* Pre decrement operator with the `def` type ```painless def x = 1; <1> @@ -235,7 +238,7 @@ pre_decrement: '--' ( variable | field ); -## Unary Positive [unary-positive-operator] +## Unary positive [unary-positive-operator] Use the `unary positive operator '+'` to the preserve the IDENTITY of a numeric type value. @@ -251,7 +254,7 @@ unary_positive: '+' expression; **Examples** -* Unary positive with different numeric types. +* Unary positive with different numeric types ```painless int x = +1; <1> @@ -261,7 +264,7 @@ unary_positive: '+' expression; 1. declare `int x`; identity `int 1` → `int 1`; store `int 1` to `x` 2. declare `long y`; load from `x` → `int 1`; identity `int 1` → `int 1`; implicit cast `int 1` to `long 1` → `long 1`; store `long 1` to `y` -* Unary positive with the `def` type. +* Unary positive with the `def` type ```painless def z = +1; <1> @@ -273,7 +276,7 @@ unary_positive: '+' expression; -## Unary Negative [unary-negative-operator] +## Unary negative [unary-negative-operator] Use the `unary negative operator '-'` to NEGATE a numeric type value. @@ -289,7 +292,7 @@ unary_negative: '-' expression; **Examples** -* Unary negative with different numeric types. +* Unary negative with different numeric types ```painless int x = -1; <1> @@ -299,7 +302,7 @@ unary_negative: '-' expression; 1. declare `int x`; negate `int 1` → `int -1`; store `int -1` to `x` 2. declare `long y`; load from `x` → `int 1`; negate `int -1` → `int 1`; implicit cast `int 1` to `long 1` → `long 1`; store `long 1` to `y` -* Unary negative with the `def` type. +* Unary negative with the `def` type ```painless def z = -1; <1> @@ -311,7 +314,7 @@ unary_negative: '-' expression; -## Bitwise Not [bitwise-not-operator] +## Bitwise not [bitwise-not-operator] Use the `bitwise not operator '~'` to NOT each bit in an integer type value where a `1-bit` is flipped to a resultant `0-bit` and a `0-bit` is flipped to a resultant `1-bit`. @@ -345,7 +348,7 @@ bitwise_not: '~' expression; **Examples** -* Bitwise not with different numeric types. +* Bitwise not with different numeric types ```painless byte b = 1; <1> @@ -357,7 +360,7 @@ bitwise_not: '~' expression; 2. declare `int i`; load from `b` → `byte 1`; implicit cast `byte 1` to `int 1` → `int 1`; bitwise not `int 1` → `int -2`; store `int -2` to `i` 3. declare `long l`; load from `i` → `int -2`; implicit cast `int -2` to `long -2` → `long -2`; bitwise not `long -2` → `long 1`; store `long 1` to `l` -* Bitwise not with the `def` type. +* Bitwise not with the `def` type ```painless def d = 1; <1> @@ -399,7 +402,7 @@ multiplication: expression '*' expression; **Examples** -* Multiplication with different numeric types. +* Multiplication with different numeric types ```painless int i = 5*4; <1> @@ -409,7 +412,7 @@ multiplication: expression '*' expression; 1. declare `int i`; multiply `int 4` by `int 5` → `int 20`; store `int 20` in `i` 2. declare `double d`; load from `int i` → `int 20`; promote `int 20` and `double 7.0`: result `double`; implicit cast `int 20` to `double 20.0` → `double 20.0`; multiply `double 20.0` by `double 7.0` → `double 140.0`; store `double 140.0` to `d` -* Multiplication with the `def` type. +* Multiplication with the `def` type ```painless def x = 5*4; <1> @@ -452,7 +455,7 @@ division: expression '/' expression; **Examples** -* Division with different numeric types. +* Division with different numeric types ```painless int i = 29/4; <1> @@ -462,7 +465,7 @@ division: expression '/' expression; 1. declare `int i`; divide `int 29` by `int 4` → `int 7`; store `int 7` in `i` 2. declare `double d`; load from `int i` → `int 7`; promote `int 7` and `double 7.0`: result `double`; implicit cast `int 7` to `double 7.0` → `double 7.0`; divide `double 7.0` by `double 7.0` → `double 1.0`; store `double 1.0` to `d` -* Division with the `def` type. +* Division with the `def` type ```painless def x = 5/4; <1> @@ -504,7 +507,7 @@ remainder: expression '%' expression; **Examples** -* Remainder with different numeric types. +* Remainder with different numeric types ```painless int i = 29%4; <1> @@ -514,7 +517,7 @@ remainder: expression '%' expression; 1. declare `int i`; remainder `int 29` by `int 4` → `int 1`; store `int 7` in `i` 2. declare `double d`; load from `int i` → `int 1`; promote `int 1` and `double 7.0`: result `double`; implicit cast `int 1` to `double 1.0` → `double 1.0`; remainder `double 1.0` by `double 7.0` → `double 1.0`; store `double 1.0` to `d` -* Remainder with the `def` type. +* Remainder with the `def` type ```painless def x = 5%4; <1> @@ -556,7 +559,7 @@ addition: expression '+' expression; **Examples** -* Addition operator with different numeric types. +* Addition operator with different numeric types ```painless int i = 29+4; <1> @@ -566,7 +569,7 @@ addition: expression '+' expression; 1. declare `int i`; add `int 29` and `int 4` → `int 33`; store `int 33` in `i` 2. declare `double d`; load from `int i` → `int 33`; promote `int 33` and `double 7.0`: result `double`; implicit cast `int 33` to `double 33.0` → `double 33.0`; add `double 33.0` and `double 7.0` → `double 40.0`; store `double 40.0` to `d` -* Addition with the `def` type. +* Addition with the `def` type ```painless def x = 5+4; <1> @@ -608,7 +611,7 @@ subtraction: expression '-' expression; **Examples** -* Subtraction with different numeric types. +* Subtraction with different numeric types ```painless int i = 29-4; <1> @@ -618,7 +621,7 @@ subtraction: expression '-' expression; 1. declare `int i`; subtract `int 4` from `int 29` → `int 25`; store `int 25` in `i` 2. declare `double d` load from `int i` → `int 25`; promote `int 25` and `double 7.5`: result `double`; implicit cast `int 25` to `double 25.0` → `double 25.0`; subtract `double 33.0` by `double 7.5` → `double 25.5`; store `double 25.5` to `d` -* Subtraction with the `def` type. +* Subtraction with the `def` type ```painless def x = 5-4; <1> @@ -630,7 +633,7 @@ subtraction: expression '-' expression; -## Left Shift [left-shift-operator] +## Left shift [left-shift-operator] Use the `left shift operator '<<'` to SHIFT lower order bits to higher order bits in a left-hand side integer type value by the distance specified in a right-hand side integer type value. @@ -660,7 +663,7 @@ The left-hand side integer type value is promoted as specified in the table belo **Examples** -* Left shift with different integer types. +* Left shift with different integer types ```painless int i = 4 << 1; <1> @@ -670,7 +673,7 @@ The left-hand side integer type value is promoted as specified in the table belo 1. declare `int i`; left shift `int 4` by `int 1` → `int 8`; store `int 8` in `i` 2. declare `long l` load from `int i` → `int 8`; implicit cast `long 2` to `int 2` → `int 2`; left shift `int 8` by `int 2` → `int 32`; implicit cast `int 32` to `long 32` → `long 32`; store `long 32` to `l` -* Left shift with the `def` type. +* Left shift with the `def` type ```painless def x = 4 << 2; <1> @@ -682,7 +685,7 @@ The left-hand side integer type value is promoted as specified in the table belo -## Right Shift [right-shift-operator] +## Right shift [right-shift-operator] Use the `right shift operator '>>'` to SHIFT higher order bits to lower order bits in a left-hand side integer type value by the distance specified in a right-hand side integer type value. The highest order bit of the left-hand side integer type value is preserved. @@ -712,7 +715,7 @@ The left-hand side integer type value is promoted as specified in the table belo **Examples** -* Right shift with different integer types. +* Right shift with different integer types ```painless int i = 32 >> 1; <1> @@ -722,7 +725,7 @@ The left-hand side integer type value is promoted as specified in the table belo 1. declare `int i`; right shift `int 32` by `int 1` → `int 16`; store `int 16` in `i` 2. declare `long l` load from `int i` → `int 16`; implicit cast `long 2` to `int 2` → `int 2`; right shift `int 16` by `int 2` → `int 4`; implicit cast `int 4` to `long 4` → `long 4`; store `long 4` to `l` -* Right shift with the `def` type. +* Right shift with the `def` type ```painless def x = 16 >> 2; <1> @@ -734,7 +737,7 @@ The left-hand side integer type value is promoted as specified in the table belo -## Unsigned Right Shift [unsigned-right-shift-operator] +## Unsigned right shift [unsigned-right-shift-operator] Use the `unsigned right shift operator '>>>'` to SHIFT higher order bits to lower order bits in a left-hand side integer type value by the distance specified in a right-hand side type integer value. The highest order bit of the left-hand side integer type value is **not** preserved. @@ -764,7 +767,7 @@ The left-hand side integer type value is promoted as specified in the table belo **Examples** -* Unsigned right shift with different integer types. +* Unsigned right shift with different integer types ```painless int i = -1 >>> 29; <1> @@ -774,7 +777,7 @@ The left-hand side integer type value is promoted as specified in the table belo 1. declare `int i`; unsigned right shift `int -1` by `int 29` → `int 7`; store `int 7` in `i` 2. declare `long l` load from `int i` → `int 7`; implicit cast `long 2` to `int 2` → `int 2`; unsigned right shift `int 7` by `int 2` → `int 3`; implicit cast `int 3` to `long 3` → `long 3`; store `long 3` to `l` -* Unsigned right shift with the `def` type. +* Unsigned right shift with the `def` type ```painless def x = 16 >>> 2; <1> @@ -786,7 +789,7 @@ The left-hand side integer type value is promoted as specified in the table belo -## Bitwise And [bitwise-and-operator] +## Bitwise and [bitwise-and-operator] Use the `bitwise and operator '&'` to AND together each bit within two integer type values where if both bits at the same index are `1` the resultant bit is `1` and `0` otherwise. @@ -822,7 +825,7 @@ bitwise_and: expression '&' expression; **Examples** -* Bitwise and with different integer types. +* Bitwise and with different integer types ```painless int i = 5 & 6; <1> @@ -832,7 +835,7 @@ bitwise_and: expression '&' expression; 1. declare `int i`; bitwise and `int 5` and `int 6` → `int 4`; store `int 4` in `i` 2. declare `long l` load from `int i` → `int 4`; promote `int 4` and `long 5`: result `long`; implicit cast `int 4` to `long 4` → `long 4`; bitwise and `long 4` and `long 5` → `long 4`; store `long 4` to `l` -* Bitwise and with the `def` type. +* Bitwise and with the `def` type ```painless def x = 15 & 6; <1> @@ -844,13 +847,13 @@ bitwise_and: expression '&' expression; -## Bitwise Xor [bitwise-xor-operator] +## Bitwise xor [bitwise-xor-operator] Use the `bitwise xor operator '^'` to XOR together each bit within two integer type values where if one bit is a `1` and the other bit is a `0` at the same index the resultant bit is `1` otherwise the resultant bit is `0`. **Errors** -* If either of the values is a non-integer type. +* If either of the values is a non-integer type **Bits** @@ -882,7 +885,7 @@ bitwise_xor: expression '^' expression; **Examples** -* Bitwise xor with different integer types. +* Bitwise xor with different integer types ```painless int i = 5 ^ 6; <1> @@ -892,7 +895,7 @@ bitwise_xor: expression '^' expression; 1. declare `int i`; bitwise xor `int 5` and `int 6` → `int 3`; store `int 3` in `i` 2. declare `long l` load from `int i` → `int 4`; promote `int 3` and `long 5`: result `long`; implicit cast `int 3` to `long 3` → `long 3`; bitwise xor `long 3` and `long 5` → `long 6`; store `long 6` to `l` -* Bitwise xor with the `def` type. +* Bitwise xor with the `def` type ```painless def x = 15 ^ 6; <1> @@ -904,7 +907,7 @@ bitwise_xor: expression '^' expression; -## Bitwise Or [bitwise-or-operator] +## Bitwise or [bitwise-or-operator] Use the `bitwise or operator '|'` to OR together each bit within two integer type values where if at least one bit is a `1` at the same index the resultant bit is `1` otherwise the resultant bit is `0`. @@ -942,7 +945,7 @@ bitwise_or: expression '|' expression; **Examples** -* Bitwise or with different integer types. +* Bitwise or with different integer types ```painless int i = 5 | 6; <1> @@ -952,7 +955,7 @@ bitwise_or: expression '|' expression; 1. declare `int i`; bitwise or `int 5` and `int 6` → `int 7`; store `int 7` in `i` 2. declare `long l` load from `int i` → `int 7`; promote `int 7` and `long 8`: result `long`; implicit cast `int 7` to `long 7` → `long 7`; bitwise or `long 7` and `long 8` → `long 15`; store `long 15` to `l` -* Bitwise or with the `def` type. +* Bitwise or with the `def` type ```painless def x = 5 ^ 6; <1> diff --git a/docs/reference/scripting-languages/painless/painless-operators-reference.md b/docs/reference/scripting-languages/painless/painless-operators-reference.md index f02df1ece770f..36d94cb200ba9 100644 --- a/docs/reference/scripting-languages/painless/painless-operators-reference.md +++ b/docs/reference/scripting-languages/painless/painless-operators-reference.md @@ -1,13 +1,16 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators-reference.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Operators: Reference [painless-operators-reference] -## Method Call [method-call-operator] +## Method call [method-call-operator] Use the `method call operator '()'` to call a member method on a [reference type](/reference/scripting-languages/painless/painless-types.md#reference-types) value. Implicit [boxing/unboxing](/reference/scripting-languages/painless/painless-casting.md#boxing-unboxing) is evaluated as necessary per argument during the method call. When a method call is made on a target `def` type value, the parameters and return type value are considered to also be of the `def` type and are evaluated at run-time. @@ -29,7 +32,7 @@ arguments: '(' (expression (',' expression)*)? ')'; **Examples** -* Method calls on different reference types. +* Method calls on different reference types ```painless Map m = new HashMap(); <1> @@ -49,7 +52,7 @@ arguments: '(' (expression (',' expression)*)? ')'; -## Field Access [field-access-operator] +## Field access [field-access-operator] Use the `field access operator '.'` to store a value to or load a value from a [reference type](/reference/scripting-languages/painless/painless-types.md#reference-types) member field. @@ -78,7 +81,7 @@ non-static member fields: * List z ``` -* Field access with the `Example` type. +* Field access with the `Example` type ```painless Example example = new Example(); <1> @@ -98,7 +101,7 @@ non-static member fields: -## Null Safe [null-safe-operator] +## Null safe [null-safe-operator] Use the `null safe operator '?.'` instead of the method call operator or field access operator to ensure a reference type value is `non-null` before a method call or field access. A `null` value will be returned if the reference type value is `null`, otherwise the method call or field access is evaluated. @@ -134,7 +137,7 @@ non-static member fields: * List x ``` -* Null safe without a `null` value. +* Null safe without a `null` value ```painless Example example = new Example(); <1> @@ -144,7 +147,7 @@ non-static member fields: 1. declare `Example example`; allocate `Example` instance → `Example reference`; store `Example reference` to `example` 2. declare `List x`; load from `example` → `Example reference`; null safe call `factory` on `Example reference` → `List reference`; store `List reference` to `x`; -* Null safe with a `null` value; +* Null safe with a `null` value ```painless Example example = null; <1> @@ -156,7 +159,7 @@ non-static member fields: -## List Initialization [list-initialization-operator] +## List initialization [list-initialization-operator] Use the `list initialization operator '[]'` to allocate an `List` type instance to the heap with a set of pre-defined values. Each value used to initialize the `List` type instance is cast to a `def` type value upon insertion into the `List` type instance using the `add` method. The order of the specified values is maintained. @@ -169,7 +172,7 @@ list_initialization: '[' expression (',' expression)* ']' **Examples** -* List initialization of an empty `List` type value. +* List initialization of an empty `List` type value ```painless List empty = []; <1> @@ -177,7 +180,7 @@ list_initialization: '[' expression (',' expression)* ']' 1. declare `List empty`; allocate `ArrayList` instance → `ArrayList reference`; implicit cast `ArrayList reference` to `List reference` → `List reference`; store `List reference` to `empty` -* List initialization with static values. +* List initialization with static values ```painless List list = [1, 2, 3]; <1> @@ -185,7 +188,7 @@ list_initialization: '[' expression (',' expression)* ']' 1. declare `List list`; allocate `ArrayList` instance → `ArrayList reference`; call `add` on `ArrayList reference` with arguments(`int 1`); call `add` on `ArrayList reference` with arguments(`int 2`); call `add` on `ArrayList reference` with arguments(`int 3`); implicit cast `ArrayList reference` to `List reference` → `List reference`; store `List reference` to `list` -* List initialization with non-static values. +* List initialization with non-static values ```painless int i = 1; <1> @@ -205,7 +208,7 @@ list_initialization: '[' expression (',' expression)* ']' -## List Access [list-access-operator] +## List access [list-access-operator] Use the `list access operator '[]'` as a shortcut for a `set` method call or `get` method call made on a `List` type value. @@ -222,7 +225,7 @@ list_access: '[' expression ']' **Examples** -* List access with the `List` type. +* List access with the `List` type ```painless List list = new ArrayList(); <1> @@ -246,7 +249,7 @@ list_access: '[' expression ']' 8. declare `int y`; store `int 1` int `y` 9. declare `int z`; load from `list` → `List reference`; load from `y` → `int 1`; call `get` on `List reference` with arguments(`int 1`) → `def`; implicit cast `def` to `int 5` → `int 5`; store `int 5` to `z` -* List access with the `def` type. +* List access with the `def` type ```painless def d = new ArrayList(); <1> @@ -272,7 +275,7 @@ list_access: '[' expression ']' -## Map Initialization [map-initialization-operator] +## Map initialization [map-initialization-operator] Use the `map initialization operator '[:]'` to allocate a `Map` type instance to the heap with a set of pre-defined values. Each pair of values used to initialize the `Map` type instance are cast to `def` type values upon insertion into the `Map` type instance using the `put` method. @@ -286,7 +289,7 @@ key_pair: expression ':' expression **Examples** -* Map initialization of an empty `Map` type value. +* Map initialization of an empty `Map` type value ```painless Map empty = [:]; <1> @@ -294,7 +297,7 @@ key_pair: expression ':' expression 1. declare `Map empty`; allocate `HashMap` instance → `HashMap reference`; implicit cast `HashMap reference` to `Map reference` → `Map reference`; store `Map reference` to `empty` -* Map initialization with static values. +* Map initialization with static values ```painless Map map = [1:2, 3:4, 5:6]; <1> @@ -302,7 +305,7 @@ key_pair: expression ':' expression 1. declare `Map map`; allocate `HashMap` instance → `HashMap reference`; call `put` on `HashMap reference` with arguments(`int 1`, `int 2`); call `put` on `HashMap reference` with arguments(`int 3`, `int 4`); call `put` on `HashMap reference` with arguments(`int 5`, `int 6`); implicit cast `HashMap reference` to `Map reference` → `Map reference`; store `Map reference` to `map` -* Map initialization with non-static values. +* Map initialization with non-static values ```painless byte b = 0; <1> @@ -324,7 +327,7 @@ key_pair: expression ':' expression -## Map Access [map-access-operator] +## Map access [map-access-operator] Use the `map access operator '[]'` as a shortcut for a `put` method call or `get` method call made on a `Map` type value. @@ -340,7 +343,7 @@ map_access: '[' expression ']' **Examples** -* Map access with the `Map` type. +* Map access with the `Map` type ```painless Map map = new HashMap(); <1> @@ -358,7 +361,7 @@ map_access: '[' expression ']' 5. declare `String y`; store `String 'value5'` to `y` 6. declare `int z`; load from `map` → `Map reference`; load from `y` → `String 'value5'`; call `get` on `Map reference` with arguments(`String 'value5'`) → `def`; implicit cast `def` to `int 5` → `int 5`; store `int 5` to `z` -* Map access with the `def` type. +* Map access with the `def` type ```painless def d = new HashMap(); <1> @@ -378,7 +381,7 @@ map_access: '[' expression ']' -## New Instance [new-instance-operator] +## New instance [new-instance-operator] Use the `new instance operator 'new ()'` to allocate a [reference type](/reference/scripting-languages/painless/painless-types.md#reference-types) instance to the heap and call a specified constructor. Implicit [boxing/unboxing](/reference/scripting-languages/painless/painless-casting.md#boxing-unboxing) is evaluated as necessary per argument during the constructor call. @@ -398,7 +401,7 @@ new_instance: 'new' TYPE '(' (expression (',' expression)*)? ')'; **Examples** -* Allocation of new instances with different types. +* Allocation of new instances with different types ```painless Map m = new HashMap(); <1> @@ -412,7 +415,7 @@ def e = new HashMap(m); <3> -## String Concatenation [string-concatenation-operator] +## String concatenation [string-concatenation-operator] Use the `string concatenation operator '+'` to concatenate two values together where at least one of the values is a [`String` type](/reference/scripting-languages/painless/painless-types.md#string-type). @@ -424,7 +427,7 @@ concatenate: expression '+' expression; **Examples** -* String concatenation with different primitive types. +* String concatenation with different primitive types ```painless String x = "con"; <1> @@ -464,7 +467,7 @@ elvis: expression '?:' expression; **Examples** -* Elvis with different reference types. +* Elvis with different reference types ```painless List x = new ArrayList(); <1> diff --git a/docs/reference/scripting-languages/painless/painless-operators.md b/docs/reference/scripting-languages/painless/painless-operators.md index 51574e74ce2d7..325b97340fe41 100644 --- a/docs/reference/scripting-languages/painless/painless-operators.md +++ b/docs/reference/scripting-languages/painless/painless-operators.md @@ -1,65 +1,143 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Operators [painless-operators] -An operator is the most basic action that can be taken to evaluate values in a script. An expression is one-to-many consecutive operations. Precedence is the order in which an operator will be evaluated relative to another operator. Associativity is the direction within an expression in which a specific operator is evaluated. The following table lists all available operators: +Operators are the fundamental building blocks for data manipulation in Painless scripts. They enable calculations, comparisons, logical operations, and data access across all {{es}} scripting [contexts](/reference/scripting-languages/painless/painless-contexts.md). + +An **operator** performs a specific action to evaluate values in a script. An **expression** combines one or more operators to produce a result. **Precedence** determines evaluation order when multiple operators are present, while **associativity** controls evaluation direction for operators with equal precedence. + +Painless operators use Java-like syntax with {{es}} specific enhancements such as null-safe navigation and specialized data structure access. + +## Operator categories + +Painless organizes operators into five functional categories based on their purpose. + +:::{image} images/painless-operator-categories.png +:alt: Painless operator categories +::: +Double-click to expand the image. + +### General operators + +Control the fundamental flow and structure of expressions in Painless scripts. These operators manage how expressions are evaluated, values are assigned, and conditional logic is run. + +* [Precedence](/reference/scripting-languages/painless/painless-operators-general.md#precedence-operator) `()`: Controls evaluation order by overriding default precedence rules +* [Function call](/reference/scripting-languages/painless/painless-operators-general.md#function-call-operator) `()`: Executes user-defined functions with arguments +* [Cast](/reference/scripting-languages/painless/painless-operators-general.md#cast-operator) `()` : Forces explicit type conversion between compatible types +* [Conditional](/reference/scripting-languages/painless/painless-operators-general.md#conditional-operator) `? :` : Provides inline if-else logic for expressions +* [Elvis](/reference/scripting-languages/painless/painless-operators-reference.md#elvis-operator) `?:` : Returns first non-null value for null coalescing +* [Assignment](/reference/scripting-languages/painless/painless-operators-general.md#assignment-operator) `=` : Stores values in variables or fields +* [Compound assignment](/reference/scripting-languages/painless/painless-operators-general.md#compound-assignment-operator) `$=` : Combines binary operations with assignment (`+=`, `-=`, and so on) + +### Numeric operators + +Perform mathematical calculations and bit-level manipulations on numeric values. These operators handle arithmetic, bitwise operations, and value modifications essential for numerical computations. + +* [Increment](/reference/scripting-languages/painless/painless-operators-numeric.md#post-increment-operator)/[Decrement](/reference/scripting-languages/painless/painless-operators-numeric.md#post-decrement-operator) (`++`. `--`) : Increases or decreases values by one (pre/post variants) +* [Unary](/reference/scripting-languages/painless/painless-operators-numeric.md#unary-positive-operator) (`+`, `-`) : preserves or negates numeric values +* [Bitwise not](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-not-operator) `~` : inverts all bits in integer values +* [Multiplication](/reference/scripting-languages/painless/painless-operators-numeric.md#multiplication-operator)/[Division](/reference/scripting-languages/painless/painless-operators-numeric.md#division-operator)/[Remainder](/reference/scripting-languages/painless/painless-operators-numeric.md#remainder-operator) `*` `/` `%` : Basic arithmetic operations +* [Addition](/reference/scripting-languages/painless/painless-operators-numeric.md#addition-operator)/[Subtraction](/reference/scripting-languages/painless/painless-operators-numeric.md#subtraction-operator) `+` `-` : Basic arithmetic operations +* [Shift](/reference/scripting-languages/painless/painless-operators-numeric.md#left-shift-operator) (`<<`, `>>`, `>>>`) : Shifts bits left or right within integer values +* [Bitwise and](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-and-operator) `&` : Performs AND operations on corresponding bits +* [Bitwise xor](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-xor-operator) `^` : Performs XOR operations on corresponding bits +* [Bitwise or](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-or-operator) `|`: Performs OR operations on corresponding bits + +### Boolean operators + +Handle logical evaluation, comparisons, and conditional expressions. These operators are fundamental for creating filters, conditional logic, and boolean expressions in scripts + +* [Boolean not](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-not-operator) `!`:Inverts boolean values (true becomes false) +* [Comparison](/reference/scripting-languages/painless/painless-operators-boolean.md#greater-than-operator) `>` `>=` `<` `<=` : Compares numeric values for ordering +* [Instanceof](/reference/scripting-languages/painless/painless-operators-boolean.md#instanceof-operator) `instanceof`: Checks if an object is an instance of a specific type +* [Equality](/reference/scripting-languages/painless/painless-operators-boolean.md#equality-equals-operator) `==` `!=` : Compares values for equality (calls equals() method) +* [Identity](/reference/scripting-languages/painless/painless-operators-boolean.md#identity-equals-operator) `===` `!==` : Compares object references for same instance +* [Boolean xor](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-xor-operator) `^`: Returns true if exactly one operand is true +* [Boolean and](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-and-operator) `&&`: Returns true only if both operands are true +* [Boolean or](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-or-operator) `||`: Returns true if at least one operand is true + +### Reference operators + +Enable interaction with objects, method calls, and data structure manipulation. These operators are essential for working with documents, collections, and complex data types in {{es}} contexts. + +* [Method call](/reference/scripting-languages/painless/painless-operators-reference.md#method-call-operator) `. ()`: Invokes methods on objects with optional arguments +* [Field access](/reference/scripting-languages/painless/painless-operators-reference.md#field-access-operator) `.`: Accesses object properties and member fields +* [Null safe](/reference/scripting-languages/painless/painless-operators-reference.md#null-safe-operator) `?.`: Safely accesses fields/methods without null pointer exceptions +* [List/Map initialization](/reference/scripting-languages/painless/painless-operators-reference.md#list-initialization-operator) `[] [:]`: Creates new List or Map collections with initial values +* [List/Map access](/reference/scripting-languages/painless/painless-operators-reference.md#list-access-operator) `[]`: Retrieves or sets elements in collections by key/index +* [New instance](/reference/scripting-languages/painless/painless-operators-reference.md#new-instance-operator) `new ()`: Creates new object instances with constructor arguments +* [String concatenation](/reference/scripting-languages/painless/painless-operators-reference.md#string-concatenation-operator) `+` : Joins strings and converts other types to strings + +### Array operators + +Provide specialized functionality for array creation, element access, and array property retrieval. These operators are essential when working with multi-value fields and array data structures. + +* [Array initialization](/reference/scripting-languages/painless/painless-operators-array.md#array-initialization-operator) `[] {}`\`: Creates arrays with predefined values +* [Array access](/reference/scripting-languages/painless/painless-operators-array.md#array-access-operator) `[]`: Retrieves or sets array elements by index position +* [Array length](/reference/scripting-languages/painless/painless-operators-array.md#array-length-operator) `.`: Returns the number of elements in an array +* [New array](/reference/scripting-languages/painless/painless-operators-array.md#new-array-operator) `new []`: Creates arrays with specified dimensions and sizes + +## Complete operator reference | **Operator** | **Category** | **Symbol(s)** | **Precedence** | **Associativity** | | --- | --- | --- | --- | --- | | [Precedence](/reference/scripting-languages/painless/painless-operators-general.md#precedence-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | () | 0 | left → right | -| [Method Call](/reference/scripting-languages/painless/painless-operators-reference.md#method-call-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | . () | 1 | left → right | -| [Field Access](/reference/scripting-languages/painless/painless-operators-reference.md#field-access-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | . | 1 | left → right | -| [Null Safe](/reference/scripting-languages/painless/painless-operators-reference.md#null-safe-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | ?. | 1 | left → right | -| [Function Call](/reference/scripting-languages/painless/painless-operators-general.md#function-call-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | () | 1 | left → right | -| [Array Initialization](/reference/scripting-languages/painless/painless-operators-array.md#array-initialization-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | [] {} | 1 | left → right | -| [Array Access](/reference/scripting-languages/painless/painless-operators-array.md#array-access-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | [] | 1 | left → right | -| [Array Length](/reference/scripting-languages/painless/painless-operators-array.md#array-length-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | . | 1 | left → right | -| [List Initialization](/reference/scripting-languages/painless/painless-operators-reference.md#list-initialization-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [] | 1 | left → right | -| [List Access](/reference/scripting-languages/painless/painless-operators-reference.md#list-access-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [] | 1 | left → right | -| [Map Initialization](/reference/scripting-languages/painless/painless-operators-reference.md#map-initialization-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [:] | 1 | left → right | -| [Map Access](/reference/scripting-languages/painless/painless-operators-reference.md#map-access-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [] | 1 | left → right | -| [Post Increment](/reference/scripting-languages/painless/painless-operators-numeric.md#post-increment-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ++ | 1 | left → right | -| [Post Decrement](/reference/scripting-languages/painless/painless-operators-numeric.md#post-decrement-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | — | 1 | left → right | -| [Pre Increment](/reference/scripting-languages/painless/painless-operators-numeric.md#pre-increment-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ++ | 2 | right → left | -| [Pre Decrement](/reference/scripting-languages/painless/painless-operators-numeric.md#pre-decrement-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | — | 2 | right → left | -| [Unary Positive](/reference/scripting-languages/painless/painless-operators-numeric.md#unary-positive-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | + | 2 | right → left | -| [Unary Negative](/reference/scripting-languages/painless/painless-operators-numeric.md#unary-negative-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | - | 2 | right → left | -| [Boolean Not](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-not-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | ! | 2 | right → left | -| [Bitwise Not](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-not-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ~ | 2 | right → left | +| [Method call](/reference/scripting-languages/painless/painless-operators-reference.md#method-call-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | . () | 1 | left → right | +| [Field access](/reference/scripting-languages/painless/painless-operators-reference.md#field-access-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | . | 1 | left → right | +| [Null safe](/reference/scripting-languages/painless/painless-operators-reference.md#null-safe-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | ?. | 1 | left → right | +| [Function call](/reference/scripting-languages/painless/painless-operators-general.md#function-call-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | () | 1 | left → right | +| [Array initialization](/reference/scripting-languages/painless/painless-operators-array.md#array-initialization-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | [] {} | 1 | left → right | +| [Array access](/reference/scripting-languages/painless/painless-operators-array.md#array-access-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | [] | 1 | left → right | +| [Array length](/reference/scripting-languages/painless/painless-operators-array.md#array-length-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | . | 1 | left → right | +| [List initialization](/reference/scripting-languages/painless/painless-operators-reference.md#list-initialization-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [] | 1 | left → right | +| [List access](/reference/scripting-languages/painless/painless-operators-reference.md#list-access-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [] | 1 | left → right | +| [Map initialization](/reference/scripting-languages/painless/painless-operators-reference.md#map-initialization-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [:] | 1 | left → right | +| [Map access](/reference/scripting-languages/painless/painless-operators-reference.md#map-access-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | [] | 1 | left → right | +| [Post increment](/reference/scripting-languages/painless/painless-operators-numeric.md#post-increment-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ++ | 1 | left → right | +| [Post decrement](/reference/scripting-languages/painless/painless-operators-numeric.md#post-decrement-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | — | 1 | left → right | +| [Pre increment](/reference/scripting-languages/painless/painless-operators-numeric.md#pre-increment-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ++ | 2 | right → left | +| [Pre decrement](/reference/scripting-languages/painless/painless-operators-numeric.md#pre-decrement-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | — | 2 | right → left | +| [Unary positive](/reference/scripting-languages/painless/painless-operators-numeric.md#unary-positive-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | + | 2 | right → left | +| [Unary negative](/reference/scripting-languages/painless/painless-operators-numeric.md#unary-negative-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | - | 2 | right → left | +| [Boolean not](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-not-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | ! | 2 | right → left | +| [Bitwise not](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-not-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ~ | 2 | right → left | | [Cast](/reference/scripting-languages/painless/painless-operators-general.md#cast-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | () | 3 | right → left | -| [New Instance](/reference/scripting-languages/painless/painless-operators-reference.md#new-instance-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | new () | 3 | right → left | -| [New Array](/reference/scripting-languages/painless/painless-operators-array.md#new-array-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | new [] | 3 | right → left | +| [New instance](/reference/scripting-languages/painless/painless-operators-reference.md#new-instance-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | new () | 3 | right → left | +| [New array](/reference/scripting-languages/painless/painless-operators-array.md#new-array-operator) | [Array](/reference/scripting-languages/painless/painless-operators-array.md) | new [] | 3 | right → left | | [Multiplication](/reference/scripting-languages/painless/painless-operators-numeric.md#multiplication-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | * | 4 | left → right | | [Division](/reference/scripting-languages/painless/painless-operators-numeric.md#division-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | / | 4 | left → right | | [Remainder](/reference/scripting-languages/painless/painless-operators-numeric.md#remainder-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | % | 4 | left → right | -| [String Concatenation](/reference/scripting-languages/painless/painless-operators-reference.md#string-concatenation-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | + | 5 | left → right | +| [String concatenation](/reference/scripting-languages/painless/painless-operators-reference.md#string-concatenation-operator) | [Reference](/reference/scripting-languages/painless/painless-operators-reference.md) | + | 5 | left → right | | [Addition](/reference/scripting-languages/painless/painless-operators-numeric.md#addition-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | + | 5 | left → right | | [Subtraction](/reference/scripting-languages/painless/painless-operators-numeric.md#subtraction-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | - | 5 | left → right | -| [Left Shift](/reference/scripting-languages/painless/painless-operators-numeric.md#left-shift-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | << | 6 | left → right | -| [Right Shift](/reference/scripting-languages/painless/painless-operators-numeric.md#right-shift-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | >> | 6 | left → right | -| [Unsigned Right Shift](/reference/scripting-languages/painless/painless-operators-numeric.md#unsigned-right-shift-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | >>> | 6 | left → right | -| [Greater Than](/reference/scripting-languages/painless/painless-operators-boolean.md#greater-than-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | > | 7 | left → right | -| [Greater Than Or Equal](/reference/scripting-languages/painless/painless-operators-boolean.md#greater-than-or-equal-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | >= | 7 | left → right | -| [Less Than](/reference/scripting-languages/painless/painless-operators-boolean.md#less-than-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | < | 7 | left → right | -| [Less Than Or Equal](/reference/scripting-languages/painless/painless-operators-boolean.md#less-than-or-equal-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | <= | 7 | left → right | +| [Left shift](/reference/scripting-languages/painless/painless-operators-numeric.md#left-shift-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | << | 6 | left → right | +| [Right shift](/reference/scripting-languages/painless/painless-operators-numeric.md#right-shift-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | >> | 6 | left → right | +| [Unsigned right shift](/reference/scripting-languages/painless/painless-operators-numeric.md#unsigned-right-shift-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | >>> | 6 | left → right | +| [Greater than](/reference/scripting-languages/painless/painless-operators-boolean.md#greater-than-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | > | 7 | left → right | +| [Greater than Or Equal](/reference/scripting-languages/painless/painless-operators-boolean.md#greater-than-or-equal-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | >= | 7 | left → right | +| [Less than](/reference/scripting-languages/painless/painless-operators-boolean.md#less-than-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | < | 7 | left → right | +| [Less than Or Equal](/reference/scripting-languages/painless/painless-operators-boolean.md#less-than-or-equal-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | <= | 7 | left → right | | [Instanceof](/reference/scripting-languages/painless/painless-operators-boolean.md#instanceof-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | instanceof | 8 | left → right | -| [Equality Equals](/reference/scripting-languages/painless/painless-operators-boolean.md#equality-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | == | 9 | left → right | -| [Equality Not Equals](/reference/scripting-languages/painless/painless-operators-boolean.md#equality-not-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | != | 9 | left → right | -| [Identity Equals](/reference/scripting-languages/painless/painless-operators-boolean.md#identity-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | === | 9 | left → right | -| [Identity Not Equals](/reference/scripting-languages/painless/painless-operators-boolean.md#identity-not-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | !== | 9 | left → right | -| [Bitwise And](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-and-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | & | 10 | left → right | -| [Boolean Xor](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-xor-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | ^ | 11 | left → right | -| [Bitwise Xor](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-xor-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ^ | 11 | left → right | -| [Bitwise Or](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-or-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | | | 12 | left → right | -| [Boolean And](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-and-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | && | 13 | left → right | -| [Boolean Or](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-or-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | || | 14 | left → right | +| [Equality equals](/reference/scripting-languages/painless/painless-operators-boolean.md#equality-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | == | 9 | left → right | +| [Equality not equals](/reference/scripting-languages/painless/painless-operators-boolean.md#equality-not-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | != | 9 | left → right | +| [Identity equals](/reference/scripting-languages/painless/painless-operators-boolean.md#identity-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | === | 9 | left → right | +| [Identity not equals](/reference/scripting-languages/painless/painless-operators-boolean.md#identity-not-equals-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | !== | 9 | left → right | +| [Bitwise and](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-and-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | & | 10 | left → right | +| [Boolean xor](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-xor-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | ^ | 11 | left → right | +| [Bitwise xor](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-xor-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | ^ | 11 | left → right | +| [Bitwise or](/reference/scripting-languages/painless/painless-operators-numeric.md#bitwise-or-operator) | [Numeric](/reference/scripting-languages/painless/painless-operators-numeric.md) | | | 12 | left → right | +| [Boolean and](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-and-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | && | 13 | left → right | +| [Boolean or](/reference/scripting-languages/painless/painless-operators-boolean.md#boolean-or-operator) | [Boolean](/reference/scripting-languages/painless/painless-operators-boolean.md) | || | 14 | left → right | | [Conditional](/reference/scripting-languages/painless/painless-operators-general.md#conditional-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | ? : | 15 | right → left | | [Elvis](/reference/scripting-languages/painless/painless-operators-reference.md#elvis-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | ?: | 16 | right → left | | [Assignment](/reference/scripting-languages/painless/painless-operators-general.md#assignment-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | = | 17 | right → left | -| [Compound Assignment](/reference/scripting-languages/painless/painless-operators-general.md#compound-assignment-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | $= | 17 | right → left | +| [Compound assignment](/reference/scripting-languages/painless/painless-operators-general.md#compound-assignment-operator) | [General](/reference/scripting-languages/painless/painless-operators-general.md) | $= | 17 | right → left | diff --git a/docs/reference/scripting-languages/painless/painless-regexes.md b/docs/reference/scripting-languages/painless/painless-regexes.md index 92d1970bcdc89..d4f40dfc03fa4 100644 --- a/docs/reference/scripting-languages/painless/painless-regexes.md +++ b/docs/reference/scripting-languages/painless/painless-regexes.md @@ -1,13 +1,18 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-regexes.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Regexes [painless-regexes] -Regular expression constants are directly supported. To ensure fast performance, this is the only mechanism for creating patterns. Regular expressions are always constants and compiled efficiently a single time. +Regular expression constants are directly supported. To ensure fast performance, this is the only mechanism for creating patterns. Regular expressions are always constants and are compiled a single time for efficiency. + +% You can check out the [regular expressions tutorial](docs-content://explore-analyze/scripting/modules-scripting-regular-expressions-tutorial.md) for related examples, and for help with troubleshooting, refer to [regex pattern matching failures](docs-content://troubleshoot/elasticsearch/painless-regex-pattern-matching-failures.md). ```painless Pattern p = /[aeiou]/ @@ -17,7 +22,6 @@ Pattern p = /[aeiou]/ A poorly written regular expression can significantly slow performance. If possible, avoid using regular expressions, particularly in frequently run scripts. :::: - ## Pattern flags [pattern-flags] You can define flags on patterns in Painless by adding characters after the trailing `/` like `/foo/i` or `/foo \w #comment/iUx`. Painless exposes all of the flags from Java’s [ Pattern class](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.md) using these characters: @@ -32,5 +36,3 @@ You can define flags on patterns in Painless by adding characters after the trai | `U` | UNICODE_CHARACTER_CLASS | `'Ɛ' ==~ /\\w/U` | | `u` | UNICODE_CASE | `'Ɛ' ==~ /ɛ/iu` | | `x` | COMMENTS (aka extended) | `'a' ==~ /a #comment/x` | - - diff --git a/docs/reference/scripting-languages/painless/painless-reindex-context.md b/docs/reference/scripting-languages/painless/painless-reindex-context.md index 168f6e2fcaaf4..0f71d6b115d1e 100644 --- a/docs/reference/scripting-languages/painless/painless-reindex-context.md +++ b/docs/reference/scripting-languages/painless/painless-reindex-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-reindex-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,7 @@ products: Use a Painless script in a [reindex](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex) operation to add, modify, or delete fields within each document in an original index as its reindexed into a target index. -**Variables** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -32,7 +35,7 @@ Use a Painless script in a [reindex](https://www.elastic.co/docs/api/doc/elastic [`ctx['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) (`Map`) : Contains extracted JSON in a `Map` and `List` structure for the fields existing in a stored document. -**Side Effects** +## Side Effects `ctx['op']` : Use the default of `index` to update a document. Set to `noop` to specify no operation or `delete` to delete the current document from the index. @@ -52,12 +55,51 @@ Use a Painless script in a [reindex](https://www.elastic.co/docs/api/doc/elastic [`ctx['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) : Modify the values in the `Map/List` structure to add, modify, or delete the fields of a document. -**Return** +## Return `void` : No expected return value. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +The following request copies all documents from the `kibana_sample_data_ecommerce` index to a new index called `data_ecommerce_usd`. During this process, a script is used to convert all monetary values to a new currency using a specified exchange rate in the `params`. + +```json +POST /_reindex +{ + "source": { + "index": "kibana_sample_data_ecommerce" + }, + "dest": { + "index": "data_ecommerce_usd" + }, + "script": { + "source": """ + float er = (float) params.exchange_rate; // Cast parameter to float to ensure proper decimal calculations + ctx._source.currency = params.target_currency; + + // Convert all prices to dollar + ctx._source.taxful_total_price = Math.round(ctx._source.taxful_total_price * er * 100) / 100; + ctx._source.taxless_total_price = Math.round(ctx._source.taxless_total_price * er * 100) / 100; + + for (product in ctx._source.products) { + product.price = Math.round(product.price * er * 100) / 100; + product.base_price = Math.round(product.base_price * er * 100) / 100; + product.taxful_price = Math.round(product.taxful_price * er * 100) / 100; + product.taxless_price = Math.round(product.taxless_price * er * 100) / 100; + } + """, + "lang": "painless", + "params": { + "exchange_rate": 1.10, + "target_currency": "USD" + } + } +} +``` diff --git a/docs/reference/scripting-languages/painless/painless-runtime-fields-context.md b/docs/reference/scripting-languages/painless/painless-runtime-fields-context.md index 1fbac613e0f79..b0a63dead8979 100644 --- a/docs/reference/scripting-languages/painless/painless-runtime-fields-context.md +++ b/docs/reference/scripting-languages/painless/painless-runtime-fields-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-runtime-fields-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,9 +12,11 @@ products: Use a Painless script to calculate and emit [runtime field](/reference/scripting-languages/painless/use-painless-scripts-in-runtime-fields.md) values. -See the [runtime fields](docs-content://manage-data/data-store/mapping/runtime-fields.md) documentation for more information about how to use runtime fields. +Runtime fields are dynamic fields that are calculated at query time rather than being indexed. This approach provides flexibility for data exploration and field creation without requiring reindexing, though it comes with performance trade-offs compared to indexed fields. -**Methods** +For comprehensive information about runtime field implementation and use cases, refer to the [runtime fields documentation](docs-content://manage-data/data-store/mapping/runtime-fields.md). You can also check the troubleshooting guide for help with runtime field exceptions. + +## Methods $$$runtime-emit-method$$$ @@ -70,7 +75,7 @@ $$$runtime-emit-method$$$ :::: -**Variables** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -81,97 +86,105 @@ $$$runtime-emit-method$$$ [`params['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) (`Map`, read-only) : Contains extracted JSON in a `Map` and `List` structure for the fields existing in a stored document. -**Return** +## Return `void` : No expected return value. -**API** +## API -Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and [Specialized Field API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-field.html) are available. +Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and specialized [Field API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-field.html) are available. -**Example** +## Example -To run the examples, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -Then, run the following request to define a runtime field named `day_of_week`. This field contains a script with the same `source` defined in [Field context](/reference/scripting-languages/painless/painless-field-context.md), but also uses an `emit` function that runtime fields require when defining a Painless script. +Run the following request to define a runtime field named `full_day_name`. This field contains a script that extracts the day of the week from the `order_date` field and assigns the full day name using the `dayOfWeekEnum` enumeration. The script uses the `emit` function, which is required for runtime fields. -Because `day_of_week` is a runtime field, it isn’t indexed, and the included script only runs at query time: +Because `full_day_name` is a runtime field, it isn’t indexed, and the script runs dynamically at query time: -```console -PUT seats/_mapping +```json +PUT kibana_sample_data_ecommerce/_mapping { "runtime": { - "day_of_week": { + "full_day_name": { "type": "keyword", "script": { - "source": "emit(doc['datetime'].value.getDayOfWeekEnum().toString())" + "source": """emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT)); + """ } } } } ``` -% TEST[setup:seats] -After defining the runtime field and script in the mappings, you can run a query that includes a terms aggregation for `day_of_week`. When the query runs, {{es}} evaluates the included Painless script and dynamically generates a value based on the script definition: +After defining the runtime field, you can run a query that includes a `terms` aggregation for `full_day_name`. At search time, {{es}} executes the script to dynamically calculate the value for each document: -```console -GET seats/_search +```json +GET kibana_sample_data_ecommerce/_search { "size": 0, - "fields": [ - "time", - "day_of_week" - ], - "aggs": { - "day_of_week": { - "terms": { - "field": "day_of_week", - "size": 10 - } + "aggs": { + "full_day_name": { + "terms": { + "field": "full_day_name", + "size": 10 } } + } } ``` -% TEST[continued] -The response includes `day_of_week` for each hit. {{es}} calculates the value for this field dynamically at search time by operating on the `datetime` field defined in the mappings. +The response includes an aggregation bucket for each time period. {{es}} calculates the value of the `full_day_name` field dynamically at search time, based on the `order_date` field in each document. -```console-result +Response: + +```json { ... - "hits" : { - "total" : { - "value" : 11, - "relation" : "eq" + "hits": { + "total": { + "value": 4675, + "relation": "eq" }, - "max_score" : null, - "hits" : [ ] + "max_score": null, + "hits": [] }, - "aggregations" : { - "day_of_week" : { - "doc_count_error_upper_bound" : 0, - "sum_other_doc_count" : 0, - "buckets" : [ + "aggregations": { + "full_day_name": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "Thu", + "doc_count": 775 + }, + { + "key": "Fri", + "doc_count": 770 + }, + { + "key": "Sat", + "doc_count": 736 + }, { - "key" : "TUESDAY", - "doc_count" : 5 + "key": "Sun", + "doc_count": 614 }, { - "key" : "THURSDAY", - "doc_count" : 4 + "key": "Tue", + "doc_count": 609 }, { - "key" : "MONDAY", - "doc_count" : 1 + "key": "Wed", + "doc_count": 592 }, { - "key" : "SUNDAY", - "doc_count" : 1 + "key": "Mon", + "doc_count": 579 } ] } } } ``` -% TESTRESPONSE[s/\.\.\./"took" : $body.took,"timed_out" : $body.timed_out,"_shards" : $body._shards,/] diff --git a/docs/reference/scripting-languages/painless/painless-score-context.md b/docs/reference/scripting-languages/painless/painless-score-context.md index dbb64fb76581f..e59a0bc631139 100644 --- a/docs/reference/scripting-languages/painless/painless-score-context.md +++ b/docs/reference/scripting-languages/painless/painless-score-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-score-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,9 @@ products: Use a Painless script in a [function score](/reference/query-languages/query-dsl/query-dsl-function-score-query.md) to apply a new score to documents returned from a query. -**Variables** +Check the troubleshooting guide for help with script score calculation errors. + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -20,39 +25,46 @@ Use a Painless script in a [function score](/reference/query-languages/query-dsl `_score` (`double` read-only) : The similarity score of the current document. -**Return** +## Return `double` : The score for the current document. -**API** +## API -Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and [Specialized Score API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-score.html) are available. +Both the standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) and specialized [Field API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-field.html) are available. -**Example** +## Example -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -The following query finds all unsold seats, with lower *row* values scored higher. +The following request boosts results for customers in New York, helping highlight local preferences or regional inventory. It uses a `function_score` query to match products with "jeans" in the name and increase their score when the city is New York. -```console -GET /seats/_search +```json +GET kibana_sample_data_ecommerce/_search { "query": { "function_score": { "query": { "match": { - "sold": "false" + "products.product_name": "jeans" } }, "script_score": { "script": { - "source": "1.0 / doc['row'].value" + "source": """ + double baseScore = _score; + def city = doc['geoip.city_name']; + + if (city.size() > 0 && city.value == 'New York') { + baseScore *= 1.5; + } + + return baseScore; + """ } } } } -} +} ``` -% TEST[setup:seats] - diff --git a/docs/reference/scripting-languages/painless/painless-scripts.md b/docs/reference/scripting-languages/painless/painless-scripts.md index a31e7c3dad840..eb9f085e50f0d 100644 --- a/docs/reference/scripting-languages/painless/painless-scripts.md +++ b/docs/reference/scripting-languages/painless/painless-scripts.md @@ -1,11 +1,17 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-scripts.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Scripts [painless-scripts] -Scripts are composed of one-to-many [statements](/reference/scripting-languages/painless/painless-statements.md) and are run in a sandbox that determines what local variables are immediately available along with what APIs are allowed. +A script in Painless is a complete program composed of one or more [statements](/reference/scripting-languages/painless/painless-statements.md) that performs a specific task within {{es}}. Scripts run within a controlled sandbox environment that determines which variables are available and which APIs can be accessed based on the [use context](docs-content://explore-analyze/scripting/painless-syntax-context-bridge.md). +Scripts are the fundamental runnable unit in Painless, allowing you to customize⁣ {{es}} behavior for search scoring, data transformation, field calculations, and operational tasks. Each script runs in isolation with access to context-specific variables and a curated set of safe operations. + +For detailed guidance on writing scripts, practical examples, and step-by-step tutorials, refer to [How to write Painless scripts](docs-content://explore-analyze/scripting/modules-scripting-using.md). diff --git a/docs/reference/scripting-languages/painless/painless-similarity-context.md b/docs/reference/scripting-languages/painless/painless-similarity-context.md index 3f1b73e5414a8..9d3dfd7fa2ba2 100644 --- a/docs/reference/scripting-languages/painless/painless-similarity-context.md +++ b/docs/reference/scripting-languages/painless/painless-similarity-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-similarity-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,11 @@ products: Use a Painless script to create a [similarity](/reference/elasticsearch/index-settings/similarity.md) equation for scoring documents in a query. -**Variables** +:::{tip} +This is an advanced feature for customizing how document relevance scores are calculated during search. For comprehensive information about similarity functions and their implementation, refer to the [similarity documentation](/reference/elasticsearch/index-settings/similarity.md). +::: + +## Variables `weight` (`float`, read-only) : The weight as calculated by a [weight script](/reference/scripting-languages/painless/painless-weight-context.md) @@ -42,12 +49,40 @@ Note that the `query`, `field`, and `term` variables are also available to the [ 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** +## Return `double` : The similarity score for the current document. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +The following request creates a new index named `ecommerce_rare_terms` with a custom `similarity` equation called `rare_boost`. This similarity function modifies how document relevance scores are calculated during search by giving more weight to rare terms. + +This can be used if you want to customize how documents are matched and scored against query terms, for example to boost products with unique or uncommon attributes in search results. + +```json +PUT kibana_sample_data_ecommerce-rare_terms +{ + "settings": { + "similarity": { + "rare_boost": { + "type": "scripted", + "script": { + "source": """ + double tf = Math.sqrt(doc.freq); + double idf = Math.log((field.docCount + 1)/(term.docFreq + 1)); // If the term appears in less than 5% of the documents, it will be boosted + double rareBoost = term.docFreq < (field.docCount * 0.05) ? 2 : 1; + return query.boost * tf * idf * rareBoost; + """ + } + } + } + } +} +``` diff --git a/docs/reference/scripting-languages/painless/painless-sort-context.md b/docs/reference/scripting-languages/painless/painless-sort-context.md index c52d6707b5167..f0e15d78784e8 100644 --- a/docs/reference/scripting-languages/painless/painless-sort-context.md +++ b/docs/reference/scripting-languages/painless/painless-sort-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-sort-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,7 @@ products: Use a Painless script to [sort](/reference/elasticsearch/rest-apis/sort-search-results.md) the documents in a query. -**Variables** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -20,42 +23,37 @@ Use a Painless script to [sort](/reference/elasticsearch/rest-apis/sort-search-r `_score` (`double` read-only) : The similarity score of the current document. -**Return** +## Return `double` or `String` : The sort key. The return type depends on the value of the `type` parameter in the script sort config (`"number"` or `"string"`). -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. -**Example** +## Example -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -To sort results by the length of the `theatre` field, submit the following query: +The following request sorts documents by the average price per item, calculated by dividing the `taxful_total_price` by the `total_quantity`. -```console -GET /_search +Documents with a higher average item price appear at the top of the results. + +```json +GET /kibana_sample_data_ecommerce/_search { - "query": { - "term": { - "sold": "true" - } - }, "sort": { "_script": { "type": "number", "script": { "lang": "painless", - "source": "doc['theatre'].value.length() * params.factor", - "params": { - "factor": 1.1 - } + "source": """ + doc['taxful_total_price'].value / doc['total_quantity'].value + """ }, - "order": "asc" + "order": "desc" } } } ``` - diff --git a/docs/reference/scripting-languages/painless/painless-statements.md b/docs/reference/scripting-languages/painless/painless-statements.md index 894ba3441b6f1..7d53c57ebf4a7 100644 --- a/docs/reference/scripting-languages/painless/painless-statements.md +++ b/docs/reference/scripting-languages/painless/painless-statements.md @@ -1,66 +1,150 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-statements.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Statements [painless-statements] -Painless supports all of Java’s [ control flow statements](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/flow.md) except the `switch` statement. +Statements are individual instructions that perform actions in your Painless scripts. They control the flow of the code, define logic branches, and manage how your code processes data. Painless supports all Java [control flow statements](https://dev.java/learn/language-basics/controlling-flow/) except the `switch` statement. + +Statements in Painless allow you to create conditional logic, iterate through data, and structure your scripts for complex data processing tasks. Understanding these control structures is essential for writing effective scripts that can handle various scenarios in {{es}} workflows. ## Conditional statements [_conditional_statements] +Conditional statements enable your script to run different code paths based on values in the data. + ### If / Else [_if_else] -```painless -if (doc[item].size() == 0) { - // do something if "item" is missing -} else if (doc[item].value == 'something') { - // do something if "item" value is: something +Use `if`, `else if`, and `else` statements to create conditional logic that runs different code blocks based on boolean expressions. + +#### Example + +In this example, the product’s category is determined based on the product’s price value, classifying it as "Affordable"m "Moderately priced", or "Expensive". + +```java +int price = 64; +String priceCategory; + +if (price > 60) { + priceCategory = "Expensive"; +} else if (price < 30) { + priceCategory = "Affordable"; } else { - // do something else + priceCategory = "Moderately Priced"; } + +return priceCategory; // Expensive ``` +### Ternary operator +The ternary operator (`? :`) provides a concise way to perform conditional assignments. It’s a conditional statement that achieves the same purpose as `if/else` but in a more compact form. -## Loop statements [_loop_statements] +#### Example -### For [_for] +In this example, the product’s category is determined based on the product’s price value, classifying it as "Affordable" or "Expensive". -Painless also supports the `for in` syntax: +```java +int price = 64; +String priceCategory; -```painless -for (def item : list) { - // do something -} -``` +priceCategory = (price >= 60) ? "Affordable" : "Expensive"; -```painless -for (item in list) { - // do something -} +return priceCategory; // Expensive ``` +## Loop statements [_loop_statements] -### While [_while] +Loop statements allow you to repeat running code multiple times, either for a specific number of iterations or while a condition remains true. -```painless -while (ctx._source.item < condition) { - // do something -} -``` +### For +Painless supports both traditional `for` loops and enhanced `for` loops (`for-each`). Use `for` loops to iterate through data collections or repeat operations a specific number of times. -### Do-While [_do_while] +#### Examples -```painless -do { - // do something +* Traditional for loop: + + The following loop creates an empty array with four positions and assigns a value from `0` to `3` to each position. + + ```java + int[] arr = new int[4]; + + for (int i = 0; i < 4; i++) { + arr[i] = i; + } + + return arr; // [0, 1, 2, 3] + ``` + +* Enhanced `for` loop (for-each): + + The following code snippets create a list containing letters. Using a `for-each` loop, they concatenate the letters into a single string called `word`. + + ```java + List letters = ["h", "e", "l", "l", "o"]; + String word = ""; + + for (l in letters) { + word += l; + } + + return word; // hello + ``` + +* Alternative `for` loop syntax: + + ```java + List letters = ["h", "e", "l", "l", "o"]; + String word = ""; + + for (def l : letters) { + word += l; + } + + return word; // hello + ``` + +### While + +Use `while` loops to repeat running code as long as a specified condition remains true. The condition is evaluated before each iteration. + +#### Example + +Similar to the first example for the `for` statement, this one assigns a number from `0` to `3` to each position of an array with four elements using a `while` loop. + +```java +int[] arr = new int[4]; +int i = 0; // counter + +while (i < 4) { + arr[i] = i; + i++; // increment counter } -while (ctx._source.item < condition) + +return arr; // [0, 1, 2, 3] ``` +### Do-While +Use `do-while` loops to run code at least once, then repeat as long as the condition remains true. The condition is evaluated after each iteration. +#### Example + +This code defines an array with mixed types (strings and integers) and uses a `do-while` loop to concatenate all elements into a single string called word. + +```java +def[] letters = new def[] {"a", 1, "b", 2}; +String word = ""; +int i = 0; // counte +do { + word += letters[i]; + i++; // increment counter +} while (i < letters.length) +return word; // a1b2 +``` diff --git a/docs/reference/scripting-languages/painless/painless-types.md b/docs/reference/scripting-languages/painless/painless-types.md index 2f12bbb96032d..ec5356326f6de 100644 --- a/docs/reference/scripting-languages/painless/painless-types.md +++ b/docs/reference/scripting-languages/painless/painless-types.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-types.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,7 @@ products: A type is a classification of data used to define the properties of a value. These properties specify what data a value represents and the rules for how a value is evaluated during an [operation](/reference/scripting-languages/painless/painless-operators.md). Each type belongs to one of the following categories: [primitive](#primitive-types), [reference](#reference-types), or [dynamic](#dynamic-types). -## Primitive Types [primitive-types] +## Primitive types [primitive-types] A primitive type represents basic data built natively into the JVM and is allocated to non-heap memory. Declare a primitive type [variable](/reference/scripting-languages/painless/painless-variables.md) or access a primitive type member field (from a reference type instance), and assign it a primitive type value for evaluation during later operations. The default value for a newly-declared primitive type variable is listed as part of the definitions below. A primitive type value is copied during an assignment or as an argument for a method/function call. @@ -49,7 +52,7 @@ The following primitive types are available. The corresponding reference type is **Examples** -* Primitive types used in declaration, declaration and assignment. +* Primitive types used in declaration, declaration and assignment ```painless int i = 1; <1> @@ -61,7 +64,7 @@ The following primitive types are available. The corresponding reference type is 2. declare `double d`; store default `double 0.0` to `d` 3. declare `boolean b`; store `boolean true` to `b` -* Method call on a primitive type using the corresponding reference type. +* Method call on a primitive type using the corresponding reference type ```painless int i = 1; <1> @@ -73,7 +76,68 @@ The following primitive types are available. The corresponding reference type is -## Reference Types [reference-types] +## Character type usage [character-type-usage] + +The `char` type is a 16-bit, unsigned, Unicode character with a range of [`0`,`65535`] and a default value of `0` or `\u0000`. + +Character values cannot be declared without an explicit cast from a string. + +``` +// This will cause an error + char x = "a"; + +// Must always cast +char x = (char) "a"; +``` + +### String to character casting + +Use the cast operator to convert a [string-type](/reference/scripting-languages/painless/painless-types.md#string-type) value into a `char`\-type value. + +**Errors** + +* If the `String` type value isn’t one character in length. +* If the `String` type value is `null`. + +**Examples** + +* Casting string literals into `char` type values + + ``` + char c = (char)"C"; // declare char c; explicit cast String "C" to char C → char C; store char C to c + c = (char)'c'; // explicit cast String 'c' to char c → char c; store char c to c + ``` + +* Casting a `String` reference into a `char` type value + + ``` + String s = "s"; // declare String s; store String "s" to s + char c = (char)s; // declare char c; load from s → String "s"; explicit cast String "s" to char s → char s; store char s to c + ``` + +* Working with character positions in strings + + ``` + String word = "Hello"; + char first = (char) word.charAt(0); // 'H' + char third = (char) word.charAt(2); // 'l' + ``` + + +### Character to string casting + +Use the cast operator to convert a `char` type value into a [`String` type](/reference/scripting-languages/painless/painless-types.md#string-type) value. + +**Examples** + +* Casting a `char` to a `String` + + ``` + char c = 65; // declare char c; store char 65 to c + String s = (String)c; // declare String s; load from c → char A; explicit cast char A to String 'A' → String 'A'; store String 'A' to s + ``` + +## Reference types [reference-types] A reference type is a named construct (object), potentially representing multiple pieces of data (member fields) and logic to manipulate that data (member methods), defined as part of the application programming interface (API) for scripts. @@ -104,7 +168,7 @@ A reference type object follows a basic inheritance model. Consider types A and **Examples** -* Reference types evaluated in several different operations. +* Reference types evaluated in several different operations ```painless List l = new ArrayList(); <1> @@ -132,7 +196,7 @@ A reference type object follows a basic inheritance model. Consider types A and 4. load from `l1` → `List reference`; implicit cast `int 2` to `def` → `def` call `add` on `List reference` with arguments (`def`) 5. declare `int i`; load from `l0` → `List reference`; call `get` on `List reference` with arguments (`int 0`) → `def @0`; implicit cast `def @0` to `int 1` → `int 1`; load from `l1` → `List reference`; call `get` on `List reference` with arguments (`int 1`) → `def @1`; implicit cast `def @1` to `int 2` → `int 2`; add `int 1` and `int 2` → `int 3`; store `int 3` to `i`; -* Using the static members of a reference type. +* Using the static members of a reference type ```painless int i = Integer.MAX_VALUE; <1> @@ -144,7 +208,7 @@ A reference type object follows a basic inheritance model. Consider types A and -## Dynamic Types [dynamic-types] +## Dynamic types [dynamic-types] A dynamic type value can represent the value of any primitive type or reference type using a single type name `def`. A `def` type value mimics the behavior of whatever value it represents at run-time and will always represent the child-most descendant type value of any type value when evaluated during operations. @@ -158,7 +222,7 @@ Using the `def` type can have a slight impact on performance. Use only primitive **Examples** -* General uses of the `def` type. +* General uses of the `def` type ```painless def dp = 1; <1> @@ -184,13 +248,13 @@ Using the `def` type can have a slight impact on performance. Use only primitive -## String Type [string-type] +## String type [string-type] The `String` type is a specialized reference type that does not require explicit allocation. Use a [string literal](/reference/scripting-languages/painless/painless-literals.md#string-literals) to directly evaluate a `String` type value. While not required, the [new instance operator](/reference/scripting-languages/painless/painless-operators-reference.md#new-instance-operator) can allocate `String` type instances. **Examples** -* General use of the `String` type. +* General use of the `String` type ```painless String r = "some text"; <1> @@ -206,13 +270,13 @@ The `String` type is a specialized reference type that does not require explicit -## void Type [void-type] +## Void type [void-type] The `void` type represents the concept of a lack of type. Use the `void` type to indicate a function returns no value. **Examples** -* Use of the `void` type in a function. +* Use of the `void` type in a function ```painless void addToList(List l, def d) { @@ -222,7 +286,7 @@ The `void` type represents the concept of a lack of type. Use the `void` type to -## Array Type [array-type] +## Array type [array-type] An array type is a specialized reference type where an array type instance contains a series of values allocated to the heap. Each value in an array type instance is defined as an element. All elements in an array type instance are of the same type (element type) specified as part of declaration. Each element is assigned an index within the range `[0, length)` where length is the total number of elements allocated for an array type instance. @@ -234,7 +298,7 @@ When an array type instance is allocated with multiple dimensions using the rang **Examples** -* General use of single-dimensional arrays. +* General use of single-dimensional arrays ```painless int[] x; <1> @@ -250,7 +314,7 @@ When an array type instance is allocated with multiple dimensions using the rang 4. load from `y` → `1-d float array reference`; store `float 1.0` to `index [9]` of `1-d float array reference` 5. load from `y` → `1-d float array reference @0`; load from `index [9]` of `1-d float array reference @0` → `float 1.0`; load from `z` → `def`; implicit cast `def` to `1-d float array reference @1` → `1-d float array reference @1`; store `float 1.0` to `index [0]` of `1-d float array reference @1` -* General use of a multi-dimensional array. +* General use of a multi-dimensional array ```painless int[][][] ia3 = new int[2][3][4]; <1> diff --git a/docs/reference/scripting-languages/painless/painless-update-by-query-context.md b/docs/reference/scripting-languages/painless/painless-update-by-query-context.md index 17e4ac429860d..08ffcc8d8c044 100644 --- a/docs/reference/scripting-languages/painless/painless-update-by-query-context.md +++ b/docs/reference/scripting-languages/painless/painless-update-by-query-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-update-by-query-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,9 @@ products: Use a Painless script in an [update by query](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update-by-query) operation to add, modify, or delete fields within each of a set of documents collected as the result of query. -**Variables** +Check out the [document update tutorial](docs-content://explore-analyze/scripting/modules-scripting-document-update-tutorial.md) for related examples. + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -32,7 +37,7 @@ Use a Painless script in an [update by query](https://www.elastic.co/docs/api/do [`ctx['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) (`Map`) : Contains extracted JSON in a `Map` and `List` structure for the fields existing in a stored document. -**Side Effects** +## Side Effects `ctx['op']` : Use the default of `index` to update a document. Set to `none` to specify no operation or `delete` to delete the current document from the index. @@ -40,49 +45,61 @@ Use a Painless script in an [update by query](https://www.elastic.co/docs/api/do [`ctx['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) : Modify the values in the `Map/List` structure to add, modify, or delete the fields of a document. -**Return** +## Return `void` : No expected return value. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. -**Example** +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +The following request uses the `_update_by_query` API to apply a discount to products that meet two conditions: -The following query finds all seats in a specific section that have not been sold and lowers the price by 2: +1. The product’s `base_price` is greater than or equal to 20\. +2. The product currently has a `discount_percentage` of 0 (no discount applied). -```console -POST /seats/_update_by_query +```json +POST /kibana_sample_data_ecommerce/_update_by_query { "query": { "bool": { "filter": [ { "range": { - "row": { - "lte": 3 + "products.base_price": { + "gte": 20 } } }, { "match": { - "sold": false + "products.discount_percentage": 0 } } ] } }, "script": { - "source": "ctx._source.cost -= params.discount", "lang": "painless", + "source": """ + for (product in ctx._source.products) { + // If product has no discount applied + if (product.discount_percentage == 0) { + product.discount_percentage = params.discount_rate; // Assigns the discount rate + product.discount_amount = product.base_price * (params.discount_rate / 100) ; // Calculates and assigns the total discount + + product.price = product.base_price - product.discount_amount; // Calculates and assigns the product price with discount + } + } + """, "params": { - "discount": 2 + "discount_rate": 15 } } } ``` -% TEST[setup:seats] diff --git a/docs/reference/scripting-languages/painless/painless-update-context.md b/docs/reference/scripting-languages/painless/painless-update-context.md index 202d8a36483ba..c2a2a66c02c9c 100644 --- a/docs/reference/scripting-languages/painless/painless-update-context.md +++ b/docs/reference/scripting-languages/painless/painless-update-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-update-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -9,7 +12,9 @@ products: Use a Painless script in an [update](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update) operation to add, modify, or delete fields within a single document. -**Variables** +Check out the [document update tutorial](docs-content://explore-analyze/scripting/modules-scripting-document-update-tutorial.md) for related examples. + +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -35,7 +40,7 @@ Use a Painless script in an [update](https://www.elastic.co/docs/api/doc/elastic [`ctx['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) (`Map`) : Contains extracted JSON in a `Map` and `List` structure for the fields existing in a stored document. -**Side Effects** +## Side Effects `ctx['op']` : Use the default of `index` to update a document. Set to `none` to specify no operation or `delete` to delete the current document from the index. @@ -43,31 +48,48 @@ Use a Painless script in an [update](https://www.elastic.co/docs/api/doc/elastic [`ctx['_source']`](/reference/elasticsearch/mapping-reference/mapping-source-field.md) : Modify the values in the `Map/List` structure to add, modify, or delete the fields of a document. -**Return** +## Return `void` : No expected return value. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. -**Example** +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +The following example demonstrates how to apply a promotional discount to an order. + +Find a valid document ID: + +```json +GET /kibana_sample_data_ecommerce/_search +{ + "size": 1, + "_source": false +} +``` -To run this example, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +The script performs two key actions: -The following query updates a document to be sold, and sets the cost to the actual price paid after discounts: +1. Calculates a discounted price by subtracting $5.00 from the original `taxful_total_price.` +2. Adds the flag `discount_applied` to identify orders that received promotional pricing. -```console -POST /seats/_update/3 +```json +POST /kibana_sample_data_ecommerce/_update/YOUR_DOCUMENT_ID { "script": { - "source": "ctx._source.sold = true; ctx._source.cost = params.sold_cost", "lang": "painless", + "source": """ + ctx._source.discount_applied = true; + ctx._source.final_price = ctx._source.taxful_total_price - params.discount_amount; + """, "params": { - "sold_cost": 26 + "discount_amount": 5.00 } } } ``` -% TEST[setup:seats] diff --git a/docs/reference/scripting-languages/painless/painless-variables.md b/docs/reference/scripting-languages/painless/painless-variables.md index 309953291a511..df2f734d2129a 100644 --- a/docs/reference/scripting-languages/painless/painless-variables.md +++ b/docs/reference/scripting-languages/painless/painless-variables.md @@ -1,33 +1,51 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-variables.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Variables [painless-variables] -A variable loads and stores a value for evaluation during [operations](/reference/scripting-languages/painless/painless-operators.md). +Variables are containers for storing data values in your Painless scripts. Variables hold different types of data like numbers, text, lists, and other values that you can access and manipulate throughout your script. + +In Painless, every variable must be declared with a specific type before you can use it. This type determines what kind of data the variable can hold and what operations you can perform on it. For example, you might use an `int` variable to store a document’s score, a `String` variable to store a field value, or a `List` variable to collect multiple results. + +## Variable Types in Painless + +Painless supports three main categories of variable types: + +* **Primitive types** for basic data such as `int`, `double`, `boolean`, and `char`. These store simple values directly and have default values (`0` for numbers, `false` for boolean). +* **Reference types** for complex objects such as `String`, `list`, `map`, and array types like `int[]`. These store references to objects and default to `null` when not initialized. +* **Dynamic type** with `def` that can represent any type of value, determined at runtime. This provides flexibility when the exact type isn’t known in advance. + + +:::{tip} +Painless variable types are similar to Java types. For a complete reference of available types and their specifications, refer to the [Java variables documentation](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html). +::: ## Declaration [variable-declaration] -Declare a variable before use with the format of [type](/reference/scripting-languages/painless/painless-types.md) followed by [identifier](/reference/scripting-languages/painless/painless-identifiers.md). Declare an [array type](/reference/scripting-languages/painless/painless-types.md#array-type) variable using an opening `[` token and a closing `]` token for each dimension directly after the identifier. Specify a comma-separated list of identifiers following the type to declare multiple variables in a single statement. Use an [assignment operator](#variable-assignment) combined with a declaration to immediately assign a value to a variable. A variable not immediately assigned a value will have a default value assigned implicitly based on the type. +Before using a variable, you must declare it with the format [type](/reference/scripting-languages/painless/painless-types.md) followed by an [identifier](/reference/scripting-languages/painless/painless-identifiers.md). If the variable is an [array-type](/reference/scripting-languages/painless/painless-types.md#array-type), use an opening `[` token and a closing `]` token for each dimension directly after the identifier. Specify a comma-separated list of identifiers following the type to declare multiple variables in a single statement. Use an [assignment operator](#variable-assignment) combined with a declaration to immediately assign a value to a variable. A variable not immediately assigned a value will have a default value assigned implicitly based on the type. -**Errors** +### Errors * If a variable is used prior to or without declaration. -**Grammar** +### Grammar -```text +``` declaration : type ID assignment? (',' ID assignment?)*; type: ID ('.' ID)* ('[' ']')*; assignment: '=' expression; ``` -**Examples** +### Examples -* Different variations of variable declaration. +* Different variations of variable declaration ```painless int x; <1> @@ -38,10 +56,10 @@ assignment: '=' expression; float[] f; <6> Map[][] m; <7> ``` - + 1. declare `int x`; store default `null` to `x` 2. declare `List y`; store default `null` to `y` - 3. declare `int x`; store default `int 0` to `x`; declare `int y`; store `int 5` to `y`; declare `int z`; store default `int 0` to `z`; + 3. declare `int x`; store default `int 0` to `x`; declare `int y`; store `int 5` to `y`; declare `int z`; store default`int 0` to `z`; 4. declare `def d`; store default `null` to `d` 5. declare `int i`; store `int 10` to `i` 6. declare `float[] f`; store default `null` to `f` @@ -55,7 +73,7 @@ Use the `assignment operator '='` to store a value in a variable for use in subs **Errors** -* If the type of value is unable to match the type of variable. +An error if the type of value is unable to match the type of variable. **Grammar** @@ -65,7 +83,7 @@ assignment: ID '=' expression **Examples** -* Variable assignment with an integer literal. +* Variable assignment with an integer literal ```painless int i; <1> @@ -75,7 +93,7 @@ assignment: ID '=' expression 1. declare `int i`; store default `int 0` to `i` 2. store `int 10` to `i` -* Declaration combined with immediate assignment. +* Declaration combined with immediate assignment ```painless int i = 10; <1> @@ -85,7 +103,7 @@ assignment: ID '=' expression 1. declare `int i`; store `int 10` to `i` 2. declare `double j`; store `double 2.0` to `j` -* Assignment of one variable to another using primitive type values. +* Assignment of one variable to another using primitive type values ```painless int i = 10; <1> @@ -95,7 +113,7 @@ assignment: ID '=' expression 1. declare `int i`; store `int 10` to `i` 2. declare `int j`; load from `i` → `int 10`; store `int 10` to `j` -* Assignment with reference types using the [new instance operator](/reference/scripting-languages/painless/painless-operators-reference.md#new-instance-operator). +* Assignment with reference types using the [new instance operator](/reference/scripting-languages/painless/painless-operators-reference.md#new-instance-operator) ```painless ArrayList l = new ArrayList(); <1> @@ -105,7 +123,7 @@ assignment: ID '=' expression 1. declare `ArrayList l`; allocate `ArrayList` instance → `ArrayList reference`; store `ArrayList reference` to `l` 2. declare `Map m`; allocate `HashMap` instance → `HashMap reference`; implicit cast `HashMap reference` to `Map reference` → `Map reference`; store `Map reference` to `m` -* Assignment of one variable to another using reference type values. +* Assignment of one variable to another using reference type values ```painless List l = new ArrayList(); <1> diff --git a/docs/reference/scripting-languages/painless/painless-walkthrough-access-doc-values.md b/docs/reference/scripting-languages/painless/painless-walkthrough-access-doc-values.md new file mode 100644 index 0000000000000..f61fff4634e04 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-walkthrough-access-doc-values.md @@ -0,0 +1,81 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# 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" + } + } + } +} +``` diff --git a/docs/reference/scripting-languages/painless/painless-walkthrough-dates.md b/docs/reference/scripting-languages/painless/painless-walkthrough-dates.md new file mode 100644 index 0000000000000..60a4cb7a9b256 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-walkthrough-dates.md @@ -0,0 +1,24 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Dates [modules-scripting-painless-dates] + +Date fields are exposed as `ZonedDateTime`, so they support methods like `getYear`, `getDayOfWeek` or, for example, 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" + } + } + } +} +``` \ No newline at end of file diff --git a/docs/reference/scripting-languages/painless/painless-walkthrough-missing-keys-or-values.md b/docs/reference/scripting-languages/painless/painless-walkthrough-missing-keys-or-values.md new file mode 100644 index 0000000000000..a4c68f9516db8 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-walkthrough-missing-keys-or-values.md @@ -0,0 +1,21 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Missing keys or values [_missing_keys] + +When you access document values, using `doc['myfield'].value` throws an exception if the specified field is missing in a document. + +For more dynamic index mappings, you can write a catch equation: + +``` +if (!doc.containsKey('myfield') || doc['myfield'].empty) { return "unavailable" } else { return doc['myfield'].value } +``` + +This expression tests for the existence of `myfield`, returning its value only if the key exists. + +To check if a document is missing a value, call `doc['myfield'].size() == 0`. \ No newline at end of file diff --git a/docs/reference/scripting-languages/painless/painless-walkthrough-regular-expressions.md b/docs/reference/scripting-languages/painless/painless-walkthrough-regular-expressions.md new file mode 100644 index 0000000000000..3d063e8bb2af1 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-walkthrough-regular-expressions.md @@ -0,0 +1,120 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# Regular expressions [modules-scripting-painless-regex] + +::::{note} +Regexes are enabled by default by the setting `script.painless.regex.enabled` which has a default value of `limited` the default. This enables the use of regular expressions but limits their complexity. Innocuous looking regexes can have sometimes have adverse performance and stack depth behavior, but they still remain a powerful tool. In addition to `limited`, you can set `script.painless.regex.enabled` to `true` in `elasticsearch.yml` to enable regular expressions without limiting them. +:::: + + +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` 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. + diff --git a/docs/reference/scripting-languages/painless/painless-walkthrough-updating-fields.md b/docs/reference/scripting-languages/painless/painless-walkthrough-updating-fields.md new file mode 100644 index 0000000000000..4c1299dc6b438 --- /dev/null +++ b/docs/reference/scripting-languages/painless/painless-walkthrough-updating-fields.md @@ -0,0 +1,58 @@ +--- +applies_to: + stack: ga + serverless: ga +products: + - id: painless +--- + +# 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.`. + +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" + } + } +} +``` diff --git a/docs/reference/scripting-languages/painless/painless-watcher-condition-context.md b/docs/reference/scripting-languages/painless/painless-watcher-condition-context.md index 9cbf798c7d175..27fb393e32efa 100644 --- a/docs/reference/scripting-languages/painless/painless-watcher-condition-context.md +++ b/docs/reference/scripting-languages/painless/painless-watcher-condition-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-watcher-condition-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -11,7 +14,7 @@ Use a Painless script as a [watch condition](docs-content://explore-analyze/aler The following variables are available in all watcher contexts. -**Variables** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -37,38 +40,56 @@ The following variables are available in all watcher contexts. `ctx['payload']` (`Map`, read-only) : The accessible watch data based upon the [watch input](docs-content://explore-analyze/alerts-cases/watcher/input.md). -**Return** +## Return `boolean` : Expects `true` if the condition is met, and `false` if it is not. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. -**Example** +## Example -To run the examples, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). +To run the examples, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). -```console +**Manufacturer revenue anomaly detection** +The following script creates a watcher that runs daily to monitor manufacturer revenue anomalies by querying the last seven days of documents and calculating `total_revenue` per `manufacturer.keyword`. + +The condition in the script filters manufacturers with `total_revenue.value` either below 200 or above 2000, triggering an alert log when any anomalous manufacturers are detected. + +```json POST _watcher/watch/_execute { - "watch" : { - "trigger" : { "schedule" : { "interval" : "24h" } }, - "input" : { - "search" : { - "request" : { - "indices" : [ "seats" ], - "body" : { - "query" : { - "term": { "sold": "true"} + "watch": { + "trigger": { + "schedule": { + "interval": "24h" + } + }, + "input": { + "search": { + "request": { + "indices": ["kibana_sample_data_ecommerce"], + "body": { + "query": { + "range": { + "order_date": { + "gte": "now-7d" + } + } }, - "aggs" : { - "theatres" : { - "terms" : { "field" : "play" }, - "aggs" : { - "money" : { - "sum": { "field" : "cost" } + "size": 0, + "aggs": { + "manufacturers": { + "terms": { + "field": "manufacturer.keyword" + }, + "aggs": { + "total_revenue": { + "sum": { + "field": "taxful_total_price" + } } } } @@ -77,89 +98,79 @@ POST _watcher/watch/_execute } } }, - "condition" : { - "script" : - """ - return ctx.payload.aggregations.theatres.buckets.stream() <1> - .filter(theatre -> theatre.money.value < 15000 || - theatre.money.value > 50000) <2> - .count() > 0 <3> + "condition": { + "script": """ + return ctx.payload.aggregations.manufacturers.buckets.stream() + .filter(manufacturer -> manufacturer.total_revenue.value < 200 || + manufacturer.total_revenue.value > 2000) + .count() > 0 """ }, - "actions" : { - "my_log" : { - "logging" : { - "text" : "The output of the search was : {{ctx.payload.aggregations.theatres.buckets}}" + "actions": { + "alert_log": { + "logging": { + "text": "ALERT: Manufacturers with anomalous sales detected: {{ctx.payload.aggregations.manufacturers.buckets}}" } } } } } ``` -% TEST[setup:seats] - -1. The Java Stream API is used in the condition. This API allows manipulation of the elements of the list in a pipeline. -2. The stream filter removes items that do not meet the filter criteria. -3. If there is at least one item in the list, the condition evaluates to true and the watch is executed. +**High-value order detection** +This example runs hourly to detect high-value orders by filtering orders from the last hour when `taxful_total_price` is more than 150. The script generates a log message when it finds high-value orders. -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 $10,000. - -```console +```json POST _watcher/watch/_execute { - "watch" : { - "trigger" : { "schedule" : { "interval" : "24h" } }, - "input" : { - "search" : { - "request" : { - "indices" : [ "seats" ], - "body" : { - "query" : { - "term": { "sold": "true"} - }, - "size": 0, - "aggs" : { - "theatres" : { - "terms" : { "field" : "play" }, - "aggs" : { - "money" : { - "sum": { - "field" : "cost", - "script": { - "source": "doc.cost.value * doc.number.value" + "watch": { + "trigger": { + "schedule": { + "interval": "1h" + } + }, + "input": { + "search": { + "request": { + "indices": ["kibana_sample_data_ecommerce"], + "body": { + "query": { + "bool": { + "filter": [ + { + "range": { + "order_date": { + "gte": "now-1h" + } + } + }, + { + "range": { + "taxful_total_price": { + "gte": 150 } } } - } + ] } - } + }, + "size": 0 } } } }, - "actions" : { - "my_log" : { - "condition": { <1> - "script" : - """ - return ctx.payload.aggregations.theatres.buckets.stream() - .anyMatch(theatre -> theatre.money.value > 10000) <2> - """ - }, - "logging" : { - "text" : "At least one play has grossed over $10,000: {{ctx.payload.aggregations.theatres.buckets}}" + "condition": { + "script": """ + return ctx.payload.hits.total > 0 + """ + }, + "actions": { + "high_value_notification": { + "logging": { + "text": "ALERT: {{ctx.payload.hits.total}} high-value orders (over 150 EUR) detected in the last hour" } } } } } ``` -% TEST[setup:seats] - -This example uses a nearly identical condition as the previous example. The differences below are subtle and are worth calling out. - -1. The location of the condition is no longer at the top level, but is within an individual action. -2. Instead of a filter, `anyMatch` is used to return a boolean value - - diff --git a/docs/reference/scripting-languages/painless/painless-watcher-transform-context.md b/docs/reference/scripting-languages/painless/painless-watcher-transform-context.md index 5b7afcabf10ce..b73fef4fb9b75 100644 --- a/docs/reference/scripting-languages/painless/painless-watcher-transform-context.md +++ b/docs/reference/scripting-languages/painless/painless-watcher-transform-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-watcher-transform-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -11,7 +14,7 @@ Use a Painless script as a [watch transform](docs-content://explore-analyze/aler The following variables are available in all watcher contexts. -**Variables** +## Variables `params` (`Map`, read-only) : User-defined parameters passed in as part of the query. @@ -37,173 +40,56 @@ The following variables are available in all watcher contexts. `ctx['payload']` (`Map`, read-only) : The accessible watch data based upon the [watch input](docs-content://explore-analyze/alerts-cases/watcher/input.md). -**Return** +## Return `Object` : The new payload. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. -**Example** +## Example -To run the examples, first follow the steps in [context examples](/reference/scripting-languages/painless/painless-context-examples.md). - -```console -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}}" - } - } - } - } -} -``` -% TEST[setup:seats] - -1. The Java Stream API is used in the transform. This API allows manipulation of the elements of the list in a pipeline. -2. The stream filter removes items that do not meet the filter criteria. -3. The stream map transforms each element into a new object. -4. The collector reduces the stream to a `java.util.List`. -5. This is done again for the second set of values in the transform. +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). +This request creates an automated sales monitoring system that checks your eCommerce data every hour and sends alerts when certain product categories are performing well. -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. +The transform script processes the aggregation results from the search input, extracting `bucket.key` (category name) and `bucket.revenue.value` (sales amount) from each category. It uses `ctx.metadata.alert_threshold` (50 USD) to determine which categories trigger alerts, creating a structured output with category details and alert flags for the logging action. -```console +```json 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" } - } - } - } - } - } - } - } + "watch": { + "metadata": { + "alert_threshold": 50 }, - "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}}" - } + "trigger": { + "schedule": { + "interval": "1h" } - } - } -} -``` -% TEST[setup:seats] - -This example uses the streaming API in a very similar manner. The differences below are subtle and worth calling out. - -1. The location of the transform is no longer at the top level, but is within an individual action. -2. 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](/reference/scripting-languages/painless/painless-watcher-condition-context.md). - -```console -POST _watcher/watch/_execute -{ - "watch" : { - "metadata" : { "high_threshold": 4000, "low_threshold": 1000 }, - "trigger" : { "schedule" : { "interval" : "24h" } }, - "input" : { - "search" : { - "request" : { - "indices" : [ "seats" ], - "body" : { - "query" : { - "term": { "sold": "true"} + }, + "input": { + "search": { + "request": { + "indices": ["kibana_sample_data_ecommerce"], + "body": { + "query": { + "range": { + "order_date": { + "gte": "now-24h" + } + } }, - "aggs" : { - "theatres" : { - "terms" : { "field" : "play" }, - "aggs" : { - "money" : { + "aggs": { + "by_category": { + "terms": { + "field": "category.keyword" + }, + "aggs": { + "revenue": { "sum": { - "field" : "cost", - "script": { - "source": "doc.cost.value * doc.number.value" - } + "field": "taxful_total_price" } } } @@ -213,132 +99,63 @@ POST _watcher/watch/_execute } } }, - "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}}" - } + "condition": { + "script": { + "source": """ + return ctx.payload.aggregations.by_category.buckets.size() > 0; + """ } - } - } -} -``` -% TEST[setup:seats] - -The following example shows the use of metadata and transforming dates into a readable format. - -```console -POST _watcher/watch/_execute -{ - "watch" : { - "metadata" : { "min_hits": 10 }, - "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": { + "source": """ +/* + Process the aggregation buckets to: + 1. Calculate total revenue across all categories. + 2. Build a list of categories with: + - name + - sales amount (rounded to 2 decimals) + - alert flag if revenue exceeds threshold from ctx.metadata. + 3. Return the processed data along with the watch execution time. + */ + + def categories = []; + def total = 0.0; + + for (bucket in ctx.payload.aggregations.by_category.buckets) { + def revenue = bucket.revenue.value; + total += revenue; + + categories.add([ + 'name': bucket.key, + 'sales': Math.round(revenue * 100) / 100.0, + 'alert': revenue > ctx.metadata.alert_threshold + ]); } - } + + return [ + 'total_sales': Math.round(total * 100) / 100.0, + 'categories': categories, + 'execution_time': ctx.execution_time + ]; + """ } }, - "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" + "actions": { + "notify": { + "logging": { + "text": """ + Daily Sales Report - {{ctx.payload.execution_time}} + Total Revenue: ${{ctx.payload.total_sales}} + + Categories: + {{#ctx.payload.categories}} + - {{name}}: ${{sales}} {{#alert}}⚠️ HIGH{{/alert}} + {{/ctx.payload.categories}} + """ } } } } } ``` -% TEST[setup:seats] diff --git a/docs/reference/scripting-languages/painless/painless-weight-context.md b/docs/reference/scripting-languages/painless/painless-weight-context.md index da0103aba61d7..d75100e76a36e 100644 --- a/docs/reference/scripting-languages/painless/painless-weight-context.md +++ b/docs/reference/scripting-languages/painless/painless-weight-context.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-weight-context.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -11,7 +14,7 @@ Use a Painless script to create a [weight](/reference/elasticsearch/index-settin Queries that contain multiple terms calculate a separate weight for each term. -**Variables** +## Variables `query.boost` (`float`, read-only) : The boost value if provided by the query. If this is not provided the value is `1.0f`. @@ -31,12 +34,63 @@ Queries that contain multiple terms calculate a separate weight for each term. `term.totalTermFreq` (`long`, read-only) : The total occurrences of the current term in the index. -**Return** +## Return `double` : A scoring factor used across all documents. -**API** +## API The standard [Painless API](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared.html) is available. +## Example + +To run the example, first [install the eCommerce sample data](/reference/scripting-languages/painless/painless-context-examples.md#painless-sample-data-install). + +This request creates an index with a smart search system that automatically boosts products with rare, distinctive features like "vintage", "handcrafted", or unique materials. It helps discover special items by making uncommon products rank higher in search results. + +```json +PUT kibana_sample_data_ecommerce-weight +{ + "settings": { + "similarity": { + "rare_term_weight": { + "type": "scripted", + "weight_script": { + "source": """ + double idf = Math.log((field.docCount + 1.0) / (term.docFreq + 1.0)); + + double rarityFactor = 1.0; + if (term.docFreq < field.docCount * 0.02) { + rarityFactor = 3.0; // Very rare terms get a high boost + } else if (term.docFreq < field.docCount * 0.10) { + rarityFactor = 1.5; // Somewhat rare terms get a medium boost + } + + return query.boost * idf * rarityFactor; + """ + }, + "script": { + "source": """ + double tf = Math.sqrt(doc.freq); + + return tf * weight; + """ + } + } + } + }, + "mappings": { + "properties": { + "product_name": { + "type": "text", + "similarity": "rare_term_weight" + }, + "description": { + "type": "text", + "similarity": "rare_term_weight" + } + } + } +} +``` diff --git a/docs/reference/scripting-languages/painless/painless.md b/docs/reference/scripting-languages/painless/painless.md index baaec6eab4fac..cc0e510156ded 100644 --- a/docs/reference/scripting-languages/painless/painless.md +++ b/docs/reference/scripting-languages/painless/painless.md @@ -1,27 +1,43 @@ --- +navigation_title: Painless mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/index.html - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-guide.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- -# Painless [painless-guide] +# Painless scripting language [painless-guide] -*Painless* is a simple, secure scripting language designed specifically for use with Elasticsearch. It is the default scripting language for Elasticsearch and can safely be used for inline and stored scripts. For a jump start into Painless, see [A Brief Painless Walkthrough](/reference/scripting-languages/painless/brief-painless-walkthrough.md). For a detailed description of the Painless syntax and language features, see the [Painless Language Specification](/reference/scripting-languages/painless/painless-language-specification.md). +:::{tip} +This section introduces Painless syntax and advanced features. If you're new to Painless scripting, start with our [introduction](docs-content://explore-analyze/scripting/modules-scripting-painless.md) for fundamentals and examples. +::: -You can use Painless anywhere scripts are used in Elasticsearch. Painless provides: +Painless is the default scripting language for {{es}}, designed for security, performance, and flexibility. Built on the [Java Virtual Machine (JVM)](https://docs.oracle.com/en/java/javase/24/vm/java-virtual-machine-technology-overview.html), Painless provides Java-like syntax with direct compilation, a sandbox environment with fine-grained allowlists, and context-aware scripting across the {{stack}}. -* Fast performance: Painless scripts [ run several times faster](https://benchmarks.elastic.co/index.md#search_qps_scripts) than the alternatives. -* Safety: Fine-grained allowlist with method call/field granularity. -* Optional typing: Variables and parameters can use explicit types or the dynamic `def` type. -* Syntax: Extends a subset of Java’s syntax to provide additional scripting language features. -* Optimizations: Designed specifically for Elasticsearch scripting. +## Language overview +Painless compiles directly to JVM bytecode, enabling native performance while maintaining security boundaries through fine-grained allowlists and a sandbox environment. The language implements a subset of Java syntax with {{es}} extensions, supporting field access patterns, datetime operations, and nested data manipulation across context-aware execution workloads. +Scripts execute within specific contexts that control available variables, allowed operations, and execution environments. Different contexts provide APIs for their specific use case. +## Core benefits +Painless provides three core benefits: +* **Security:** Fine-grained allowlists that prevent access to restricted Java APIs and enforce security layers through a sandbox environment and JVM-level protection +* **Performance:** Native compilation eliminates interpretation overhead and leverages JVM optimization, delivering native execution speed for production environments +* **Flexibility:** Scripting syntax and execution contexts span the {{es}} stack, from search scoring and data processing to operational processing +## Reference resources +The reference documentation includes the following resources: +* [**Language specification:**](/reference/scripting-languages/painless/painless-language-specification.md) syntax, operators, data types, and compilation semantics +* [**Contexts:**](/reference/scripting-languages/painless/painless-contexts.md) Execution environments, available variables, and context-specific APIs +* [**API examples:**](/reference/scripting-languages/painless/painless-api-examples.md) Examples of how to run the Painless execute API to build and test scripts. +* **Debugging & Troubleshooting:** Debugging techniques, common errors, and solutions +For step-by-step tutorials and real-world examples, refer to [How to write Painless scripts](docs-content://explore-analyze/scripting/modules-scripting-using.md) and [Painless script tutorials](docs-content://explore-analyze/scripting/common-script-uses.md) in the Explore and Analyze section. diff --git a/docs/reference/scripting-languages/painless/use-painless-scripts-in-runtime-fields.md b/docs/reference/scripting-languages/painless/use-painless-scripts-in-runtime-fields.md index 6508bd10c56c5..c40cd8ec9f81a 100644 --- a/docs/reference/scripting-languages/painless/use-painless-scripts-in-runtime-fields.md +++ b/docs/reference/scripting-languages/painless/use-painless-scripts-in-runtime-fields.md @@ -1,11 +1,14 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-runtime-fields.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- -# Use painless scripts in runtime fields [painless-runtime-fields] +# Use Painless scripts in runtime fields [painless-runtime-fields] A runtime field is a field that is evaluated at query time. When you define a runtime field, you can immediately use it in search requests, aggregations, filtering, and sorting. diff --git a/docs/reference/scripting-languages/painless/using-datetime-in-painless.md b/docs/reference/scripting-languages/painless/using-datetime-in-painless.md index 87f547277fbfb..1d9aa7ca77a1b 100644 --- a/docs/reference/scripting-languages/painless/using-datetime-in-painless.md +++ b/docs/reference/scripting-languages/painless/using-datetime-in-painless.md @@ -1,6 +1,9 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-datetime.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- @@ -17,805 +20,18 @@ Datetimes in Painless use the standard Java libraries and are available through * [java.time.temporal](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time-temporal.html) * [java.time.zone](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time-zone.html) - -## Datetime Representation [_datetime_representation] - -Datetimes in Painless are most commonly represented as a numeric value, a string value, or a complex value. - -numeric -: a datetime representation as a number from a starting offset called an epoch; in Painless this is typically a [long](/reference/scripting-languages/painless/painless-types.md#primitive-types) as milliseconds since an epoch of 1970-01-01 00:00:00 Zulu Time - -string -: a datetime representation as a sequence of characters defined by a standard format or a custom format; in Painless this is typically a [String](/reference/scripting-languages/painless/painless-types.md#string-type) of the standard format [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) - -complex -: a datetime representation as a complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) that abstracts away internal details of how the datetime is stored and often provides utilities for modification and comparison; in Painless this is typically a [ZonedDateTime](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZonedDateTime) - -Switching between different representations of datetimes is often necessary to achieve a script’s objective(s). A typical pattern in a script is to switch a numeric or string datetime to a complex datetime, modify or compare the complex datetime, and then switch it back to a numeric or string datetime for storage or to return a result. - - -## Datetime Parsing and Formatting [_datetime_parsing_and_formatting] - -Datetime parsing is a switch from a string datetime to a complex datetime, and datetime formatting is a switch from a complex datetime to a string datetime. - -A [DateTimeFormatter](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time-format.html#painless-api-reference-shared-DateTimeFormatter) is a complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) that defines the allowed sequence of characters for a string datetime. Datetime parsing and formatting often require a DateTimeFormatter. For more information about how to use a DateTimeFormatter see the [Java documentation](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html). - -### Datetime Parsing Examples [_datetime_parsing_examples] - -* parse from milliseconds - - ```painless - String milliSinceEpochString = "434931330000"; - long milliSinceEpoch = Long.parseLong(milliSinceEpochString); - Instant instant = Instant.ofEpochMilli(milliSinceEpoch); - ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); - ``` - -* parse from ISO 8601 - - ```painless - String datetime = '1983-10-13T22:15:30Z'; - ZonedDateTime zdt = ZonedDateTime.parse(datetime); <1> - ``` - - 1. Note the parse method uses ISO 8601 by default. - -* parse from RFC 1123 - - ```painless - String datetime = 'Thu, 13 Oct 1983 22:15:30 GMT'; - ZonedDateTime zdt = ZonedDateTime.parse(datetime, - DateTimeFormatter.RFC_1123_DATE_TIME); <1> - ``` - - 1. Note the use of a built-in DateTimeFormatter. - -* parse from a custom format - - ```painless - String datetime = 'custom y 1983 m 10 d 13 22:15:30 Z'; - DateTimeFormatter dtf = DateTimeFormatter.ofPattern( - "'custom' 'y' yyyy 'm' MM 'd' dd HH:mm:ss VV"); - ZonedDateTime zdt = ZonedDateTime.parse(datetime, dtf); <1> - ``` - - 1. Note the use of a custom DateTimeFormatter. - - - -### Datetime Formatting Examples [_datetime_formatting_examples] - -* format to ISO 8601 - - ```painless - ZonedDateTime zdt = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - String datetime = zdt.format(DateTimeFormatter.ISO_INSTANT); <1> - ``` - - 1. Note the use of a built-in DateTimeFormatter. - -* format to a custom format - - ```painless - ZonedDateTime zdt = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - DateTimeFormatter dtf = DateTimeFormatter.ofPattern( - "'date:' yyyy/MM/dd 'time:' HH:mm:ss"); - String datetime = zdt.format(dtf); <1> - ``` - - 1. Note the use of a custom DateTimeFormatter. - - - - -## Datetime Conversion [_datetime_conversion] - -Datetime conversion is a switch from a numeric datetime to a complex datetime and vice versa. - -### Datetime Conversion Examples [_datetime_conversion_examples] - -* convert from milliseconds - - ```painless - long milliSinceEpoch = 434931330000L; - Instant instant = Instant.ofEpochMilli(milliSinceEpoch); - ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); - ``` - -* convert to milliseconds - - ```painless - ZonedDateTime zdt = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - long milliSinceEpoch = zdt.toInstant().toEpochMilli(); - ``` - - - - -## Datetime Pieces [_datetime_pieces] - -Datetime representations often contain the data to extract individual datetime pieces such as year, hour, timezone, etc. Use individual pieces of a datetime to create a complex datetime, and use a complex datetime to extract individual pieces. - -### Datetime Pieces Examples [_datetime_pieces_examples] - -* create a complex datetime from pieces - - ```painless - int year = 1983; - int month = 10; - int day = 13; - int hour = 22; - int minutes = 15; - int seconds = 30; - int nanos = 0; - ZonedDateTime zdt = ZonedDateTime.of( - year, month, day, hour, minutes, seconds, nanos, ZoneId.of('Z')); - ``` - -* extract pieces from a complex datetime - - ```painless - ZonedDateTime zdt = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 100, ZoneId.of(tz)); - int year = zdt.getYear(); - int month = zdt.getMonthValue(); - int day = zdt.getDayOfMonth(); - int hour = zdt.getHour(); - int minutes = zdt.getMinute(); - int seconds = zdt.getSecond(); - int nanos = zdt.getNano(); - ``` - - - - -## Datetime Modification [_datetime_modification] - -Use either a numeric datetime or a complex datetime to do modification such as adding several seconds to a datetime or subtracting several days from a datetime. Use standard [numeric operators](/reference/scripting-languages/painless/painless-operators-numeric.md) to modify a numeric datetime. Use [methods](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZonedDateTime) (or fields) to modify a complex datetime. Note many complex datetimes are immutable so upon modification a new complex datetime is created that requires [assignment](/reference/scripting-languages/painless/painless-variables.md#variable-assignment) or immediate use. - -### Datetime Modification Examples [_datetime_modification_examples] - -* Subtract three seconds from a numeric datetime in milliseconds - - ```painless - long milliSinceEpoch = 434931330000L; - milliSinceEpoch = milliSinceEpoch - 1000L*3L; - ``` - -* Add three days to a complex datetime - - ```painless - ZonedDateTime zdt = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - ZonedDateTime updatedZdt = zdt.plusDays(3); - ``` - -* Subtract 125 minutes from a complex datetime - - ```painless - ZonedDateTime zdt = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - ZonedDateTime updatedZdt = zdt.minusMinutes(125); - ``` - -* Set the year on a complex datetime - - ```painless - ZonedDateTime zdt = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - ZonedDateTime updatedZdt = zdt.withYear(1976); - ``` - - - - -## Datetime Difference (Elapsed Time) [_datetime_difference_elapsed_time] - -Use either two numeric datetimes or two complex datetimes to calculate the difference (elapsed time) between two different datetimes. Use [subtraction](/reference/scripting-languages/painless/painless-operators-numeric.md#subtraction-operator) to calculate the difference between two numeric datetimes of the same time unit such as milliseconds. For complex datetimes there is often a method or another complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) available to calculate the difference. Use [ChronoUnit](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time-temporal.html#painless-api-reference-shared-ChronoUnit) to calculate the difference between two complex datetimes if supported. - -### Datetime Difference Examples [_datetime_difference_examples] - -* Difference in milliseconds between two numeric datetimes - - ```painless - long startTimestamp = 434931327000L; - long endTimestamp = 434931330000L; - long differenceInMillis = endTimestamp - startTimestamp; - ``` - -* Difference in milliseconds between two complex datetimes - - ```painless - ZonedDateTime zdt1 = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 11000000, ZoneId.of('Z')); - ZonedDateTime zdt2 = - ZonedDateTime.of(1983, 10, 13, 22, 15, 35, 0, ZoneId.of('Z')); - long differenceInMillis = ChronoUnit.MILLIS.between(zdt1, zdt2); - ``` - -* Difference in days between two complex datetimes - - ```painless - ZonedDateTime zdt1 = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 11000000, ZoneId.of('Z')); - ZonedDateTime zdt2 = - ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); - long differenceInDays = ChronoUnit.DAYS.between(zdt1, zdt2); - ``` - - - - -## Datetime Comparison [_datetime_comparison] - -Use either two numeric datetimes or two complex datetimes to do a datetime comparison. Use standard [comparison operators](/reference/scripting-languages/painless/painless-operators-boolean.md) to compare two numeric datetimes of the same time unit such as milliseconds. For complex datetimes there is often a method or another complex type ([object](/reference/scripting-languages/painless/painless-types.md#reference-types)) available to do the comparison. - -### Datetime Comparison Examples [_datetime_comparison_examples] - -* Greater than comparison of two numeric datetimes in milliseconds - - ```painless - long timestamp1 = 434931327000L; - long timestamp2 = 434931330000L; - - if (timestamp1 > timestamp2) { - // handle condition - } - ``` - -* Equality comparison of two complex datetimes - - ```painless - ZonedDateTime zdt1 = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - ZonedDateTime zdt2 = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - - if (zdt1.equals(zdt2)) { - // handle condition - } - ``` - -* Less than comparison of two complex datetimes - - ```painless - ZonedDateTime zdt1 = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - ZonedDateTime zdt2 = - ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); - - if (zdt1.isBefore(zdt2)) { - // handle condition - } - ``` - -* Greater than comparison of two complex datetimes - - ```painless - ZonedDateTime zdt1 = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - ZonedDateTime zdt2 = - ZonedDateTime.of(1983, 10, 17, 22, 15, 35, 0, ZoneId.of('Z')); - - if (zdt1.isAfter(zdt2)) { - // handle condition - } - ``` - - - - -## Datetime Zone [_datetime_zone] - -Both string datetimes and complex datetimes have a timezone with a default of `UTC`. Numeric datetimes do not have enough explicit information to have a timezone, so `UTC` is always assumed. Use [methods](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZonedDateTime) (or fields) in conjunction with a [ZoneId](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html#painless-api-reference-shared-ZoneId) to change the timezone for a complex datetime. Parse a string datetime into a complex datetime to change the timezone, and then format the complex datetime back into a desired string datetime. Note many complex datetimes are immutable so upon modification a new complex datetime is created that requires [assignment](/reference/scripting-languages/painless/painless-variables.md#variable-assignment) or immediate use. - -### Datetime Zone Examples [_datetime_zone_examples] - -* Modify the timezone for a complex datetime - - ```painless - ZonedDateTime utc = - ZonedDateTime.of(1983, 10, 13, 22, 15, 30, 0, ZoneId.of('Z')); - ZonedDateTime pst = utc.withZoneSameInstant(ZoneId.of('America/Los_Angeles')); - ``` - -* Modify the timezone for a string datetime - - ```painless - String gmtString = 'Thu, 13 Oct 1983 22:15:30 GMT'; - ZonedDateTime gmtZdt = ZonedDateTime.parse(gmtString, - DateTimeFormatter.RFC_1123_DATE_TIME); <1> - ZonedDateTime pstZdt = - gmtZdt.withZoneSameInstant(ZoneId.of('America/Los_Angeles')); - String pstString = pstZdt.format(DateTimeFormatter.RFC_1123_DATE_TIME); - ``` - - 1. Note the use of a built-in DateTimeFormatter. - - - - -## Datetime Input [_datetime_input] - -There are several common ways datetimes are used as input for a script determined by the [Painless context](/reference/scripting-languages/painless/painless-contexts.md). Typically, datetime input will be accessed from parameters specified by the user, from an original source document, or from an indexed document. - -### Datetime Input From User Parameters [_datetime_input_from_user_parameters] - -Use the [params section](docs-content://explore-analyze/scripting/modules-scripting-using.md) during script specification to pass in a numeric datetime or string datetime as a script input. Access to user-defined parameters within a script is dependent on the Painless context, though, the parameters are most commonly accessible through an input called `params`. - -**Examples** - -* Parse a numeric datetime from user parameters to a complex datetime - - * Input: - - ```JSON - ... - "script": { - ... - "params": { - "input_datetime": 434931327000 - } - } - ... - ``` - - * Script: - - ```painless - long inputDateTime = params['input_datetime']; - Instant instant = Instant.ofEpochMilli(inputDateTime); - ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); - ``` - -* Parse a string datetime from user parameters to a complex datetime - - * Input: - - ```JSON - ... - "script": { - ... - "params": { - "input_datetime": "custom y 1983 m 10 d 13 22:15:30 Z" - } - } - ... - ``` - - * Script: - - ```painless - String datetime = params['input_datetime']; - DateTimeFormatter dtf = DateTimeFormatter.ofPattern( - "'custom' 'y' yyyy 'm' MM 'd' dd HH:mm:ss VV"); - ZonedDateTime zdt = ZonedDateTime.parse(datetime, dtf); <1> - ``` - - 1. Note the use of a custom DateTimeFormatter. - - - -### Datetime Input From a Source Document [_datetime_input_from_a_source_document] - -Use an original [source](/reference/elasticsearch/mapping-reference/mapping-source-field.md) document as a script input to access a numeric datetime or string datetime for a specific field within that document. Access to an original source document within a script is dependent on the Painless context and is not always available. An original source document is most commonly accessible through an input called `ctx['_source']` or `params['_source']`. - -**Examples** - -* Parse a numeric datetime from a sourced document to a complex datetime - - * Input: - - ```JSON - { - ... - "input_datetime": 434931327000 - ... - } - ``` - - * Script: - - ```painless - long inputDateTime = ctx['_source']['input_datetime']; <1> - Instant instant = Instant.ofEpochMilli(inputDateTime); - ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Z')); - ``` - - 1. Note access to `_source` is dependent on the Painless context. - -* Parse a string datetime from a sourced document to a complex datetime - - * Input: - - ```JSON - { - ... - "input_datetime": "1983-10-13T22:15:30Z" - ... - } - ``` - - * Script: - - ```painless - String datetime = params['_source']['input_datetime']; <1> - ZonedDateTime zdt = ZonedDateTime.parse(datetime); <2> - ``` - - 1. Note access to `_source` is dependent on the Painless context. - 2. Note the parse method uses ISO 8601 by default. - - - -### Datetime Input From an Indexed Document [_datetime_input_from_an_indexed_document] - -Use an indexed document as a script input to access a complex datetime for a specific field within that document where the field is mapped as a [standard date](/reference/elasticsearch/mapping-reference/date.md) or a [nanosecond date](/reference/elasticsearch/mapping-reference/date_nanos.md). Numeric datetime fields mapped as [numeric](/reference/elasticsearch/mapping-reference/number.md) and string datetime fields mapped as [keyword](/reference/elasticsearch/mapping-reference/keyword.md) are accessible through an indexed document as well. Access to an indexed document within a script is dependent on the Painless context and is not always available. An indexed document is most commonly accessible through an input called `doc`. - -**Examples** - -* Format a complex datetime from an indexed document to a string datetime - - * Assumptions: - - * The field `input_datetime` exists in all indexes as part of the query - * All indexed documents contain the field `input_datetime` - - * Mappings: - - ```JSON - { - "mappings": { - ... - "properties": { - ... - "input_datetime": { - "type": "date" - } - ... - } - ... - } - } - ``` - - * Script: - - ```painless - ZonedDateTime input = doc['input_datetime'].value; - String output = input.format(DateTimeFormatter.ISO_INSTANT); <1> - ``` - - 1. Note the use of a built-in DateTimeFormatter. - -* Find the difference between two complex datetimes from an indexed document - - * Assumptions: - - * The fields `start` and `end` may **not** exist in all indexes as part of the query - * The fields `start` and `end` may **not** have values in all indexed documents - - * Mappings: - - ```JSON - { - "mappings": { - ... - "properties": { - ... - "start": { - "type": "date" - }, - "end": { - "type": "date" - } - ... - } - ... - } - } - ``` - - * Script: - - ```painless - if (doc.containsKey('start') && doc.containsKey('end')) { <1> - - if (doc['start'].size() > 0 && doc['end'].size() > 0) { <2> - - ZonedDateTime start = doc['start'].value; - ZonedDateTime end = doc['end'].value; - long differenceInMillis = ChronoUnit.MILLIS.between(start, end); - - // handle difference in times - } else { - // handle fields without values - } - } else { - // handle index with missing fields - } - ``` - - 1. When a query’s results span multiple indexes, some indexes may not contain a specific field. Use the `containsKey` method call on the `doc` input to ensure a field exists as part of the index for the current document. - 2. Some fields within a document may have no values. Use the `size` method call on a field within the `doc` input to ensure that field has at least one value for the current document. - - - - -## Datetime Now [_datetime_now] - -Under most Painless contexts the current datetime, `now`, is not supported. There are two primary reasons for this. The first is that scripts are often run once per document, so each time the script is run a different `now` is returned. The second is that scripts are often run in a distributed fashion without a way to appropriately synchronize `now`. Instead, pass in a user-defined parameter with either a string datetime or numeric datetime for `now`. A numeric datetime is preferred as there is no need to parse it for comparison. - -### Datetime Now Examples [_datetime_now_examples] - -* Use a numeric datetime as `now` - - * Assumptions: - - * The field `input_datetime` exists in all indexes as part of the query - * All indexed documents contain the field `input_datetime` - - * Mappings: - - ```JSON - { - "mappings": { - ... - "properties": { - ... - "input_datetime": { - "type": "date" - } - ... - } - ... - } - } - ``` - % NOTCONSOLE - - * Input: - - ```JSON - ... - "script": { - ... - "params": { - "now": - } - } - ... - ``` - % NOTCONSOLE - - * Script: - - ```painless - long now = params['now']; - ZonedDateTime inputDateTime = doc['input_datetime']; - long millisDateTime = inputDateTime.toInstant().toEpochMilli(); - long elapsedTime = now - millisDateTime; - ``` - % NOTCONSOLE - -* Use a string datetime as `now` - - * Assumptions: - - * The field `input_datetime` exists in all indexes as part of the query - * All indexed documents contain the field `input_datetime` - - * Mappings: - - ```JSON - { - "mappings": { - ... - "properties": { - ... - "input_datetime": { - "type": "date" - } - ... - } - ... - } - } - ``` - % NOTCONSOLE - - * Input: - - ```JSON - ... - "script": { - ... - "params": { - "now": "" - } - } - ... - ``` - % NOTCONSOLE - - * Script: - - ```painless - String nowString = params['now']; - ZonedDateTime nowZdt = ZonedDateTime.parse(nowString); <1> - long now = ZonedDateTime.toInstant().toEpochMilli(); - ZonedDateTime inputDateTime = doc['input_datetime']; - long millisDateTime = zdt.toInstant().toEpochMilli(); - long elapsedTime = now - millisDateTime; - ``` - % NOTCONSOLE - - 1. Note this parses the same string datetime every time the script runs. Use a numeric datetime to avoid a significant performance hit. - - - - -## Datetime Examples in Contexts [_datetime_examples_in_contexts] - -### Load the Example Data [_load_the_example_data] - -Run the following curl commands to load the data necessary for the context examples into an Elasticsearch cluster: - -1. Create [mappings](docs-content://manage-data/data-store/mapping.md) for the sample data. - - ```console - PUT /messages - { - "mappings": { - "properties": { - "priority": { - "type": "integer" - }, - "datetime": { - "type": "date" - }, - "message": { - "type": "text" - } - } - } - } - ``` - -2. Load the sample data. - - ```console - POST /_bulk - { "index" : { "_index" : "messages", "_id" : "1" } } - { "priority": 1, "datetime": "2019-07-17T12:13:14Z", "message": "m1" } - { "index" : { "_index" : "messages", "_id" : "2" } } - { "priority": 1, "datetime": "2019-07-24T01:14:59Z", "message": "m2" } - { "index" : { "_index" : "messages", "_id" : "3" } } - { "priority": 2, "datetime": "1983-10-14T00:36:42Z", "message": "m3" } - { "index" : { "_index" : "messages", "_id" : "4" } } - { "priority": 3, "datetime": "1983-10-10T02:15:15Z", "message": "m4" } - { "index" : { "_index" : "messages", "_id" : "5" } } - { "priority": 3, "datetime": "1983-10-10T17:18:19Z", "message": "m5" } - { "index" : { "_index" : "messages", "_id" : "6" } } - { "priority": 1, "datetime": "2019-08-03T17:19:31Z", "message": "m6" } - { "index" : { "_index" : "messages", "_id" : "7" } } - { "priority": 3, "datetime": "2019-08-04T17:20:00Z", "message": "m7" } - { "index" : { "_index" : "messages", "_id" : "8" } } - { "priority": 2, "datetime": "2019-08-04T18:01:01Z", "message": "m8" } - { "index" : { "_index" : "messages", "_id" : "9" } } - { "priority": 3, "datetime": "1983-10-10T19:00:45Z", "message": "m9" } - { "index" : { "_index" : "messages", "_id" : "10" } } - { "priority": 2, "datetime": "2019-07-23T23:39:54Z", "message": "m10" } - ``` - % TEST[continued] - - - -### Day-of-the-Week Bucket Aggregation Example [_day_of_the_week_bucket_aggregation_example] - -The following example uses a [terms aggregation](/reference/aggregations/search-aggregations-bucket-terms-aggregation.md#search-aggregations-bucket-terms-aggregation-script) as part of the [bucket script aggregation context](/reference/scripting-languages/painless/painless-bucket-script-agg-context.md) to display the number of messages from each day-of-the-week. - -```console -GET /messages/_search?pretty=true -{ - "aggs": { - "day-of-week-count": { - "terms": { - "script": "return doc[\"datetime\"].value.getDayOfWeekEnum();" - } - } - } -} -``` -% TEST[continued] - -### Morning/Evening Bucket Aggregation Example [_morningevening_bucket_aggregation_example] - -The following example uses a [terms aggregation](/reference/aggregations/search-aggregations-bucket-terms-aggregation.md#search-aggregations-bucket-terms-aggregation-script) as part of the [bucket script aggregation context](/reference/scripting-languages/painless/painless-bucket-script-agg-context.md) to display the number of messages received in the morning versus the evening. - -```console -GET /messages/_search?pretty=true -{ - "aggs": { - "am-pm-count": { - "terms": { - "script": "return doc[\"datetime\"].value.getHour() < 12 ? \"AM\" : \"PM\";" - } - } - } -} -``` -% TEST[continued] - -### Age of a Message Script Field Example [_age_of_a_message_script_field_example] - -The following example uses a [script field](/reference/elasticsearch/rest-apis/retrieve-selected-fields.md#script-fields) as part of the [field context](/reference/scripting-languages/painless/painless-field-context.md) to display the elapsed time between "now" and when a message was received. - -```console -GET /_search?pretty=true -{ - "query": { - "match_all": {} - }, - "script_fields": { - "message_age": { - "script": { - "source": "ZonedDateTime now = ZonedDateTime.ofInstant(Instant.ofEpochMilli(params[\"now\"]), ZoneId.of(\"Z\")); ZonedDateTime mdt = doc[\"datetime\"].value; String age; long years = mdt.until(now, ChronoUnit.YEARS); age = years + \"Y \"; mdt = mdt.plusYears(years); long months = mdt.until(now, ChronoUnit.MONTHS); age += months + \"M \"; mdt = mdt.plusMonths(months); long days = mdt.until(now, ChronoUnit.DAYS); age += days + \"D \"; mdt = mdt.plusDays(days); long hours = mdt.until(now, ChronoUnit.HOURS); age += hours + \"h \"; mdt = mdt.plusHours(hours); long minutes = mdt.until(now, ChronoUnit.MINUTES); age += minutes + \"m \"; mdt = mdt.plusMinutes(minutes); long seconds = mdt.until(now, ChronoUnit.SECONDS); age += hours + \"s\"; return age;", - "params": { - "now": 1574005645830 - } - } - } - } -} -``` -% TEST[continued] - -The following shows the script broken into multiple lines: - -```painless -ZonedDateTime now = ZonedDateTime.ofInstant( - Instant.ofEpochMilli(params['now']), ZoneId.of('Z')); <1> -ZonedDateTime mdt = doc['datetime'].value; <2> - -String age; - -long years = mdt.until(now, ChronoUnit.YEARS); <3> -age = years + 'Y '; <4> -mdt = mdt.plusYears(years); <5> - -long months = mdt.until(now, ChronoUnit.MONTHS); -age += months + 'M '; -mdt = mdt.plusMonths(months); - -long days = mdt.until(now, ChronoUnit.DAYS); -age += days + 'D '; -mdt = mdt.plusDays(days); - -long hours = mdt.until(now, ChronoUnit.HOURS); -age += hours + 'h '; -mdt = mdt.plusHours(hours); - -long minutes = mdt.until(now, ChronoUnit.MINUTES); -age += minutes + 'm '; -mdt = mdt.plusMinutes(minutes); - -long seconds = mdt.until(now, ChronoUnit.SECONDS); -age += hours + 's'; - -return age; <6> -``` - -1. Parse the datetime "now" as input from the user-defined params. -2. Store the datetime the message was received as a `ZonedDateTime`. -3. Find the difference in years between "now" and the datetime the message was received. -4. Add the difference in years later returned in the format `Y ...` for the age of a message. -5. Add the years so only the remainder of the months, days, etc. remain as the difference between "now" and the datetime the message was received. Repeat this pattern until the desired granularity is reached (seconds in this example). -6. Return the age of the message in the format `Y M D h m s `. - - - - +## Datetime operations + +Refer to the following pages to learn about performing datetime operations in Painless. + +- [](/reference/scripting-languages/painless/painless-datetime-representation.md) +- [](/reference/scripting-languages/painless/painless-datetime-parsing-and-formatting.md) +- [](/reference/scripting-languages/painless/painless-datetime-conversion.md) +- [](/reference/scripting-languages/painless/painless-datetime-pieces.md) +- [](/reference/scripting-languages/painless/painless-datetime-modification.md) +- [](/reference/scripting-languages/painless/painless-datetime-difference.md) +- [](/reference/scripting-languages/painless/painless-datetime-comparison.md) +- [](/reference/scripting-languages/painless/painless-datetime-zone.md) +- [](/reference/scripting-languages/painless/painless-datetime-input.md) +- [](/reference/scripting-languages/painless/painless-datetime-now.md) +- [](/reference/scripting-languages/painless/painless-datetime-examples-in-contexts.md) diff --git a/docs/reference/scripting-languages/painless/using-ingest-processors-in-painless.md b/docs/reference/scripting-languages/painless/using-ingest-processors-in-painless.md index 6062a943ade3c..735a2bd529b92 100644 --- a/docs/reference/scripting-languages/painless/using-ingest-processors-in-painless.md +++ b/docs/reference/scripting-languages/painless/using-ingest-processors-in-painless.md @@ -1,13 +1,35 @@ --- mapped_pages: - https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-ingest.html +applies_to: + stack: ga + serverless: ga products: - id: painless --- # Using ingest processors in Painless [painless-ingest] -Some [ingest processors](/reference/enrich-processor/index.md) expose behavior through Painless methods that can be called in Painless scripts that execute in ingest pipelines. +Painless scripts in [ingest pipelines](https://www.elastic.co/docs/manage-data/ingest/transform-enrich/ingest-pipelines) can access certain [ingest processor](https://www.elastic.co/docs/reference/scripting-languages/painless/painless-ingest-processor-context) functionality through the `Processors` namespace, enabling custom logic while leveraging {{es}} built-in transformations. Scripts execute within the `ctx` context to modify documents during ingestion. + +Only a subset of ingest processors expose methods in the `Processors` namespace for use in Painless scripts. The following ingest processors expose methods in Painless: + +* Bytes +* Lowercase +* Uppercase +* Json + + +## When to choose each approach + + +| Method | Use for | Pros | Cons | +| :---- | :---- | :---- | :---- | +| [script processor](/reference/enrich-processor/script-processor.md) (Painless) | * complex logic
* conditional operations
* multi-field validation | * full control
* custom business logic
* cross-field operations | * performance overhead
* complexity | +| [ingest processor](docs-content://manage-data/ingest/transform-enrich/ingest-pipelines.md) | * common transformations
* standard operations | * optimized performance
* built-in validation
* simple configuration | * limited logic
* single-field focus | +| [runtime fields](docs-content://manage-data/data-store/mapping/runtime-fields.md) | * query-time calculations
* schema flexibility | * no reindexing required * dynamic computation during queries | * query-time performance cost
* read-only operations
* not used in ingest pipeline. | + +**Performance considerations:** Script processors can impact pipeline performance. Prefer ingest processors for simple transformations. ## Method usage [_method_usage] diff --git a/docs/reference/scripting-languages/toc.yml b/docs/reference/scripting-languages/toc.yml index 5fa5f6e78ca66..3124d79148f8b 100644 --- a/docs/reference/scripting-languages/toc.yml +++ b/docs/reference/scripting-languages/toc.yml @@ -3,8 +3,26 @@ toc: - file: painless/painless.md children: - file: painless/brief-painless-walkthrough.md + children: + - file: painless/painless-walkthrough-access-doc-values.md + - file: painless/painless-walkthrough-missing-keys-or-values.md + - file: painless/painless-walkthrough-updating-fields.md + - file: painless/painless-walkthrough-dates.md + - file: painless/painless-walkthrough-regular-expressions.md - file: painless/use-painless-scripts-in-runtime-fields.md - file: painless/using-datetime-in-painless.md + children: + - file: painless/painless-datetime-representation.md + - file: painless/painless-datetime-parsing-and-formatting.md + - file: painless/painless-datetime-conversion.md + - file: painless/painless-datetime-pieces.md + - file: painless/painless-datetime-modification.md + - file: painless/painless-datetime-difference.md + - file: painless/painless-datetime-comparison.md + - file: painless/painless-datetime-zone.md + - file: painless/painless-datetime-input.md + - file: painless/painless-datetime-now.md + - file: painless/painless-datetime-examples-in-contexts.md - file: painless/how-painless-dispatches-function.md - file: painless/painless-debugging.md - file: painless/painless-api-examples.md