From e115bb79491deaf47c1f93c8debf265be448f459 Mon Sep 17 00:00:00 2001 From: Stuart Tettemer Date: Mon, 29 Nov 2021 14:27:52 -0600 Subject: [PATCH 1/3] Script: fields API for byte, double, float, integer, long, short * Adds DocValuesField for byte, double, float, integer, long and short The integral types implement ScriptDocValues.Supplier while the floating point types implement ScriptDocValues.Supplier. Refs: #79105 --- .../org.elasticsearch.script.fields.txt | 32 ++ .../test/painless/50_script_doc_values.yml | 312 +++++++++++++++++- .../index/mapper/LongScriptFieldType.java | 10 +- .../index/mapper/NumberFieldMapper.java | 44 +-- .../script/field/ByteDocValuesField.java | 141 ++++++++ .../script/field/DoubleDocValuesField.java | 137 ++++++++ .../script/field/FloatDocValuesField.java | 137 ++++++++ .../script/field/IntegerDocValuesField.java | 137 ++++++++ .../script/field/LongDocValuesField.java | 137 ++++++++ .../script/field/ShortDocValuesField.java | 140 ++++++++ 10 files changed, 1185 insertions(+), 42 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java create mode 100644 server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java create mode 100644 server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java create mode 100644 server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java create mode 100644 server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java create mode 100644 server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.script.fields.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.script.fields.txt index 9e5b000438dd7..c22a2bc0d1052 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.script.fields.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.script.fields.txt @@ -35,3 +35,35 @@ class org.elasticsearch.script.field.BooleanDocValuesField @dynamic_type { boolean get(boolean) boolean get(int, boolean) } + +class org.elasticsearch.script.field.IntegerDocValuesField @dynamic_type { + int get(int) + int get(int, int) +} + +class org.elasticsearch.script.field.LongDocValuesField @dynamic_type { + long get(long) + long get(int, long) +} + +class org.elasticsearch.script.field.DoubleDocValuesField @dynamic_type { + double get(double) + double get(int, double) +} + +class org.elasticsearch.script.field.FloatDocValuesField @dynamic_type { + float get(float) + float get(int, float) +} + +# defaults are cast to byte, taking an int facilitates resolution with constants without casting +class org.elasticsearch.script.field.ByteDocValuesField @dynamic_type { + byte get(int) + byte get(int, int) +} + +# defaults are cast to short, taking an int facilitates resolution with constants without casting +class org.elasticsearch.script.field.ShortDocValuesField @dynamic_type { + short get(int) + short get(int, int) +} diff --git a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml index 42d3f6f10a5d9..2f08212b823b3 100644 --- a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml +++ b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml @@ -37,12 +37,16 @@ setup: token_count: type: token_count analyzer: standard + rank: + type: integer + - do: index: index: test id: 1 body: + rank: 1 boolean: true date: 2017-01-01T12:11:12 geo_point: 41.12,-71.34 @@ -62,14 +66,23 @@ setup: index: index: test id: 2 - body: {} + body: + rank: 2 - do: index: index: test id: 3 body: + rank: 3 boolean: [true, false, true] + long: [1152921504606846976, 576460752303423488] + integer: [5, 17, 29] + short: [6, 18, 30, 45] + byte: [16, 32, 64, 8, 4] + double: [3.141592653588, 2.141592653587] + float: [1.123, 2.234] + - do: indices.refresh: {} @@ -115,7 +128,6 @@ setup: body: query: { term: { _id: 1 } } script_fields: - field: script: source: "field('boolean').get(false)" @@ -398,6 +410,44 @@ setup: source: "doc['integer'].value" - match: { hits.hits.0.fields.field.0: 134134566 } + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('integer').get(-1)" + - match: { hits.hits.0.fields.field.0: 134134566 } + - match: { hits.hits.1.fields.field.0: -1 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('integer').get(1, -3)" + - match: { hits.hits.0.fields.field.0: -3 } + - match: { hits.hits.1.fields.field.0: -3 } + - match: { hits.hits.2.fields.field.0: 17 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "int total = 0; for (int i : field('integer')) { total += i; } total + field('integer').size();" + - match: { hits.hits.0.fields.field.0: 134134567 } + - match: { hits.hits.1.fields.field.0: 0 } + - match: { hits.hits.2.fields.field.0: 54 } + --- "short": - do: @@ -422,6 +472,69 @@ setup: source: "doc['short'].value" - match: { hits.hits.0.fields.field.0: 1324 } + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('short').get(-1)" + - match: { hits.hits.0.fields.field.0: 1324 } + - match: { hits.hits.1.fields.field.0: -1 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "short defaultShort = -1; field('short').get(defaultShort)" + - match: { hits.hits.0.fields.field.0: 1324 } + - match: { hits.hits.1.fields.field.0: -1 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('short').get(1, -3)" + - match: { hits.hits.0.fields.field.0: -3 } + - match: { hits.hits.1.fields.field.0: -3 } + - match: { hits.hits.2.fields.field.0: 18 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "short defaultShort = -3; field('short').get(1, defaultShort)" + - match: { hits.hits.0.fields.field.0: -3 } + - match: { hits.hits.1.fields.field.0: -3 } + - match: { hits.hits.2.fields.field.0: 18 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "int total = 0; for (short s : field('short')) { total += s; } total + field('short').size();" + - match: { hits.hits.0.fields.field.0: 1325 } + - match: { hits.hits.1.fields.field.0: 0 } + - match: { hits.hits.2.fields.field.0: 103 } + --- "byte": - do: @@ -446,6 +559,97 @@ setup: source: "doc['byte'].value" - match: { hits.hits.0.fields.field.0: 12 } + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('byte').get((byte) 5)" + - match: { hits.hits.0.fields.field.0: 12 } + - match: { hits.hits.1.fields.field.0: 5 } + - match: { hits.hits.2.fields.field.0: 4 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "byte defaultByte = 5; field('byte').get(defaultByte)" + - match: { hits.hits.0.fields.field.0: 12 } + - match: { hits.hits.1.fields.field.0: 5 } + - match: { hits.hits.2.fields.field.0: 4 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('byte').get(5)" + - match: { hits.hits.0.fields.field.0: 12 } + - match: { hits.hits.1.fields.field.0: 5 } + - match: { hits.hits.2.fields.field.0: 4 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('byte').get(1, (byte) 7)" + - match: { hits.hits.0.fields.field.0: 7 } + - match: { hits.hits.1.fields.field.0: 7 } + - match: { hits.hits.2.fields.field.0: 8 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "byte defaultByte = 7; field('byte').get(1, defaultByte)" + - match: { hits.hits.0.fields.field.0: 7 } + - match: { hits.hits.1.fields.field.0: 7 } + - match: { hits.hits.2.fields.field.0: 8 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('byte').get(1, 7)" + - match: { hits.hits.0.fields.field.0: 7 } + - match: { hits.hits.1.fields.field.0: 7 } + - match: { hits.hits.2.fields.field.0: 8 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "int total = 0; for (byte s : field('byte')) { total += s; } total + field('byte').size();" + - match: { hits.hits.0.fields.field.0: 13 } + - match: { hits.hits.1.fields.field.0: 0 } + - match: { hits.hits.2.fields.field.0: 129 } + --- "double": - do: @@ -470,6 +674,58 @@ setup: source: "doc['double'].value" - match: { hits.hits.0.fields.field.0: 3.14159265358979 } + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('double').get(-1)" + - match: { hits.hits.0.fields.field.0: 3.14159265358979 } + - match: { hits.hits.1.fields.field.0: -1 } + - match: { hits.hits.2.fields.field.0: 2.141592653587 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "double defaultDouble = 7.8; field('double').get(1, defaultDouble)" + - match: { hits.hits.0.fields.field.0: 7.8 } + - match: { hits.hits.1.fields.field.0: 7.8 } + - match: { hits.hits.2.fields.field.0: 3.141592653588 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "field('double').get(1, 9.2)" + - match: { hits.hits.0.fields.field.0: 9.2 } + - match: { hits.hits.1.fields.field.0: 9.2 } + - match: { hits.hits.2.fields.field.0: 3.141592653588 } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "double total = 0; for (double d : field('double')) { total += d; } total + field('double').size();" + - match: { hits.hits.0.fields.field.0: 4.14159265358979 } + - match: { hits.hits.1.fields.field.0: 0 } + - match: { hits.hits.2.fields.field.0: 7.283185307175 } + --- "float": - do: @@ -494,6 +750,58 @@ setup: source: "doc['float'].value" - match: { hits.hits.0.fields.field.0: 3.1415927410125732 } # this ends up as a double + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "Float.toString(field('float').get(-1))" + - match: { hits.hits.0.fields.field.0: "3.1415927" } + - match: { hits.hits.1.fields.field.0: "-1.0" } + - match: { hits.hits.2.fields.field.0: "1.123" } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "float defaultFloat = 7.8f; Float.toString(field('float').get(1, defaultFloat))" + - match: { hits.hits.0.fields.field.0: "7.8" } + - match: { hits.hits.1.fields.field.0: "7.8" } + - match: { hits.hits.2.fields.field.0: "2.234" } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "Float.toString(field('float').get(1, 9.2f))" + - match: { hits.hits.0.fields.field.0: "9.2" } + - match: { hits.hits.1.fields.field.0: "9.2" } + - match: { hits.hits.2.fields.field.0: "2.234" } + + - do: + search: + rest_total_hits_as_int: true + body: + sort: [ { rank: asc } ] + script_fields: + field: + script: + source: "float total = 0; for (float f : field('float')) { total += f; } Float.toString(total + field('float').size());" + - match: { hits.hits.0.fields.field.0: "4.141593" } + - match: { hits.hits.1.fields.field.0: "0.0" } + - match: { hits.hits.2.fields.field.0: "5.357" } + --- "half_float": - do: diff --git a/server/src/main/java/org/elasticsearch/index/mapper/LongScriptFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/LongScriptFieldType.java index 4791eb5398ff5..dfbe79176ae4d 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/LongScriptFieldType.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/LongScriptFieldType.java @@ -15,14 +15,12 @@ import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.index.fielddata.LongScriptFieldData; -import org.elasticsearch.index.fielddata.ScriptDocValues.Longs; -import org.elasticsearch.index.fielddata.ScriptDocValues.LongsSupplier; import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.script.CompositeFieldScript; import org.elasticsearch.script.LongFieldScript; import org.elasticsearch.script.Script; -import org.elasticsearch.script.field.DelegateDocValuesField; +import org.elasticsearch.script.field.LongDocValuesField; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.lookup.SearchLookup; import org.elasticsearch.search.runtime.LongScriptFieldExistsQuery; @@ -96,11 +94,7 @@ public DocValueFormat docValueFormat(String format, ZoneId timeZone) { @Override public LongScriptFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier searchLookup) { - return new LongScriptFieldData.Builder( - name(), - leafFactory(searchLookup.get()), - (dv, n) -> new DelegateDocValuesField(new Longs(new LongsSupplier(dv)), n) - ); + return new LongScriptFieldData.Builder(name(), leafFactory(searchLookup.get()), LongDocValuesField::new); } @Override diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index 075d475029930..1d5b852067219 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -37,8 +37,6 @@ import org.elasticsearch.index.fielddata.IndexNumericFieldData.NumericType; import org.elasticsearch.index.fielddata.ScriptDocValues.Doubles; import org.elasticsearch.index.fielddata.ScriptDocValues.DoublesSupplier; -import org.elasticsearch.index.fielddata.ScriptDocValues.Longs; -import org.elasticsearch.index.fielddata.ScriptDocValues.LongsSupplier; import org.elasticsearch.index.fielddata.plain.SortedDoublesIndexFieldData; import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData; import org.elasticsearch.index.mapper.TimeSeriesParams.MetricType; @@ -47,7 +45,13 @@ import org.elasticsearch.script.LongFieldScript; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptCompiler; +import org.elasticsearch.script.field.ByteDocValuesField; import org.elasticsearch.script.field.DelegateDocValuesField; +import org.elasticsearch.script.field.DoubleDocValuesField; +import org.elasticsearch.script.field.FloatDocValuesField; +import org.elasticsearch.script.field.IntegerDocValuesField; +import org.elasticsearch.script.field.LongDocValuesField; +import org.elasticsearch.script.field.ShortDocValuesField; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.lookup.FieldValues; import org.elasticsearch.search.lookup.SearchLookup; @@ -445,11 +449,7 @@ public List createFields(String name, Number value, boolean indexed, bool @Override public IndexFieldData.Builder getFieldDataBuilder(String name) { - return new SortedDoublesIndexFieldData.Builder( - name, - numericType(), - (dv, n) -> new DelegateDocValuesField(new Doubles(new DoublesSupplier(dv)), n) - ); + return new SortedDoublesIndexFieldData.Builder(name, numericType(), FloatDocValuesField::new); } private void validateParsed(float value) { @@ -539,11 +539,7 @@ public List createFields(String name, Number value, boolean indexed, bool @Override public IndexFieldData.Builder getFieldDataBuilder(String name) { - return new SortedDoublesIndexFieldData.Builder( - name, - numericType(), - (dv, n) -> new DelegateDocValuesField(new Doubles(new DoublesSupplier(dv)), n) - ); + return new SortedDoublesIndexFieldData.Builder(name, numericType(), DoubleDocValuesField::new); } private void validateParsed(double value) { @@ -620,11 +616,7 @@ Number valueForSearch(Number value) { @Override public IndexFieldData.Builder getFieldDataBuilder(String name) { - return new SortedNumericIndexFieldData.Builder( - name, - numericType(), - (dv, n) -> new DelegateDocValuesField(new Longs(new LongsSupplier(dv)), n) - ); + return new SortedNumericIndexFieldData.Builder(name, numericType(), ByteDocValuesField::new); } }, SHORT("short", NumericType.SHORT) { @@ -691,11 +683,7 @@ Number valueForSearch(Number value) { @Override public IndexFieldData.Builder getFieldDataBuilder(String name) { - return new SortedNumericIndexFieldData.Builder( - name, - numericType(), - (dv, n) -> new DelegateDocValuesField(new Longs(new LongsSupplier(dv)), n) - ); + return new SortedNumericIndexFieldData.Builder(name, numericType(), ShortDocValuesField::new); } }, INTEGER("integer", NumericType.INT) { @@ -821,11 +809,7 @@ public List createFields(String name, Number value, boolean indexed, bool @Override public IndexFieldData.Builder getFieldDataBuilder(String name) { - return new SortedNumericIndexFieldData.Builder( - name, - numericType(), - (dv, n) -> new DelegateDocValuesField(new Longs(new LongsSupplier(dv)), n) - ); + return new SortedNumericIndexFieldData.Builder(name, numericType(), IntegerDocValuesField::new); } }, LONG("long", NumericType.LONG) { @@ -921,11 +905,7 @@ public List createFields(String name, Number value, boolean indexed, bool @Override public IndexFieldData.Builder getFieldDataBuilder(String name) { - return new SortedNumericIndexFieldData.Builder( - name, - numericType(), - (dv, n) -> new DelegateDocValuesField(new Longs(new LongsSupplier(dv)), n) - ); + return new SortedNumericIndexFieldData.Builder(name, numericType(), LongDocValuesField::new); } }; diff --git a/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java new file mode 100644 index 0000000000000..d466026238f83 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.script.field; + +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.index.fielddata.ScriptDocValues; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class ByteDocValuesField implements DocValuesField, ScriptDocValues.Supplier { + + protected final SortedNumericDocValues input; + protected final String name; + + protected byte[] values = new byte[0]; + protected int count; + + private ScriptDocValues.Longs longs = null; + + public ByteDocValuesField(SortedNumericDocValues input, String name) { + this.input = input; + this.name = name; + } + + /** + * Set the current document ID. + * + * @param docId + */ + @Override + public void setNextDocId(int docId) throws IOException { + if (input.advanceExact(docId)) { + resize(input.docValueCount()); + for (int i = 0; i < count; i++) { + values[i] = (byte) input.nextValue(); + } + } else { + resize(0); + } + } + + protected void resize(int newSize) { + count = newSize; + + assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?"; + values = ArrayUtil.grow(values, count); + } + + /** + * Returns a {@code ScriptDocValues} of the appropriate type for this field. + * This is used to support backwards compatibility for accessing field values + * through the {@code doc} variable. + */ + @Override + public ScriptDocValues getScriptDocValues() { + if (longs == null) { + longs = new ScriptDocValues.Longs(this); + } + + return longs; + } + + @Override + public Long getInternal(int index) { + return (long) values[index]; + } + + /** + * Returns the name of this field. + */ + @Override + public String getName() { + throw new UnsupportedOperationException(); + } + + /** + * Returns {@code true} if this field has no values, otherwise {@code false}. + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns the number of values this field has. + */ + @Override + public int size() { + return count; + } + + /** + * Returns an iterator over elements of type {@code T}. + * + * @return an Iterator. + */ + @Override + public Iterator iterator() { + return new Iterator() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < count; + } + + @Override + public Byte next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return values[index++]; + } + }; + } + + public byte get(int defaultValue) { + return get(0, defaultValue); + } + + // constants in java and painless are ints, so letting the defaultValue be an int allows users to + // call this without casting. A byte variable will be automatically widened to an int. + // If the user does pass a value outside the range, it will be cast down to a byte. + public byte get(int index, int defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return (byte) defaultValue; + } + + return values[index]; + } + +} diff --git a/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java new file mode 100644 index 0000000000000..9f8549a959ac7 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.script.field; + +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.index.fielddata.ScriptDocValues; +import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class DoubleDocValuesField implements DocValuesField, ScriptDocValues.Supplier { + + protected final SortedNumericDoubleValues input; + protected final String name; + + protected double[] values = new double[0]; + protected int count; + + private ScriptDocValues.Doubles doubles = null; + + public DoubleDocValuesField(SortedNumericDoubleValues input, String name) { + this.input = input; + this.name = name; + } + + /** + * Set the current document ID. + * + * @param docId + */ + @Override + public void setNextDocId(int docId) throws IOException { + if (input.advanceExact(docId)) { + resize(input.docValueCount()); + for (int i = 0; i < count; i++) { + values[i] = input.nextValue(); + } + } else { + resize(0); + } + } + + protected void resize(int newSize) { + count = newSize; + + assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?"; + values = ArrayUtil.grow(values, count); + } + + /** + * Returns a {@code ScriptDocValues} of the appropriate type for this field. + * This is used to support backwards compatibility for accessing field values + * through the {@code doc} variable. + */ + @Override + public ScriptDocValues getScriptDocValues() { + if (doubles == null) { + doubles = new ScriptDocValues.Doubles(this); + } + + return doubles; + } + + @Override + public Double getInternal(int index) { + return values[index]; + } + + /** + * Returns the name of this field. + */ + @Override + public String getName() { + return name; + } + + /** + * Returns {@code true} if this field has no values, otherwise {@code false}. + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns the number of values this field has. + */ + @Override + public int size() { + return count; + } + + /** + * Returns an iterator over elements of type {@code T}. + * + * @return an Iterator. + */ + @Override + public Iterator iterator() { + return new Iterator() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < count; + } + + @Override + public Double next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return values[index++]; + } + }; + } + + public double get(double defaultValue) { + return get(0, defaultValue); + } + + public double get(int index, double defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return defaultValue; + } + + return values[index]; + } +} diff --git a/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java new file mode 100644 index 0000000000000..a6d9ac680fde1 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.script.field; + +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.index.fielddata.ScriptDocValues; +import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class FloatDocValuesField implements DocValuesField, ScriptDocValues.Supplier { + + protected final SortedNumericDoubleValues input; + protected final String name; + + protected float[] values = new float[0]; + protected int count; + + private ScriptDocValues.Doubles doubles = null; + + public FloatDocValuesField(SortedNumericDoubleValues input, String name) { + this.input = input; + this.name = name; + } + + /** + * Set the current document ID. + * + * @param docId + */ + @Override + public void setNextDocId(int docId) throws IOException { + if (input.advanceExact(docId)) { + resize(input.docValueCount()); + for (int i = 0; i < count; i++) { + values[i] = (float) input.nextValue(); + } + } else { + resize(0); + } + } + + protected void resize(int newSize) { + count = newSize; + + assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?"; + values = ArrayUtil.grow(values, count); + } + + /** + * Returns a {@code ScriptDocValues} of the appropriate type for this field. + * This is used to support backwards compatibility for accessing field values + * through the {@code doc} variable. + */ + @Override + public ScriptDocValues getScriptDocValues() { + if (doubles == null) { + doubles = new ScriptDocValues.Doubles(this); + } + + return doubles; + } + + @Override + public Double getInternal(int index) { + return (double) values[index]; + } + + /** + * Returns the name of this field. + */ + @Override + public String getName() { + return name; + } + + /** + * Returns {@code true} if this field has no values, otherwise {@code false}. + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns the number of values this field has. + */ + @Override + public int size() { + return count; + } + + /** + * Returns an iterator over elements of type {@code T}. + * + * @return an Iterator. + */ + @Override + public Iterator iterator() { + return new Iterator() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < count; + } + + @Override + public Float next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return values[index++]; + } + }; + } + + public float get(float defaultValue) { + return get(0, defaultValue); + } + + public float get(int index, float defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return defaultValue; + } + + return values[index]; + } +} diff --git a/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java new file mode 100644 index 0000000000000..aee55d791807a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.script.field; + +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.index.fielddata.ScriptDocValues; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class IntegerDocValuesField implements DocValuesField, ScriptDocValues.Supplier { + + protected final SortedNumericDocValues input; + protected final String name; + + protected int[] values = new int[0]; + protected int count; + + private ScriptDocValues.Longs longs = null; + + public IntegerDocValuesField(SortedNumericDocValues input, String name) { + this.input = input; + this.name = name; + } + + /** + * Set the current document ID. + * + * @param docId + */ + @Override + public void setNextDocId(int docId) throws IOException { + if (input.advanceExact(docId)) { + resize(input.docValueCount()); + for (int i = 0; i < count; i++) { + values[i] = (int) input.nextValue(); + } + } else { + resize(0); + } + } + + protected void resize(int newSize) { + count = newSize; + + assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?"; + values = ArrayUtil.grow(values, count); + } + + /** + * Returns a {@code ScriptDocValues} of the appropriate type for this field. + * This is used to support backwards compatibility for accessing field values + * through the {@code doc} variable. + */ + @Override + public ScriptDocValues getScriptDocValues() { + if (longs == null) { + longs = new ScriptDocValues.Longs(this); + } + + return longs; + } + + @Override + public Long getInternal(int index) { + return (long) values[index]; + } + + /** + * Returns the name of this field. + */ + @Override + public String getName() { + return name; + } + + /** + * Returns {@code true} if this field has no values, otherwise {@code false}. + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns the number of values this field has. + */ + @Override + public int size() { + return count; + } + + /** + * Returns an iterator over elements of type {@code T}. + * + * @return an Iterator. + */ + @Override + public Iterator iterator() { + return new Iterator() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < count; + } + + @Override + public Integer next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return values[index++]; + } + }; + } + + public int get(int defaultValue) { + return get(0, defaultValue); + } + + public int get(int index, int defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return defaultValue; + } + + return values[index]; + } +} diff --git a/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java new file mode 100644 index 0000000000000..598a0b37f9107 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.script.field; + +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.index.fielddata.ScriptDocValues; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class LongDocValuesField implements DocValuesField, ScriptDocValues.Supplier { + + protected final SortedNumericDocValues input; + protected final String name; + + protected long[] values = new long[0]; + protected int count; + + private ScriptDocValues.Longs longs = null; + + public LongDocValuesField(SortedNumericDocValues input, String name) { + this.input = input; + this.name = name; + } + + /** + * Set the current document ID. + * + * @param docId + */ + @Override + public void setNextDocId(int docId) throws IOException { + if (input.advanceExact(docId)) { + resize(input.docValueCount()); + for (int i = 0; i < count; i++) { + values[i] = input.nextValue(); + } + } else { + resize(0); + } + } + + protected void resize(int newSize) { + count = newSize; + + assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?"; + values = ArrayUtil.grow(values, count); + } + + /** + * Returns a {@code ScriptDocValues} of the appropriate type for this field. + * This is used to support backwards compatibility for accessing field values + * through the {@code doc} variable. + */ + @Override + public ScriptDocValues getScriptDocValues() { + if (longs == null) { + longs = new ScriptDocValues.Longs(this); + } + + return longs; + } + + @Override + public Long getInternal(int index) { + return values[index]; + } + + /** + * Returns the name of this field. + */ + @Override + public String getName() { + return name; + } + + /** + * Returns {@code true} if this field has no values, otherwise {@code false}. + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns the number of values this field has. + */ + @Override + public int size() { + return count; + } + + /** + * Returns an iterator over elements of type {@code T}. + * + * @return an Iterator. + */ + @Override + public Iterator iterator() { + return new Iterator() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < count; + } + + @Override + public Long next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return values[index++]; + } + }; + } + + public long get(long defaultValue) { + return get(0, defaultValue); + } + + public long get(int index, long defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return defaultValue; + } + + return values[index]; + } +} diff --git a/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java new file mode 100644 index 0000000000000..6f6c46883dd9f --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.script.field; + +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.index.fielddata.ScriptDocValues; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class ShortDocValuesField implements DocValuesField, ScriptDocValues.Supplier { + + protected final SortedNumericDocValues input; + protected final String name; + + protected short[] values = new short[0]; + protected int count; + + private ScriptDocValues.Longs longs = null; + + public ShortDocValuesField(SortedNumericDocValues input, String name) { + this.input = input; + this.name = name; + } + + /** + * Set the current document ID. + * + * @param docId + */ + @Override + public void setNextDocId(int docId) throws IOException { + if (input.advanceExact(docId)) { + resize(input.docValueCount()); + for (int i = 0; i < count; i++) { + values[i] = (short) input.nextValue(); + } + } else { + resize(0); + } + } + + protected void resize(int newSize) { + count = newSize; + + assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?"; + values = ArrayUtil.grow(values, count); + } + + /** + * Returns a {@code ScriptDocValues} of the appropriate type for this field. + * This is used to support backwards compatibility for accessing field values + * through the {@code doc} variable. + */ + @Override + public ScriptDocValues getScriptDocValues() { + if (longs == null) { + longs = new ScriptDocValues.Longs(this); + } + + return longs; + } + + @Override + public Long getInternal(int index) { + return (long) values[index]; + } + + /** + * Returns the name of this field. + */ + @Override + public String getName() { + return name; + } + + /** + * Returns {@code true} if this field has no values, otherwise {@code false}. + */ + @Override + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns the number of values this field has. + */ + @Override + public int size() { + return count; + } + + /** + * Returns an iterator over elements of type {@code T}. + * + * @return an Iterator. + */ + @Override + public Iterator iterator() { + return new Iterator() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < count; + } + + @Override + public Short next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return values[index++]; + } + }; + } + + public short get(int defaultValue) { + return get(0, defaultValue); + } + + // constants in java and painless are ints, so letting the defaultValue be an int allows users to + // call this without casting. A short variable will be automatically widened to an int. + // If the user does pass a value outside the range, it will be cast down to a short. + public short get(int index, int defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return (short) defaultValue; + } + + return values[index]; + } +} From 77f7bbd6322bd6a4596fed6f7e25d15af4c640d1 Mon Sep 17 00:00:00 2001 From: Stuart Tettemer Date: Mon, 29 Nov 2021 16:13:27 -0600 Subject: [PATCH 2/3] Add tests --- .../script/field/BooleanDocValuesField.java | 2 +- .../script/field/ByteDocValuesField.java | 2 +- .../script/field/DoubleDocValuesField.java | 2 +- .../script/field/FloatDocValuesField.java | 2 +- .../script/field/IntegerDocValuesField.java | 2 +- .../script/field/LongDocValuesField.java | 2 +- .../script/field/ShortDocValuesField.java | 2 +- .../fielddata/FloatDocValuesFieldTests.java | 95 ++++++++++++++ .../IntegralDocValuesFieldTests.java | 117 ++++++++++++++++++ .../fielddata/ScriptDocValuesLongsTests.java | 20 +-- 10 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/index/fielddata/FloatDocValuesFieldTests.java create mode 100644 server/src/test/java/org/elasticsearch/index/fielddata/IntegralDocValuesFieldTests.java diff --git a/server/src/main/java/org/elasticsearch/script/field/BooleanDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/BooleanDocValuesField.java index 8dd3674feb470..6e58e8796ed7e 100644 --- a/server/src/main/java/org/elasticsearch/script/field/BooleanDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/BooleanDocValuesField.java @@ -56,7 +56,7 @@ private void resize(int newSize) { } @Override - public ScriptDocValues getScriptDocValues() { + public ScriptDocValues getScriptDocValues() { if (booleans == null) { booleans = new ScriptDocValues.Booleans(this); } diff --git a/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java index d466026238f83..d402843fcc0b5 100644 --- a/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java @@ -61,7 +61,7 @@ protected void resize(int newSize) { * through the {@code doc} variable. */ @Override - public ScriptDocValues getScriptDocValues() { + public ScriptDocValues getScriptDocValues() { if (longs == null) { longs = new ScriptDocValues.Longs(this); } diff --git a/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java index 9f8549a959ac7..34bf7564ef3f3 100644 --- a/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java @@ -61,7 +61,7 @@ protected void resize(int newSize) { * through the {@code doc} variable. */ @Override - public ScriptDocValues getScriptDocValues() { + public ScriptDocValues getScriptDocValues() { if (doubles == null) { doubles = new ScriptDocValues.Doubles(this); } diff --git a/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java index a6d9ac680fde1..99a34a3d9ee9f 100644 --- a/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java @@ -61,7 +61,7 @@ protected void resize(int newSize) { * through the {@code doc} variable. */ @Override - public ScriptDocValues getScriptDocValues() { + public ScriptDocValues getScriptDocValues() { if (doubles == null) { doubles = new ScriptDocValues.Doubles(this); } diff --git a/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java index aee55d791807a..164234d09a26f 100644 --- a/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java @@ -61,7 +61,7 @@ protected void resize(int newSize) { * through the {@code doc} variable. */ @Override - public ScriptDocValues getScriptDocValues() { + public ScriptDocValues getScriptDocValues() { if (longs == null) { longs = new ScriptDocValues.Longs(this); } diff --git a/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java index 598a0b37f9107..4ce0b7c888b6d 100644 --- a/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java @@ -61,7 +61,7 @@ protected void resize(int newSize) { * through the {@code doc} variable. */ @Override - public ScriptDocValues getScriptDocValues() { + public ScriptDocValues getScriptDocValues() { if (longs == null) { longs = new ScriptDocValues.Longs(this); } diff --git a/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java index 6f6c46883dd9f..c83f451f0a214 100644 --- a/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java @@ -61,7 +61,7 @@ protected void resize(int newSize) { * through the {@code doc} variable. */ @Override - public ScriptDocValues getScriptDocValues() { + public ScriptDocValues getScriptDocValues() { if (longs == null) { longs = new ScriptDocValues.Longs(this); } diff --git a/server/src/test/java/org/elasticsearch/index/fielddata/FloatDocValuesFieldTests.java b/server/src/test/java/org/elasticsearch/index/fielddata/FloatDocValuesFieldTests.java new file mode 100644 index 0000000000000..27cd9d08df132 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/fielddata/FloatDocValuesFieldTests.java @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.index.fielddata; + +import org.elasticsearch.script.field.DoubleDocValuesField; +import org.elasticsearch.script.field.FloatDocValuesField; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.function.DoubleSupplier; + +public class FloatDocValuesFieldTests extends ESTestCase { + public void testFloatField() throws IOException { + double[][] values = generate(ESTestCase::randomFloat); + FloatDocValuesField floatField = new FloatDocValuesField(wrap(values), "test"); + for (int round = 0; round < 10; round++) { + int d = between(0, values.length - 1); + floatField.setNextDocId(d); + if (values[d].length > 0) { + assertEquals(values[d][0], floatField.get(Float.MIN_VALUE), 0.0); + assertEquals(values[d][0], floatField.get(0, Float.MIN_VALUE), 0.0); + } + assertEquals(values[d].length, floatField.size()); + for (int i = 0; i < values[d].length; i++) { + assertEquals(values[d][i], floatField.get(i, Float.MIN_VALUE), 0.0); + } + int i = 0; + for (float f : floatField) { + assertEquals(values[d][i++], f, 0.0); + } + } + } + + public void testDoubleField() throws IOException { + double[][] values = generate(ESTestCase::randomFloat); + DoubleDocValuesField doubleField = new DoubleDocValuesField(wrap(values), "test"); + for (int round = 0; round < 10; round++) { + int d = between(0, values.length - 1); + doubleField.setNextDocId(d); + if (values[d].length > 0) { + assertEquals(values[d][0], doubleField.get(Float.MIN_VALUE), 0.0); + assertEquals(values[d][0], doubleField.get(0, Float.MIN_VALUE), 0.0); + } + assertEquals(values[d].length, doubleField.size()); + for (int i = 0; i < values[d].length; i++) { + assertEquals(values[d][i], doubleField.get(i, Float.MIN_VALUE), 0.0); + } + int i = 0; + for (double dbl : doubleField) { + assertEquals(values[d][i++], dbl, 0.0); + } + } + } + + protected double[][] generate(DoubleSupplier supplier) { + double[][] values = new double[between(3, 10)][]; + for (int d = 0; d < values.length; d++) { + values[d] = new double[randomBoolean() ? randomBoolean() ? 0 : 1 : between(2, 100)]; + for (int i = 0; i < values[d].length; i++) { + values[d][i] = supplier.getAsDouble(); + } + } + return values; + } + + protected SortedNumericDoubleValues wrap(double[][] values) { + return new SortedNumericDoubleValues() { + double[] current; + int i; + + @Override + public boolean advanceExact(int doc) { + i = 0; + current = values[doc]; + return current.length > 0; + } + + @Override + public int docValueCount() { + return current.length; + } + + @Override + public double nextValue() { + return current[i++]; + } + }; + } +} diff --git a/server/src/test/java/org/elasticsearch/index/fielddata/IntegralDocValuesFieldTests.java b/server/src/test/java/org/elasticsearch/index/fielddata/IntegralDocValuesFieldTests.java new file mode 100644 index 0000000000000..60fe5e706b316 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/fielddata/IntegralDocValuesFieldTests.java @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.index.fielddata; + +import org.elasticsearch.script.field.ByteDocValuesField; +import org.elasticsearch.script.field.IntegerDocValuesField; +import org.elasticsearch.script.field.ShortDocValuesField; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.function.LongSupplier; + +public class IntegralDocValuesFieldTests extends ESTestCase { + public void testByteField() throws IOException { + long[][] values = generate(ESTestCase::randomByte); + ByteDocValuesField byteField = new ByteDocValuesField(wrap(values), "test"); + for (int round = 0; round < 10; round++) { + int d = between(0, values.length - 1); + byteField.setNextDocId(d); + if (values[d].length > 0) { + assertEquals(values[d][0], byteField.get(Byte.MIN_VALUE)); + assertEquals(values[d][0], byteField.get(0, Byte.MIN_VALUE)); + } + assertEquals(values[d].length, byteField.size()); + for (int i = 0; i < values[d].length; i++) { + assertEquals(values[d][i], byteField.get(i, Byte.MIN_VALUE)); + } + int i = 0; + for (byte b : byteField) { + assertEquals(values[d][i++], b); + } + } + } + + public void testShortField() throws IOException { + long[][] values = generate(ESTestCase::randomShort); + ShortDocValuesField shortField = new ShortDocValuesField(wrap(values), "test"); + for (int round = 0; round < 10; round++) { + int d = between(0, values.length - 1); + shortField.setNextDocId(d); + if (values[d].length > 0) { + assertEquals(values[d][0], shortField.get(Short.MIN_VALUE)); + assertEquals(values[d][0], shortField.get(0, Short.MIN_VALUE)); + } + assertEquals(values[d].length, shortField.size()); + for (int i = 0; i < values[d].length; i++) { + assertEquals(values[d][i], shortField.get(i, Short.MIN_VALUE)); + } + int i = 0; + for (short s : shortField) { + assertEquals(values[d][i++], s); + } + } + } + + public void testIntegerField() throws IOException { + long[][] values = generate(ESTestCase::randomInt); + IntegerDocValuesField intField = new IntegerDocValuesField(wrap(values), "test"); + for (int round = 0; round < 10; round++) { + int d = between(0, values.length - 1); + intField.setNextDocId(d); + if (values[d].length > 0) { + assertEquals(values[d][0], intField.get(Integer.MIN_VALUE)); + assertEquals(values[d][0], intField.get(0, Integer.MIN_VALUE)); + } + assertEquals(values[d].length, intField.size()); + for (int i = 0; i < values[d].length; i++) { + assertEquals(values[d][i], intField.get(i, Integer.MIN_VALUE)); + } + int i = 0; + for (int ii : intField) { + assertEquals(values[d][i++], ii); + } + } + } + + protected long[][] generate(LongSupplier supplier) { + long[][] values = new long[between(3, 10)][]; + for (int d = 0; d < values.length; d++) { + values[d] = new long[randomBoolean() ? randomBoolean() ? 0 : 1 : between(2, 100)]; + for (int i = 0; i < values[d].length; i++) { + values[d][i] = supplier.getAsLong(); + } + } + return values; + } + + protected AbstractSortedNumericDocValues wrap(long[][] values) { + return new AbstractSortedNumericDocValues() { + long[] current; + int i; + + @Override + public boolean advanceExact(int doc) { + i = 0; + current = values[doc]; + return current.length > 0; + } + + @Override + public int docValueCount() { + return current.length; + } + + @Override + public long nextValue() { + return current[i++]; + } + }; + } +} diff --git a/server/src/test/java/org/elasticsearch/index/fielddata/ScriptDocValuesLongsTests.java b/server/src/test/java/org/elasticsearch/index/fielddata/ScriptDocValuesLongsTests.java index e2460614e275a..dd36a5ad02977 100644 --- a/server/src/test/java/org/elasticsearch/index/fielddata/ScriptDocValuesLongsTests.java +++ b/server/src/test/java/org/elasticsearch/index/fielddata/ScriptDocValuesLongsTests.java @@ -9,7 +9,7 @@ package org.elasticsearch.index.fielddata; import org.elasticsearch.index.fielddata.ScriptDocValues.Longs; -import org.elasticsearch.index.fielddata.ScriptDocValues.LongsSupplier; +import org.elasticsearch.script.field.LongDocValuesField; import org.elasticsearch.test.ESTestCase; import java.io.IOException; @@ -24,16 +24,19 @@ public void testLongs() throws IOException { } } - Longs longs = wrap(values); + LongDocValuesField longField = wrap(values); + Longs longs = (Longs) longField.getScriptDocValues(); for (int round = 0; round < 10; round++) { int d = between(0, values.length - 1); - longs.getSupplier().setNextDocId(d); + longField.setNextDocId(d); if (values[d].length > 0) { assertEquals(values[d][0], longs.getValue()); assertEquals(values[d][0], (long) longs.get(0)); + assertEquals(values[d][0], longField.get(Long.MIN_VALUE)); + assertEquals(values[d][0], longField.get(0, Long.MIN_VALUE)); } else { - Exception e = expectThrows(IllegalStateException.class, () -> longs.getValue()); + Exception e = expectThrows(IllegalStateException.class, longs::getValue); assertEquals( "A document doesn't have a value for a field! " + "Use doc[].size()==0 to check if a document is missing a field!", @@ -46,9 +49,10 @@ public void testLongs() throws IOException { e.getMessage() ); } - assertEquals(values[d].length, longs.size()); + assertEquals(values[d].length, longField.size()); for (int i = 0; i < values[d].length; i++) { assertEquals(values[d][i], longs.get(i).longValue()); + assertEquals(values[d][i], longField.get(i, Long.MIN_VALUE)); } Exception e = expectThrows(UnsupportedOperationException.class, () -> longs.add(100L)); @@ -56,8 +60,8 @@ public void testLongs() throws IOException { } } - private Longs wrap(long[][] values) { - return new Longs(new LongsSupplier(new AbstractSortedNumericDocValues() { + private LongDocValuesField wrap(long[][] values) { + return new LongDocValuesField(new AbstractSortedNumericDocValues() { long[] current; int i; @@ -77,6 +81,6 @@ public int docValueCount() { public long nextValue() { return current[i++]; } - })); + }, "test"); } } From 07df2f4a2ff9b6e427f912955f2e0d82cfa18666 Mon Sep 17 00:00:00 2001 From: Stuart Tettemer Date: Tue, 30 Nov 2021 13:38:51 -0600 Subject: [PATCH 3/3] toString float, javadoc widening comment, remove unhelpful javadoc --- .../test/painless/50_script_doc_values.yml | 6 +++--- .../script/field/ByteDocValuesField.java | 18 +++++------------- .../script/field/DoubleDocValuesField.java | 10 ---------- .../script/field/FloatDocValuesField.java | 10 ---------- .../script/field/IntegerDocValuesField.java | 10 ---------- .../script/field/LongDocValuesField.java | 10 ---------- .../script/field/ShortDocValuesField.java | 18 +++++------------- 7 files changed, 13 insertions(+), 69 deletions(-) diff --git a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml index 2f08212b823b3..40c081cb0ca9b 100644 --- a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml +++ b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/50_script_doc_values.yml @@ -758,7 +758,7 @@ setup: script_fields: field: script: - source: "Float.toString(field('float').get(-1))" + source: "field('float').get(-1).toString()" # toString to avoid making this a double - match: { hits.hits.0.fields.field.0: "3.1415927" } - match: { hits.hits.1.fields.field.0: "-1.0" } - match: { hits.hits.2.fields.field.0: "1.123" } @@ -771,7 +771,7 @@ setup: script_fields: field: script: - source: "float defaultFloat = 7.8f; Float.toString(field('float').get(1, defaultFloat))" + source: "float defaultFloat = 7.8f; field('float').get(1, defaultFloat).toString()" - match: { hits.hits.0.fields.field.0: "7.8" } - match: { hits.hits.1.fields.field.0: "7.8" } - match: { hits.hits.2.fields.field.0: "2.234" } @@ -784,7 +784,7 @@ setup: script_fields: field: script: - source: "Float.toString(field('float').get(1, 9.2f))" + source: "field('float').get(1, 9.2f).toString()" - match: { hits.hits.0.fields.field.0: "9.2" } - match: { hits.hits.1.fields.field.0: "9.2" } - match: { hits.hits.2.fields.field.0: "2.234" } diff --git a/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java index d402843fcc0b5..f0552ebb83e8f 100644 --- a/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/ByteDocValuesField.java @@ -31,11 +31,6 @@ public ByteDocValuesField(SortedNumericDocValues input, String name) { this.name = name; } - /** - * Set the current document ID. - * - * @param docId - */ @Override public void setNextDocId(int docId) throws IOException { if (input.advanceExact(docId)) { @@ -98,11 +93,6 @@ public int size() { return count; } - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ @Override public Iterator iterator() { return new Iterator() { @@ -127,9 +117,11 @@ public byte get(int defaultValue) { return get(0, defaultValue); } - // constants in java and painless are ints, so letting the defaultValue be an int allows users to - // call this without casting. A byte variable will be automatically widened to an int. - // If the user does pass a value outside the range, it will be cast down to a byte. + /** + * Note: Constants in java and painless are ints, so letting the defaultValue be an int allows users to + * call this without casting. A byte variable will be automatically widened to an int. + * If the user does pass a value outside the range, it will be cast down to a byte. + */ public byte get(int index, int defaultValue) { if (isEmpty() || index < 0 || index >= count) { return (byte) defaultValue; diff --git a/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java index 34bf7564ef3f3..e2ba781a056dd 100644 --- a/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/DoubleDocValuesField.java @@ -31,11 +31,6 @@ public DoubleDocValuesField(SortedNumericDoubleValues input, String name) { this.name = name; } - /** - * Set the current document ID. - * - * @param docId - */ @Override public void setNextDocId(int docId) throws IOException { if (input.advanceExact(docId)) { @@ -98,11 +93,6 @@ public int size() { return count; } - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ @Override public Iterator iterator() { return new Iterator() { diff --git a/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java index 99a34a3d9ee9f..2245c697c993a 100644 --- a/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/FloatDocValuesField.java @@ -31,11 +31,6 @@ public FloatDocValuesField(SortedNumericDoubleValues input, String name) { this.name = name; } - /** - * Set the current document ID. - * - * @param docId - */ @Override public void setNextDocId(int docId) throws IOException { if (input.advanceExact(docId)) { @@ -98,11 +93,6 @@ public int size() { return count; } - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ @Override public Iterator iterator() { return new Iterator() { diff --git a/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java index 164234d09a26f..4269232c94755 100644 --- a/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/IntegerDocValuesField.java @@ -31,11 +31,6 @@ public IntegerDocValuesField(SortedNumericDocValues input, String name) { this.name = name; } - /** - * Set the current document ID. - * - * @param docId - */ @Override public void setNextDocId(int docId) throws IOException { if (input.advanceExact(docId)) { @@ -98,11 +93,6 @@ public int size() { return count; } - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ @Override public Iterator iterator() { return new Iterator() { diff --git a/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java index 4ce0b7c888b6d..0ff7b9197a503 100644 --- a/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/LongDocValuesField.java @@ -31,11 +31,6 @@ public LongDocValuesField(SortedNumericDocValues input, String name) { this.name = name; } - /** - * Set the current document ID. - * - * @param docId - */ @Override public void setNextDocId(int docId) throws IOException { if (input.advanceExact(docId)) { @@ -98,11 +93,6 @@ public int size() { return count; } - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ @Override public Iterator iterator() { return new Iterator() { diff --git a/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java index c83f451f0a214..b77596112a88b 100644 --- a/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/ShortDocValuesField.java @@ -31,11 +31,6 @@ public ShortDocValuesField(SortedNumericDocValues input, String name) { this.name = name; } - /** - * Set the current document ID. - * - * @param docId - */ @Override public void setNextDocId(int docId) throws IOException { if (input.advanceExact(docId)) { @@ -98,11 +93,6 @@ public int size() { return count; } - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ @Override public Iterator iterator() { return new Iterator() { @@ -127,9 +117,11 @@ public short get(int defaultValue) { return get(0, defaultValue); } - // constants in java and painless are ints, so letting the defaultValue be an int allows users to - // call this without casting. A short variable will be automatically widened to an int. - // If the user does pass a value outside the range, it will be cast down to a short. + /** + * Note: Constants in java and painless are ints, so letting the defaultValue be an int allows users to + * call this without casting. A short variable will be automatically widened to an int. + * If the user does pass a value outside the range, it will be cast down to a short. + */ public short get(int index, int defaultValue) { if (isEmpty() || index < 0 || index >= count) { return (short) defaultValue;