Skip to content

Commit bf3208b

Browse files
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
1 parent be09f8b commit bf3208b

File tree

4 files changed

+119
-20
lines changed

4 files changed

+119
-20
lines changed

docs/changelog/64422.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 64422
2+
summary: Add `scripted_metric` agg context to `unsigned_long`
3+
area: Search
4+
type: enhancement
5+
issues:
6+
- 64347

docs/reference/mapping/types/unsigned_long.asciidoc

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ GET /my_index/_search
7171
"query": {
7272
"range" : {
7373
"my_counter" : {
74-
"gte" : "9223372036854775808.5",
74+
"gte" : "9223372036854775808",
7575
"lte" : "18446744073709551615"
7676
}
7777
}
@@ -80,7 +80,7 @@ GET /my_index/_search
8080
--------------------------------
8181
//TEST[continued]
8282

83-
83+
==== Sort values
8484
For queries with sort on an `unsigned_long` field,
8585
for a particular document {es} returns a sort value of the type `long`
8686
if the value of this document is within the range of long values,
@@ -102,9 +102,6 @@ GET /my_index/_search
102102
//TEST[continued]
103103

104104

105-
==== Unsigned long in scripts
106-
Currently unsigned_long is not supported in scripts.
107-
108105
==== Stored fields
109106
A stored field of `unsigned_long` is stored and returned as `String`.
110107

@@ -113,6 +110,67 @@ For `terms` aggregations, similarly to sort values, `Long` or
113110
`BigInteger` values are used. For other aggregations,
114111
values are converted to the `double` type.
115112

113+
==== Script values
114+
By default, script values of an `unsigned_long` field are returned as
115+
Java signed `Long`, which means that values that are greater than
116+
`Long.MAX_VALUE` are shown as negative values. You can use
117+
`Long.compareUnsigned(long, long)`, `Long.divideUnsigned(long, long)`
118+
and `Long.remainderUnsigned(long, long)` to correctly work with
119+
these values.
120+
121+
For example, the script below returns a value of the counter
122+
divided by 10.
123+
124+
[source,console]
125+
--------------------------------
126+
GET /my_index/_search
127+
{
128+
"query": {
129+
"match_all" : {}
130+
},
131+
"script_fields": {
132+
"count10" : {
133+
"script": {
134+
"source": "Long.divideUnsigned(doc['my_counter'].value, 10)"
135+
}
136+
}
137+
}
138+
}
139+
--------------------------------
140+
//TEST[continued]
141+
142+
143+
Alternatively, you can treat the unsigned long type as `BigInteger`
144+
in your scripts by using the field API. For example, this script
145+
treats `my_counter` as `BigInteger` with a default value of `BigInteger.ZERO`:
146+
147+
[source,js]
148+
--------------------------------------------------
149+
"script": {
150+
"source": "field('my_counter').asBigInteger(BigInteger.ZERO)"
151+
}
152+
--------------------------------------------------
153+
// NOTCONSOLE
154+
155+
For scripts that need to return float or double values, you
156+
can further convert `BigInteger` values to double or float:
157+
158+
[source,console]
159+
--------------------------------
160+
GET /my_index/_search
161+
{
162+
"query": {
163+
"script_score": {
164+
"query": {"match_all": {}},
165+
"script": {
166+
"source": "field('my_counter').asBigInteger(BigInteger.ZERO).floatValue()"
167+
}
168+
}
169+
}
170+
}
171+
--------------------------------
172+
//TEST[continued]
173+
116174
==== Queries with mixed numeric types
117175

118176
Searches with mixed numeric types one of which is `unsigned_long` are

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

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
import org.elasticsearch.script.NumberSortScript;
1818
import org.elasticsearch.script.ScoreScript;
1919
import org.elasticsearch.script.ScriptContext;
20+
import org.elasticsearch.script.ScriptedMetricAggContexts;
2021
import org.elasticsearch.script.StringSortScript;
2122

2223
import java.util.List;
2324
import java.util.Map;
2425

2526
import static java.util.Collections.singletonList;
27+
import static java.util.Map.entry;
2628

2729
public class DocValuesWhitelistExtension implements PainlessExtension {
2830

@@ -34,21 +36,18 @@ public class DocValuesWhitelistExtension implements PainlessExtension {
3436
@Override
3537
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
3638
List<Whitelist> whitelist = singletonList(WHITELIST);
37-
return Map.of(
38-
FieldScript.CONTEXT,
39-
whitelist,
40-
ScoreScript.CONTEXT,
41-
whitelist,
42-
FilterScript.CONTEXT,
43-
whitelist,
44-
AggregationScript.CONTEXT,
45-
whitelist,
46-
NumberSortScript.CONTEXT,
47-
whitelist,
48-
StringSortScript.CONTEXT,
49-
whitelist,
50-
BucketAggregationSelectorScript.CONTEXT,
51-
whitelist
39+
return Map.ofEntries(
40+
entry(FieldScript.CONTEXT, whitelist),
41+
entry(ScoreScript.CONTEXT, whitelist),
42+
entry(FilterScript.CONTEXT, whitelist),
43+
entry(AggregationScript.CONTEXT, whitelist),
44+
entry(NumberSortScript.CONTEXT, whitelist),
45+
entry(StringSortScript.CONTEXT, whitelist),
46+
entry(BucketAggregationSelectorScript.CONTEXT, whitelist),
47+
entry(ScriptedMetricAggContexts.InitScript.CONTEXT, whitelist),
48+
entry(ScriptedMetricAggContexts.MapScript.CONTEXT, whitelist),
49+
entry(ScriptedMetricAggContexts.CombineScript.CONTEXT, whitelist),
50+
entry(ScriptedMetricAggContexts.ReduceScript.CONTEXT, whitelist)
5251
);
5352
}
5453
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ setup:
146146
script:
147147
source: "field('ul').isEmpty() == false"
148148
- match: { hits.total.value: 5 }
149+
150+
149151
- do:
150152
search:
151153
index: test1
@@ -185,6 +187,7 @@ setup:
185187
source: "field('ul').asBigInteger(BigInteger.valueOf(Long.parseUnsignedLong('18446744073709551614'))).doubleValue()"
186188

187189
- match: { hits.total.value: 5 }
190+
188191
- do:
189192
search:
190193
index: test1
@@ -196,3 +199,36 @@ setup:
196199
source: "Math.abs(doc['ul'].value)"
197200

198201
- match: { hits.total.value: 5 }
202+
203+
---
204+
"Scripted Metric":
205+
- skip:
206+
version: " - 8.0.99"
207+
reason: "context for scripted metric was added in 8.1"
208+
209+
- do:
210+
search:
211+
index: test1
212+
body:
213+
query:
214+
term:
215+
ul: 18446744073709551615
216+
aggs:
217+
sqrt:
218+
scripted_metric:
219+
init_script: |
220+
state.sqrd = []
221+
map_script: |
222+
double v = field('ul').asBigInteger(BigInteger.ZERO).doubleValue();
223+
state.sqrd.add(v * v)
224+
combine_script: |
225+
state.sqrd
226+
reduce_script: |
227+
def sum = 0.0;
228+
for (s in states) {
229+
for (v in s) {
230+
sum += v;
231+
}
232+
}
233+
return sum
234+
- match: { aggregations.sqrt.value: 3.4028236692093846E38 }

0 commit comments

Comments
 (0)