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 cf8f1ff2560b5..29ea5c4fe61d7 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 @@ -23,3 +23,9 @@ class org.elasticsearch.script.field.DelegateDocValuesField @dynamic_type { def getValue(def) List getValues() } + +class org.elasticsearch.script.field.BooleanDocValuesField @dynamic_type { + boolean getValue(boolean) + boolean getValue(int, boolean) + boolean[] getValues() +} 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 29d526a7c7187..34b519370ae32 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 @@ -58,6 +58,12 @@ setup: scaled_float: 3.14 token_count: count all these words please + - do: + index: + index: test + id: 2 + body: {} + - do: indices.refresh: {} @@ -67,6 +73,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -77,12 +84,80 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: source: "doc['boolean'].value" - match: { hits.hits.0.fields.field.0: true } + - do: + search: + rest_total_hits_as_int: true + body: + query: { term: { _id: 1 } } + script_fields: + field: + script: + source: "field('boolean').getValue(false)" + - match: { hits.hits.0.fields.field.0: true } + + - do: + search: + rest_total_hits_as_int: true + body: + query: { term: { _id: 1 } } + script_fields: + field: + script: + source: "field('boolean').getValue(false)" + - match: { hits.hits.0.fields.field.0: true } + + - do: + search: + rest_total_hits_as_int: true + body: + query: { term: { _id: 2 } } + script_fields: + field: + script: + source: "field('boolean').getValue(false)" + - match: { hits.hits.0.fields.field.0: false } + + - do: + search: + rest_total_hits_as_int: true + body: + query: { term: { _id: 1 } } + script_fields: + field: + script: + source: "field('boolean').getValue(1, false)" + - match: { hits.hits.0.fields.field.0: false } + + - do: + search: + rest_total_hits_as_int: true + body: + query: { term: { _id: 1 } } + script_fields: + field: + script: + source: "field('boolean').getValues().length" + - match: { hits.hits.0.fields.field.0: 1 } + + - do: + search: + rest_total_hits_as_int: true + body: + query: { term: { _id: 2 } } + script_fields: + field: + script: + source: "field('boolean').getValues().length" + - match: { hits.hits.0.fields.field.0: 0 } + + --- "date": - skip: @@ -92,6 +167,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -102,6 +178,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -114,6 +191,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -125,6 +203,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -136,6 +215,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: centroid: script: @@ -147,6 +227,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: bbox: script: @@ -160,6 +241,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: topLeft: script: @@ -176,6 +258,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: type: script: @@ -186,6 +269,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: width: script: @@ -202,6 +286,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -212,6 +297,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -224,6 +310,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -234,6 +321,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -249,6 +337,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -259,6 +348,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -271,6 +361,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -281,6 +372,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -293,6 +385,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -303,6 +396,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -315,6 +409,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -325,6 +420,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -337,6 +433,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -347,6 +444,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -359,6 +457,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -369,6 +468,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -381,6 +481,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -391,6 +492,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -403,6 +505,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -413,6 +516,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -425,6 +529,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: @@ -435,6 +540,7 @@ setup: search: rest_total_hits_as_int: true body: + query: { term: { _id: 1 } } script_fields: field: script: diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java index af5d9038478f2..e45ef46d60da2 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java @@ -17,13 +17,13 @@ import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.time.DateUtils; import org.elasticsearch.geometry.utils.Geohash; +import org.elasticsearch.script.field.BooleanDocValuesField; import java.io.IOException; import java.time.Instant; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.AbstractList; -import java.util.Arrays; import java.util.Comparator; import java.util.function.UnaryOperator; @@ -454,60 +454,31 @@ public GeoBoundingBox getBoundingBox() { public static final class Booleans extends ScriptDocValues { - private final SortedNumericDocValues in; - private boolean[] values = new boolean[0]; - private int count; + private final BooleanDocValuesField booleanDocValuesField; - public Booleans(SortedNumericDocValues in) { - this.in = in; + public Booleans(BooleanDocValuesField booleanDocValuesField) { + this.booleanDocValuesField = booleanDocValuesField; } @Override public void setNextDocId(int docId) throws IOException { - if (in.advanceExact(docId)) { - resize(in.docValueCount()); - for (int i = 0; i < count; i++) { - values[i] = in.nextValue() == 1; - } - } else { - resize(0); - } - } - - /** - * Set the {@link #size()} and ensure that the {@link #values} array can - * store at least that many entries. - */ - protected void resize(int newSize) { - count = newSize; - values = grow(values, count); + throw new UnsupportedOperationException(); } public boolean getValue() { + throwIfEmpty(); return get(0); } @Override public Boolean get(int index) { - if (count == 0) { - throw new IllegalStateException( - "A document doesn't have a value for a field! " - + "Use doc[].size()==0 to check if a document is missing a field!" - ); - } - return values[index]; + throwIfEmpty(); + return booleanDocValuesField.getInternalValues()[index]; } @Override public int size() { - return count; - } - - private static boolean[] grow(boolean[] array, int minSize) { - assert minSize >= 0 : "size must be positive (got " + minSize + "): likely integer overflow?"; - if (array.length < minSize) { - return Arrays.copyOf(array, ArrayUtil.oversize(minSize, 1)); - } else return array; + return booleanDocValuesField.size(); } } diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/plain/LeafLongFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LeafLongFieldData.java index 5219575befd05..fc4014f8365d4 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/plain/LeafLongFieldData.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LeafLongFieldData.java @@ -16,6 +16,7 @@ import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; +import org.elasticsearch.script.field.BooleanDocValuesField; import org.elasticsearch.script.field.DelegateDocValuesField; import org.elasticsearch.script.field.DocValuesField; import org.elasticsearch.search.DocValueFormat; @@ -56,7 +57,7 @@ public final DocValuesField getScriptField(String name) { name ); case BOOLEAN: - return new DelegateDocValuesField(new ScriptDocValues.Booleans(getLongValues()), name); + return new BooleanDocValuesField(getLongValues(), name); default: return new DelegateDocValuesField(new ScriptDocValues.Longs(getLongValues()), name); } diff --git a/server/src/main/java/org/elasticsearch/script/field/BooleanDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/BooleanDocValuesField.java new file mode 100644 index 0000000000000..10fa6fa03e23a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/BooleanDocValuesField.java @@ -0,0 +1,121 @@ +/* + * 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.Arrays; + +public class BooleanDocValuesField implements DocValuesField { + + private final SortedNumericDocValues input; + private final String name; + + private boolean[] values = new boolean[0]; + private int count; + + private ScriptDocValues.Booleans booleansSDV = null; + + public BooleanDocValuesField(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() == 1; + } + } else { + resize(0); + } + } + + + private void resize(int newSize) { + count = newSize; + + assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?"; + if (values.length < count) { + values = Arrays.copyOf(values, ArrayUtil.oversize(count, 1)); + } + } + + /** + * 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 (booleansSDV == null) { + booleansSDV = new ScriptDocValues.Booleans(this); + } + + return booleansSDV; + } + + /** + * 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; + } + + public boolean[] getValues() { + if (isEmpty()) { + return new boolean[0]; + } + return Arrays.copyOf(values, count); + } + + public boolean getValue(boolean defaultValue) { + return getValue(0, defaultValue); + } + + public boolean getValue(int index, boolean defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return defaultValue; + } + + return values[index]; + } + + public boolean[] getInternalValues() { + return values; + } + +} diff --git a/server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptFieldTypeTests.java index f99b6ee3e1390..e26d35ab643da 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/BooleanScriptFieldTypeTests.java @@ -38,6 +38,7 @@ import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptCompiler; import org.elasticsearch.script.ScriptType; +import org.elasticsearch.script.field.BooleanDocValuesField; import org.elasticsearch.search.MultiValueMode; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xcontent.XContentParser; @@ -137,6 +138,23 @@ public double execute(ExplanationHolder explanation) { }; } }, searchContext.lookup(), 2.5f, "test", 0, Version.CURRENT)), equalTo(1)); + assertThat(searcher.count(new ScriptScoreQuery(new MatchAllDocsQuery(), new Script("test"), new ScoreScript.LeafFactory() { + @Override + public boolean needs_score() { + return false; + } + + @Override + public ScoreScript newInstance(DocReader docReader) { + return new ScoreScript(Map.of(), searchContext.lookup(), docReader) { + @Override + public double execute(ExplanationHolder explanation) { + BooleanDocValuesField booleans = (BooleanDocValuesField) field("test"); + return booleans.getValues()[0] ? 3 : 0; + } + }; + } + }, searchContext.lookup(), 2.5f, "test", 0, Version.CURRENT)), equalTo(1)); } } }