Skip to content

Commit

Permalink
Script: fields API for x-pack constant_keyword (elastic#82292)
Browse files Browse the repository at this point in the history
* Script: fields API for x-pack constant_keyword

Adds the fields API for the constant_keyword field mapper

Moves implementation to `AbstractKeywordDocValuesField`, allowing
code sharing with `KeywordDocValuesField`.

API:
```
field('const').get('default')
field('const').get(0, 'default')
```

Refs: elastic#79105
  • Loading branch information
stu-elastic authored and astefan committed Jan 7, 2022
1 parent d77b6ce commit a0b2cdc
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 123 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/82292.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 82292
summary: "Script: fields API for x-pack `constant_keyword`"
area: Infra/Scripting
type: enhancement
issues: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* 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.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;

import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class AbstractKeywordDocValuesField implements DocValuesField<String>, ScriptDocValues.Supplier<String> {

protected final SortedBinaryDocValues input;
protected final String name;

protected BytesRefBuilder[] values = new BytesRefBuilder[0];
protected int count;

// used for backwards compatibility for old-style "doc" access
// as a delegate to this field class
protected ScriptDocValues.Strings strings = null;

public AbstractKeywordDocValuesField(SortedBinaryDocValues input, String name) {
this.input = input;
this.name = name;
}

@Override
public void setNextDocId(int docId) throws IOException {
if (input.advanceExact(docId)) {
resize(input.docValueCount());
for (int i = 0; i < count; i++) {
// We need to make a copy here, because BytesBinaryDVLeafFieldData's SortedBinaryDocValues
// implementation reuses the returned BytesRef. Otherwise we would end up with the same BytesRef
// instance for all slots in the values array.
values[i].copyBytes(input.nextValue());
}
} else {
resize(0);
}
}

private void resize(int newSize) {
count = newSize;
assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?";
if (newSize > values.length) {
final int oldLength = values.length;
values = ArrayUtil.grow(values, count);
for (int i = oldLength; i < values.length; ++i) {
values[i] = new BytesRefBuilder();
}
}
}

@Override
public ScriptDocValues<String> getScriptDocValues() {
if (strings == null) {
strings = new ScriptDocValues.Strings(this);
}

return strings;
}

// this method is required to support the Boolean return values
// for the old-style "doc" access in ScriptDocValues
@Override
public String getInternal(int index) {
return bytesToString(values[index].toBytesRef());
}

protected String bytesToString(BytesRef bytesRef) {
return bytesRef.utf8ToString();
}

@Override
public String getName() {
return name;
}

@Override
public boolean isEmpty() {
return count == 0;
}

@Override
public int size() {
return count;
}

public String get(String defaultValue) {
return get(0, defaultValue);
}

public String get(int index, String defaultValue) {
if (isEmpty() || index < 0 || index >= count) {
return defaultValue;
}

return bytesToString(values[index].toBytesRef());
}

@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;

@Override
public boolean hasNext() {
return index < count;
}

@Override
public String next() {
if (hasNext() == false) {
throw new NoSuchElementException();
}
return bytesToString(values[index++].toBytesRef());
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,124 +8,10 @@

package org.elasticsearch.script.field;

import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;

import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class KeywordDocValuesField implements DocValuesField<String>, ScriptDocValues.Supplier<String> {

private final SortedBinaryDocValues input;
private final String name;

private BytesRefBuilder[] values = new BytesRefBuilder[0];
private int count;

// used for backwards compatibility for old-style "doc" access
// as a delegate to this field class
private ScriptDocValues.Strings strings = null;

public class KeywordDocValuesField extends AbstractKeywordDocValuesField {
public KeywordDocValuesField(SortedBinaryDocValues input, String name) {
this.input = input;
this.name = name;
}

@Override
public void setNextDocId(int docId) throws IOException {
if (input.advanceExact(docId)) {
resize(input.docValueCount());
for (int i = 0; i < count; i++) {
// We need to make a copy here, because BytesBinaryDVLeafFieldData's SortedBinaryDocValues
// implementation reuses the returned BytesRef. Otherwise we would end up with the same BytesRef
// instance for all slots in the values array.
values[i].copyBytes(input.nextValue());
}
} else {
resize(0);
}
}

private void resize(int newSize) {
count = newSize;
assert count >= 0 : "size must be positive (got " + count + "): likely integer overflow?";
if (newSize > values.length) {
final int oldLength = values.length;
values = ArrayUtil.grow(values, count);
for (int i = oldLength; i < values.length; ++i) {
values[i] = new BytesRefBuilder();
}
}
}

@Override
public ScriptDocValues<String> getScriptDocValues() {
if (strings == null) {
strings = new ScriptDocValues.Strings(this);
}

return strings;
}

// this method is required to support the Boolean return values
// for the old-style "doc" access in ScriptDocValues
@Override
public String getInternal(int index) {
return bytesToString(values[index].toBytesRef());
}

protected String bytesToString(BytesRef bytesRef) {
return bytesRef.utf8ToString();
}

@Override
public String getName() {
return name;
}

@Override
public boolean isEmpty() {
return count == 0;
}

@Override
public int size() {
return count;
}

public String get(String defaultValue) {
return get(0, defaultValue);
}

public String get(int index, String defaultValue) {
if (isEmpty() || index < 0 || index >= count) {
return defaultValue;
}

return bytesToString(values[index].toBytesRef());
}

@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;

@Override
public boolean hasNext() {
return index < count;
}

@Override
public String next() {
if (hasNext() == false) {
throw new NoSuchElementException();
}
return bytesToString(values[index++].toBytesRef());
}
};
super(input, name);
}
}
4 changes: 3 additions & 1 deletion x-pack/plugin/mapper-constant-keyword/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
apply plugin: 'elasticsearch.internal-es-plugin'
apply plugin: 'elasticsearch.internal-cluster-test'
apply plugin: 'elasticsearch.internal-yaml-rest-test'

esplugin {
name 'constant-keyword'
description 'Module for the constant-keyword field type, which is a specialization of keyword for the case when all documents have the same value.'
classname 'org.elasticsearch.xpack.constantkeyword.ConstantKeywordMapperPlugin'
extendedPlugins = ['x-pack-core']
extendedPlugins = ['x-pack-core', 'lang-painless']
}
archivesBaseName = 'x-pack-constant-keyword'

dependencies {
compileOnly project(':modules:lang-painless:spi')
compileOnly project(path: xpackModule('core'))
internalClusterTestImplementation(testArtifact(project(xpackModule('core'))))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.constantkeyword;

import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.script.field.AbstractKeywordDocValuesField;

public class ConstantKeywordDocValuesField extends AbstractKeywordDocValuesField {
public ConstantKeywordDocValuesField(SortedBinaryDocValues input, String name) {
super(input, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.constantkeyword;

import org.elasticsearch.painless.spi.PainlessExtension;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.spi.WhitelistLoader;
import org.elasticsearch.script.ScriptContext;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.Collections.singletonList;
import static org.elasticsearch.script.ScriptModule.CORE_CONTEXTS;

public class ConstantKeywordPainlessExtension implements PainlessExtension {
private static final Whitelist WHITELIST = WhitelistLoader.loadFromResourceFiles(
ConstantKeywordPainlessExtension.class,
"org.elasticsearch.xpack.constantkeyword.txt"
);

@Override
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
List<Whitelist> whitelist = singletonList(WHITELIST);
Map<ScriptContext<?>, List<Whitelist>> contextWhitelists = new HashMap<>(CORE_CONTEXTS.size());
for (ScriptContext<?> scriptContext : CORE_CONTEXTS.values()) {
contextWhitelists.put(scriptContext, whitelist);
}
return contextWhitelists;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.fielddata.FieldData;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData;
import org.elasticsearch.index.mapper.ConstantFieldType;
import org.elasticsearch.index.mapper.DocumentParserContext;
Expand All @@ -37,10 +36,10 @@
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.ValueFetcher;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.field.DelegateDocValuesField;
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xpack.constantkeyword.ConstantKeywordDocValuesField;
import org.elasticsearch.xpack.core.termsenum.action.SimpleTermCountEnum;
import org.elasticsearch.xpack.core.termsenum.action.TermCount;

Expand Down Expand Up @@ -137,10 +136,7 @@ public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, S
value,
name(),
CoreValuesSourceType.KEYWORD,
(dv, n) -> new DelegateDocValuesField(
new ScriptDocValues.Strings(new ScriptDocValues.StringsSupplier(FieldData.toString(dv))),
n
)
(dv, n) -> new ConstantKeywordDocValuesField(FieldData.toString(dv), n)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.elasticsearch.xpack.constantkeyword.ConstantKeywordPainlessExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# 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.
#

class org.elasticsearch.xpack.constantkeyword.ConstantKeywordDocValuesField @dynamic_type {
String get(String)
String get(int, String)
}
Loading

0 comments on commit a0b2cdc

Please sign in to comment.