Browse Source

Add scripted_metric agg context to unsigned_long (#64422)

Also enhance documentation to provide more examples
how unsigned_long field should be used in scripts

Closes #64347
Mayya Sharipova 3 years ago
parent
commit
bf3208b028

+ 6 - 0
docs/changelog/64422.yaml

@@ -0,0 +1,6 @@
+pr: 64422
+summary: Add `scripted_metric` agg context to `unsigned_long`
+area: Search
+type: enhancement
+issues:
+ - 64347

+ 63 - 5
docs/reference/mapping/types/unsigned_long.asciidoc

@@ -71,7 +71,7 @@ GET /my_index/_search
     "query": {
         "range" : {
             "my_counter" : {
-                "gte" : "9223372036854775808.5",
+                "gte" : "9223372036854775808",
                 "lte" : "18446744073709551615"
             }
         }
@@ -80,7 +80,7 @@ GET /my_index/_search
 --------------------------------
 //TEST[continued]
 
-
+==== Sort values
 For queries with sort on an `unsigned_long` field,
 for a particular document {es} returns a sort value of the type `long`
 if the value of this document is within the range of long values,
@@ -102,9 +102,6 @@ GET /my_index/_search
 //TEST[continued]
 
 
-==== Unsigned long in scripts
-Currently unsigned_long is not supported in scripts.
-
 ==== Stored fields
 A stored field of `unsigned_long` is stored and returned as `String`.
 
@@ -113,6 +110,67 @@ For `terms` aggregations, similarly to sort values, `Long` or
 `BigInteger` values are used. For other aggregations,
 values are converted to the `double` type.
 
+==== Script values
+By default, script values of an `unsigned_long` field are returned as
+Java signed `Long`, which means that values that are greater than
+`Long.MAX_VALUE` are shown as negative values. You can use
+`Long.compareUnsigned(long, long)`, `Long.divideUnsigned(long, long)`
+and `Long.remainderUnsigned(long, long)` to correctly work with
+these values.
+
+For example, the script below returns a value of the counter
+divided by 10.
+
+[source,console]
+--------------------------------
+GET /my_index/_search
+{
+    "query": {
+        "match_all" : {}
+    },
+    "script_fields": {
+        "count10" : {
+          "script": {
+            "source": "Long.divideUnsigned(doc['my_counter'].value, 10)"
+          }
+        }
+    }
+}
+--------------------------------
+//TEST[continued]
+
+
+Alternatively, you can treat the unsigned long type as `BigInteger`
+in your scripts by using the field API. For example, this script
+treats `my_counter` as `BigInteger` with a default value of `BigInteger.ZERO`:
+
+[source,js]
+--------------------------------------------------
+"script": {
+    "source": "field('my_counter').asBigInteger(BigInteger.ZERO)"
+}
+--------------------------------------------------
+// NOTCONSOLE
+
+For scripts that need to return float or double values, you
+can further convert `BigInteger` values to double or float:
+
+[source,console]
+--------------------------------
+GET /my_index/_search
+{
+    "query": {
+        "script_score": {
+          "query": {"match_all": {}},
+          "script": {
+            "source": "field('my_counter').asBigInteger(BigInteger.ZERO).floatValue()"
+          }
+        }
+    }
+}
+--------------------------------
+//TEST[continued]
+
 ==== Queries with mixed numeric types
 
 Searches with mixed numeric types one of which is `unsigned_long` are

+ 14 - 15
x-pack/plugin/mapper-unsigned-long/src/main/java/org/elasticsearch/xpack/unsignedlong/DocValuesWhitelistExtension.java

@@ -17,12 +17,14 @@ import org.elasticsearch.script.FilterScript;
 import org.elasticsearch.script.NumberSortScript;
 import org.elasticsearch.script.ScoreScript;
 import org.elasticsearch.script.ScriptContext;
+import org.elasticsearch.script.ScriptedMetricAggContexts;
 import org.elasticsearch.script.StringSortScript;
 
 import java.util.List;
 import java.util.Map;
 
 import static java.util.Collections.singletonList;
+import static java.util.Map.entry;
 
 public class DocValuesWhitelistExtension implements PainlessExtension {
 
@@ -34,21 +36,18 @@ public class DocValuesWhitelistExtension implements PainlessExtension {
     @Override
     public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
         List<Whitelist> whitelist = singletonList(WHITELIST);
-        return Map.of(
-            FieldScript.CONTEXT,
-            whitelist,
-            ScoreScript.CONTEXT,
-            whitelist,
-            FilterScript.CONTEXT,
-            whitelist,
-            AggregationScript.CONTEXT,
-            whitelist,
-            NumberSortScript.CONTEXT,
-            whitelist,
-            StringSortScript.CONTEXT,
-            whitelist,
-            BucketAggregationSelectorScript.CONTEXT,
-            whitelist
+        return Map.ofEntries(
+            entry(FieldScript.CONTEXT, whitelist),
+            entry(ScoreScript.CONTEXT, whitelist),
+            entry(FilterScript.CONTEXT, whitelist),
+            entry(AggregationScript.CONTEXT, whitelist),
+            entry(NumberSortScript.CONTEXT, whitelist),
+            entry(StringSortScript.CONTEXT, whitelist),
+            entry(BucketAggregationSelectorScript.CONTEXT, whitelist),
+            entry(ScriptedMetricAggContexts.InitScript.CONTEXT, whitelist),
+            entry(ScriptedMetricAggContexts.MapScript.CONTEXT, whitelist),
+            entry(ScriptedMetricAggContexts.CombineScript.CONTEXT, whitelist),
+            entry(ScriptedMetricAggContexts.ReduceScript.CONTEXT, whitelist)
         );
     }
 }

+ 36 - 0
x-pack/plugin/mapper-unsigned-long/src/yamlRestTest/resources/rest-api-spec/test/50_script_values.yml

@@ -146,6 +146,8 @@ setup:
                   script:
                     source: "field('ul').isEmpty() == false"
   - match: { hits.total.value: 5 }
+
+
   - do:
       search:
         index: test1
@@ -185,6 +187,7 @@ setup:
                 source: "field('ul').asBigInteger(BigInteger.valueOf(Long.parseUnsignedLong('18446744073709551614'))).doubleValue()"
 
   - match: { hits.total.value: 5 }
+
   - do:
       search:
         index: test1
@@ -196,3 +199,36 @@ setup:
                 source: "Math.abs(doc['ul'].value)"
 
   - match: { hits.total.value: 5 }
+
+---
+"Scripted Metric":
+  - skip:
+      version: " - 8.0.99"
+      reason: "context for scripted metric was added in 8.1"
+
+  - do:
+      search:
+        index: test1
+        body:
+          query:
+            term:
+              ul: 18446744073709551615
+          aggs:
+            sqrt:
+              scripted_metric:
+                init_script: |
+                  state.sqrd = []
+                map_script: |
+                  double v = field('ul').asBigInteger(BigInteger.ZERO).doubleValue();
+                  state.sqrd.add(v * v)
+                combine_script: |
+                  state.sqrd
+                reduce_script: |
+                  def sum = 0.0;
+                  for (s in states) {
+                    for (v in s) {
+                      sum += v;
+                    }
+                  }
+                  return sum
+  - match: { aggregations.sqrt.value: 3.4028236692093846E38 }