# What exactly does painless do under the hood of painless script's Array contains method?

**URL:** <https://discuss.elastic.co/t/what-exactly-does-painless-do-under-the-hood-of-painless-scripts-array-contains-method/201592>\
**Category:** Elasticsearch\
**Tags:** painless\
**Created:** [September 30, 2019, 8:15am UTC](https://discuss.elastic.co/t/what-exactly-does-painless-do-under-the-hood-of-painless-scripts-array-contains-method/201592 "2019-09-30T08:15:02Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![wusongchao](https://avatars.discourse-cdn.com/v4/letter/w/4af34b/32.png) [@wusongchao](https://discuss.elastic.co/u/wusongchao)\
**Post date:** [September 30, 2019, 8:15am UTC](https://discuss.elastic.co/t/what-exactly-does-painless-do-under-the-hood-of-painless-scripts-array-contains-method/201592/1 "2019-09-30T08:15:02Z")

</div>

Suppose that i have a doc mapping field like below

```
{
    "template": {
        "mappings":{
            "template":{
                "properties": {
                    "sth": {
                        "type": "long"
                    }
                }
            }
        }
    }
}

```

The field `sth` is the type of array.

I want to check whether the field `sth` contains a value or not, so i write painless script like  
`doc['sth'].values.contains(1)`

It failed, and i read this [article](https://discuss.elastic.co/t/painless-collection-contains-not-working/178944), understand why it fail, since the i must pass a  
`Long` to the `contains` method, so i change my painless script to  
`doc['sth'].values.contains(1L)`

It works, but some further experiment exhaust me more.

The script

`doc['sth'].values[0] == 1` and `doc['sth'].values[0] == 1L`

both can work, ok, i read the [painless document](https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators-boolean.html#equality-equals-operator), understanding that the integer type will be promoted as long type.

But `doc['sth'].values[0] == Integer.valueOf(156)` also can work, however, according to the document

> If a comparison is made between a primitive type value and a reference type value.

Should it raise an error? Or automatically unboxing/boxing happens somewhere?

Nevertheless, i wrote script  
`doc['sth'].values[0] instanceof Long` and `doc['sth'].values[0] instanceof long`,  
both can work and return true.

Is painless array type store the primitive type, or the boxing reference type?

Finally, comes to the topic question, what exactly does painless do under the hood of painless script's Array contains method.

It's just my irresponsible guess

Is `Array::contains` have a signature like `contains(Object o)`, and use `==` to  
compare the parameter with its storage?

But if its the truth, why does `doc['sth'].values[0] == 1`  
success but `doc['sth'].values[0] == 1` fail?

Here's the relation link of this question in stackoverflow:

> <https://stackoverflow.com/questions/58163879/what-exactly-does-painless-do-under-the-hood-of-painless-scripts-array-contains>

---

<div class="post-metadata">

**Author:** ![Jack\_Conradson](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/jack_conradson/32/47236_2.png) [@Jack\_Conradson](https://discuss.elastic.co/u/Jack_Conradson)\
**Post date:** [October 7, 2019, 4:39pm UTC](https://discuss.elastic.co/t/what-exactly-does-painless-do-under-the-hood-of-painless-scripts-array-contains-method/201592/2 "2019-10-07T16:39:56Z")

</div>

Hi wusongchao,

Lot's to break down in this question -

To start `doc` is a specialized `java.util.Map`. The brackets are a shortcut to call the `get` method. The signature for this method in Painless is `def get(def)` translated to Java is `Object get(Object)`. Painless often uses `def` in place of Java generics. (This is due to type erasure, so when we resolve the `def` type at run-time we don't have generic information anyway.) From there we call a shortcut, `values`, which is actually a method on `doc` called `getValues`. Painless does this translation under-the-hood. The `getValues` method returns a List of `def` values representing `Long` values in this specific case. The contains method is actually a standard Java `List` call. So, when you pass in `1` to `contains`, it is boxed into an Integer. An `Integer` in Java is never equal to a `Long` since they aren't the same type so this will return false. When a `Long` is passed into `contains` it returns true instead as they are the same type as the values in the `List`.

One note about `doc['sth'].values[0]` is the `[0]` is actually a shortcut for a `List` `get` method in Painless. Another under-the-hood translation. The `get` method for `List` returns a `def` value, so all the comparisons made in the examples given are against `def` which changes the behavior relative to primitive/reference types.

What happens with `doc['sth'].values[0] == 1` is the following:

1. the left-hand side is a `def` type and the right-hand side is an `int` type
2. the right-hand side is promoted to `def`
3. at run-time a comparison is made between the left-hand side and the right-hand side where `def` is always considered to be a primitive if it's a numeric type, so we now are comparing a `long` and an `int`
4. the `int` is promoted to `long`
5. the comparison of `1L` to `1L` is made and returns `true`

This is the same for `doc['sth'].values[0] == 1L` except there's no need to promote the right-hand side at run-time since it's already a `long`.

The `doc['sth'].values[0] == Integer.valueOf(156)` is similar and breaks down into the following:

1. the left-hand side is a `def` type and the right-hand side is an `Integer` type
2. the right-hand side is promoted to `def`
3. at run-time a comparison is made between the left-hand side and the right-hand side where `def` is always considered to be a primitive if it's a numeric type, so we now are comparing a `long` and an `int` again (in this case, the `Integer` is actually unboxed at run-time - the behavior here is admittedly a bit confusing; however, we do not currently track what a def type may explicitly be, so we don't currently have enough information at run-time to make a decision about whether or not a `def` is a boxed/unboxed type, and it made more sense for the majority of math operations to always assume unboxed)
4. the `int` is promoted to `long`
5. the comparison of `1L` to `1L` is made and returns `true`

Hopefully, this provides some more clarity on what is happening. Please let me know if you have any follow-up questions or if anything here needs more information.

Regards,  
Jack

---

<div class="post-metadata">

**Author:** ![wusongchao](https://avatars.discourse-cdn.com/v4/letter/w/4af34b/32.png) [@wusongchao](https://discuss.elastic.co/u/wusongchao)\
**Post date:** [October 8, 2019, 2:26am UTC](https://discuss.elastic.co/t/what-exactly-does-painless-do-under-the-hood-of-painless-scripts-array-contains-method/201592/3 "2019-10-08T02:26:53Z")

</div>

Special thanks to you, it did explain clearly.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/elastic/original/3X/1/a/1ac57faf039f6b580b3f104ef42a2a89e41014de.png) [@system](https://discuss.elastic.co/u/system)\
**Post date:** [November 5, 2019, 2:30am UTC](https://discuss.elastic.co/t/what-exactly-does-painless-do-under-the-hood-of-painless-scripts-array-contains-method/201592/4 "2019-11-05T02:30:05Z")

</div>

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.
