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

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, 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, 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:

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

Special thanks to you, it did explain clearly.

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