Skip to content

Commit b0ed13a

Browse files
authored
Add BinaryDocValuesField to replace BytesRef (ScriptDocValues) (#79760)
This change creates the classes required for the scripting fields API to provide a binary field composed of doc values using BytesRef as the representation returned to the user as a value.
1 parent 2aff5b2 commit b0ed13a

File tree

37 files changed

+379
-118
lines changed

37 files changed

+379
-118
lines changed

modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.script.fields.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,25 @@
88

99
# The whitelist for the fields api
1010

11-
# API
1211
class org.elasticsearch.script.field.Field @dynamic_type {
1312
String getName()
1413
boolean isEmpty()
1514
int size()
1615
}
1716

17+
class org.elasticsearch.script.field.EmptyField @dynamic_type {
18+
def get(def)
19+
def get(int, def)
20+
}
21+
1822
class org.elasticsearch.script.DocBasedScript {
1923
org.elasticsearch.script.field.Field field(String)
2024
}
2125

2226
class org.elasticsearch.script.field.DelegateDocValuesField @dynamic_type {
23-
def getValue(def)
24-
List getValues()
2527
}
28+
29+
class org.elasticsearch.script.field.BinaryDocValuesField @dynamic_type {
30+
ByteBuffer get(ByteBuffer)
31+
ByteBuffer get(int, ByteBuffer)
32+
}

modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/20_scriptfield.yml

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -157,31 +157,4 @@ setup:
157157
- match: { error.failed_shards.0.reason.type: "script_exception" }
158158
- match: { error.failed_shards.0.reason.reason: "compile error" }
159159

160-
---
161-
"Scripted Field with error accessing an unsupported field via the script fields api":
162-
- do:
163-
catch: bad_request
164-
search:
165-
rest_total_hits_as_int: true
166-
body:
167-
script_fields:
168-
bar:
169-
script:
170-
source: "field('foo').getValue('')"
171-
172-
- match: { error.failed_shards.0.reason.caused_by.type: "unsupported_operation_exception" }
173-
- match: { error.failed_shards.0.reason.caused_by.reason: "field [foo] is not supported through the fields api, use [doc] instead"}
174-
175-
- do:
176-
catch: bad_request
177-
search:
178-
rest_total_hits_as_int: true
179-
body:
180-
script_fields:
181-
bar:
182-
script:
183-
source: "field('foo').getValues()"
184-
185-
- match: { error.failed_shards.0.reason.caused_by.type: "unsupported_operation_exception" }
186-
- match: { error.failed_shards.0.reason.caused_by.reason: "field [foo] is not supported through the fields api, use [doc] instead" }
187160

modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,3 +440,25 @@ setup:
440440
script:
441441
source: "doc['token_count'].value"
442442
- match: { hits.hits.0.fields.field.0: 5 }
443+
444+
---
445+
"empty":
446+
- do:
447+
search:
448+
rest_total_hits_as_int: true
449+
body:
450+
script_fields:
451+
field:
452+
script:
453+
source: "int value = field('dne').get(1); value"
454+
- match: { hits.hits.0.fields.field.0: 1}
455+
456+
- do:
457+
search:
458+
rest_total_hits_as_int: true
459+
body:
460+
script_fields:
461+
field:
462+
script:
463+
source: "int value = field('dne').get(1, 1); value"
464+
- match: { hits.hits.0.fields.field.0: 1 }

modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/60_script_doc_values_binary.yml

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,30 @@
2020
index: test
2121
id: 1
2222
body:
23-
binary: U29tZSBiaW5hcnkgYmxvYg==
23+
binary: "U29tZSBiaW5hcnkgYmxvYg=="
24+
25+
- do:
26+
#set the header so we won't randomize it
27+
headers:
28+
Content-Type: application/json
29+
index:
30+
index: test
31+
id: 2
32+
body:
33+
binary: [
34+
"U29tZSBiaW5hcnkgYmxvYg==",
35+
"MTIzNA==",
36+
"dGVzdA=="
37+
]
38+
39+
- do:
40+
#set the header so we won't randomize it
41+
headers:
42+
Content-Type: application/json
43+
index:
44+
index: test
45+
id: 3
46+
body: {}
2447

2548
- do:
2649
indices.refresh: {}
@@ -31,9 +54,55 @@
3154
script_fields:
3255
field1:
3356
script:
34-
source: "doc['binary'].get(0).utf8ToString()"
57+
source: "if (doc['binary'].size() == 0) {return 'empty'} doc['binary'].get(0).utf8ToString()"
3558
field2:
3659
script:
37-
source: "doc['binary'].value.utf8ToString()"
60+
source: "if (doc['binary'].size() == 0) {return 'empty'} doc['binary'].value.utf8ToString()"
3861
- match: { hits.hits.0.fields.field1.0: "Some binary blob" }
3962
- match: { hits.hits.0.fields.field2.0: "Some binary blob" }
63+
64+
- do:
65+
search:
66+
body:
67+
script_fields:
68+
field1:
69+
script:
70+
source: "ByteBuffer bb = field('binary').get(null); if (bb == null) {return -1;} return bb.get(0)"
71+
field2:
72+
script:
73+
source: "ByteBuffer bb = field('binary').get(0, null); if (bb == null) {return -1;} return bb.get(0)"
74+
field3:
75+
script:
76+
source: "int total = 0; for (value in field('binary')) {total += value.get(0)} total"
77+
- match: { hits.hits.0.fields.field1.0: 83 }
78+
- match: { hits.hits.0.fields.field2.0: 83 }
79+
- match: { hits.hits.0.fields.field3.0: 83 }
80+
- match: { hits.hits.1.fields.field1.0: 49 }
81+
- match: { hits.hits.1.fields.field2.0: 49 }
82+
- match: { hits.hits.1.fields.field3.0: 248 }
83+
- match: { hits.hits.2.fields.field1.0: -1 }
84+
- match: { hits.hits.2.fields.field2.0: -1 }
85+
- match: { hits.hits.2.fields.field3.0: 0 }
86+
87+
- do:
88+
search:
89+
body:
90+
script_fields:
91+
field1:
92+
script:
93+
source: "ByteBuffer bb = field('binary').get(null); if (bb == null) {return -1;} return bb.limit()"
94+
field2:
95+
script:
96+
source: "ByteBuffer bb = field('binary').get(0, null); if (bb == null) {return -1;} return bb.limit()"
97+
field3:
98+
script:
99+
source: "int total = 0; for (ByteBuffer value : field('binary')) {total += value.limit()} total"
100+
- match: { hits.hits.0.fields.field1.0: 16 }
101+
- match: { hits.hits.0.fields.field2.0: 16 }
102+
- match: { hits.hits.0.fields.field3.0: 16 }
103+
- match: { hits.hits.1.fields.field1.0: 4 }
104+
- match: { hits.hits.1.fields.field2.0: 4 }
105+
- match: { hits.hits.1.fields.field3.0: 24 }
106+
- match: { hits.hits.2.fields.field1.0: -1 }
107+
- match: { hits.hits.2.fields.field2.0: -1 }
108+
- match: { hits.hits.2.fields.field3.0: 0 }

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ private static class ScaledFloatLeafFieldData implements LeafNumericFieldData {
527527
}
528528

529529
@Override
530-
public DocValuesField getScriptField(String name) {
530+
public DocValuesField<?> getScriptField(String name) {
531531
return new DelegateDocValuesField(new ScriptDocValues.Doubles(getDoubleValues()), name);
532532
}
533533

server/src/main/java/org/elasticsearch/index/fielddata/IpScriptFieldData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public BinaryScriptLeafFieldData loadDirect(LeafReaderContext context) throws Ex
5252
IpFieldScript script = leafFactory.newInstance(context);
5353
return new BinaryScriptLeafFieldData() {
5454
@Override
55-
public DocValuesField getScriptField(String name) {
55+
public DocValuesField<?> getScriptField(String name) {
5656
return new DelegateDocValuesField(new IpScriptDocValues(getBytesValues()), name);
5757
}
5858

server/src/main/java/org/elasticsearch/index/fielddata/LeafFieldData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public interface LeafFieldData extends Accountable, Releasable {
2323
/**
2424
* Returns an {@code Field} for use in accessing field values in scripting.
2525
*/
26-
DocValuesField getScriptField(String name);
26+
DocValuesField<?> getScriptField(String name);
2727

2828
/**
2929
* Return a String representation of the values.

server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.elasticsearch.common.geo.GeoUtils;
1818
import org.elasticsearch.common.time.DateUtils;
1919
import org.elasticsearch.geometry.utils.Geohash;
20+
import org.elasticsearch.script.field.BinaryDocValuesField;
2021

2122
import java.io.IOException;
2223
import java.time.Instant;
@@ -585,30 +586,33 @@ public final String getValue() {
585586
}
586587
}
587588

588-
public static final class BytesRefs extends BinaryScriptDocValues<BytesRef> {
589+
public static final class BytesRefs extends ScriptDocValues<BytesRef> {
589590

590-
public BytesRefs(SortedBinaryDocValues in) {
591-
super(in);
591+
private final BinaryDocValuesField binaryDocValuesField;
592+
593+
public BytesRefs(BinaryDocValuesField binaryDocValuesField) {
594+
this.binaryDocValuesField = binaryDocValuesField;
592595
}
593596

594597
@Override
595-
public BytesRef get(int index) {
596-
if (count == 0) {
597-
throw new IllegalStateException(
598-
"A document doesn't have a value for a field! "
599-
+ "Use doc[<field>].size()==0 to check if a document is missing a field!"
600-
);
601-
}
602-
/**
603-
* We need to make a copy here because {@link BinaryScriptDocValues} might reuse the
604-
* returned value and the same instance might be used to
605-
* return values from multiple documents.
606-
**/
607-
return values[index].toBytesRef();
598+
public void setNextDocId(int docId) throws IOException {
599+
throw new UnsupportedOperationException();
608600
}
609601

610602
public BytesRef getValue() {
603+
throwIfEmpty();
611604
return get(0);
612605
}
606+
607+
@Override
608+
public BytesRef get(int index) {
609+
throwIfEmpty();
610+
return binaryDocValuesField.getInternal(index);
611+
}
612+
613+
@Override
614+
public int size() {
615+
return binaryDocValuesField.size();
616+
}
613617
}
614618
}

server/src/main/java/org/elasticsearch/index/fielddata/StringScriptFieldData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public BinaryScriptLeafFieldData loadDirect(LeafReaderContext context) throws Ex
4444
StringFieldScript script = leafFactory.newInstance(context);
4545
return new BinaryScriptLeafFieldData() {
4646
@Override
47-
public DocValuesField getScriptField(String name) {
47+
public DocValuesField<?> getScriptField(String name) {
4848
return new DelegateDocValuesField(new ScriptDocValues.Strings(getBytesValues()), name);
4949
}
5050

server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractLeafGeoPointFieldData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public final SortedBinaryDocValues getBytesValues() {
2727
}
2828

2929
@Override
30-
public final DocValuesField getScriptField(String name) {
30+
public final DocValuesField<?> getScriptField(String name) {
3131
return new DelegateDocValuesField(new ScriptDocValues.GeoPoints(getGeoPointValues()), name);
3232
}
3333

0 commit comments

Comments
 (0)