Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add boolean values script fields #60830

Merged
merged 7 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;

import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.index.mapper.BooleanFieldMapper;
import org.elasticsearch.index.mapper.IpFieldMapper;
import org.elasticsearch.index.mapper.KeywordFieldMapper;
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
Expand Down Expand Up @@ -183,6 +184,7 @@ private static String painlessToLoadFromSource(String name, String type) {

private static final Map<String, String> PAINLESS_TO_EMIT = Map.ofEntries(
// TODO implement dates against the parser
Map.entry(BooleanFieldMapper.CONTENT_TYPE, "value(parse(value));"),
Map.entry(
NumberType.DOUBLE.typeName(),
"value(value instanceof Number ? ((Number) value).doubleValue() : Double.parseDouble(value.toString()));"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.runtimefields;

import org.apache.lucene.index.LeafReaderContext;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.spi.WhitelistLoader;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptFactory;
import org.elasticsearch.search.lookup.SearchLookup;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public abstract class BooleanScriptFieldScript extends AbstractScriptFieldScript {
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("boolean_script_field", Factory.class);

static List<Whitelist> whitelist() {
return List.of(WhitelistLoader.loadFromResourceFiles(RuntimeFieldsPainlessExtension.class, "boolean_whitelist.txt"));
}

public static final String[] PARAMETERS = {};

public interface Factory extends ScriptFactory {
LeafFactory newFactory(Map<String, Object> params, SearchLookup searchLookup);
}

public interface LeafFactory {
BooleanScriptFieldScript newInstance(LeafReaderContext ctx) throws IOException;
}

private int trues;
private int falses;

public BooleanScriptFieldScript(Map<String, Object> params, SearchLookup searchLookup, LeafReaderContext ctx) {
super(params, searchLookup, ctx);
}

/**
* Execute the script for the provided {@code docId}.
*/
public final void runForDoc(int docId) {
trues = 0;
falses = 0;
setDocument(docId);
execute();
}

/**
* How many {@code true} values were returned for this document.
*/
public final int trues() {
return trues;
}

/**
* How many {@code false} values were returned for this document.
*/
public final int falses() {
return falses;
}

private void collectValue(boolean v) {
if (v) {
trues++;
} else {
falses++;
}
}

public static boolean parse(Object str) {
return Booleans.parseBoolean(str.toString());
}

public static class Value {
private final BooleanScriptFieldScript script;

public Value(BooleanScriptFieldScript script) {
this.script = script;
}

public void value(boolean v) {
script.collectValue(v);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public Map<String, Mapper.TypeParser> getMappers() {
@Override
public List<ScriptContext<?>> getContexts() {
return List.of(
BooleanScriptFieldScript.CONTEXT,
DateScriptFieldScript.CONTEXT,
DoubleScriptFieldScript.CONTEXT,
IpScriptFieldScript.CONTEXT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class RuntimeFieldsPainlessExtension implements PainlessExtension {
@Override
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
return Map.ofEntries(
Map.entry(BooleanScriptFieldScript.CONTEXT, BooleanScriptFieldScript.whitelist()),
Map.entry(DateScriptFieldScript.CONTEXT, DateScriptFieldScript.whitelist()),
Map.entry(DoubleScriptFieldScript.CONTEXT, DoubleScriptFieldScript.whitelist()),
Map.entry(IpScriptFieldScript.CONTEXT, IpScriptFieldScript.whitelist()),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.runtimefields.fielddata;

import org.elasticsearch.index.fielddata.AbstractSortedNumericDocValues;
import org.elasticsearch.xpack.runtimefields.BooleanScriptFieldScript;

import java.io.IOException;

public final class ScriptBooleanDocValues extends AbstractSortedNumericDocValues {
private final BooleanScriptFieldScript script;
private int cursor;

ScriptBooleanDocValues(BooleanScriptFieldScript script) {
this.script = script;
}

@Override
public boolean advanceExact(int docId) {
script.runForDoc(docId);
cursor = 0;
return script.trues() > 0 || script.falses() > 0;
}

@Override
public long nextValue() throws IOException {
// Emit all false values before all true values
return cursor++ < script.falses() ? 0 : 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must say that multiple values for booleans look really weird, especially trying to summarize multiple values into one, and re-sorting them. Is this behaviour documented anywhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it is, though it is what happens when you use doc values for out of the box booleans.

}

@Override
public int docValueCount() {
return script.trues() + script.falses();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.runtimefields.fielddata;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.plain.LeafLongFieldData;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import org.elasticsearch.xpack.runtimefields.BooleanScriptFieldScript;

import java.io.IOException;

public final class ScriptBooleanFieldData extends IndexNumericFieldData {

public static class Builder implements IndexFieldData.Builder {
private final String name;
private final BooleanScriptFieldScript.LeafFactory leafFactory;

public Builder(String name, BooleanScriptFieldScript.LeafFactory leafFactory) {
this.name = name;
this.leafFactory = leafFactory;
}

@Override
public ScriptBooleanFieldData build(IndexFieldDataCache cache, CircuitBreakerService breakerService, MapperService mapperService) {
return new ScriptBooleanFieldData(name, leafFactory);
}
}

private final String fieldName;
private final BooleanScriptFieldScript.LeafFactory leafFactory;

private ScriptBooleanFieldData(String fieldName, BooleanScriptFieldScript.LeafFactory leafFactory) {
this.fieldName = fieldName;
this.leafFactory = leafFactory;
}

@Override
public String getFieldName() {
return fieldName;
}

@Override
public ValuesSourceType getValuesSourceType() {
return CoreValuesSourceType.BOOLEAN;
}

@Override
public ScriptBooleanLeafFieldData load(LeafReaderContext context) {
try {
return loadDirect(context);
} catch (Exception e) {
throw ExceptionsHelper.convertToElastic(e);
}
}

@Override
public ScriptBooleanLeafFieldData loadDirect(LeafReaderContext context) throws IOException {
return new ScriptBooleanLeafFieldData(new ScriptBooleanDocValues(leafFactory.newInstance(context)));
}

@Override
public NumericType getNumericType() {
return NumericType.BOOLEAN;
}

@Override
protected boolean sortRequiresCustomComparator() {
return true;
}

public static class ScriptBooleanLeafFieldData extends LeafLongFieldData {
private final ScriptBooleanDocValues scriptBooleanDocValues;

ScriptBooleanLeafFieldData(ScriptBooleanDocValues scriptBooleanDocValues) {
super(0, NumericType.BOOLEAN);
this.scriptBooleanDocValues = scriptBooleanDocValues;
}

@Override
public SortedNumericDocValues getLongValues() {
return scriptBooleanDocValues;
}

@Override
public void close() {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import org.elasticsearch.common.time.DateFormatter;
import org.elasticsearch.common.util.LocaleUtils;
import org.elasticsearch.index.mapper.BooleanFieldMapper;
import org.elasticsearch.index.mapper.DateFieldMapper;
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.IpFieldMapper;
Expand All @@ -19,6 +20,7 @@
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.xpack.runtimefields.BooleanScriptFieldScript;
import org.elasticsearch.xpack.runtimefields.DateScriptFieldScript;
import org.elasticsearch.xpack.runtimefields.DoubleScriptFieldScript;
import org.elasticsearch.xpack.runtimefields.IpScriptFieldScript;
Expand Down Expand Up @@ -83,6 +85,20 @@ protected String contentType() {
public static class Builder extends ParametrizedFieldMapper.Builder {

static final Map<String, BiFunction<Builder, BuilderContext, AbstractScriptMappedFieldType>> FIELD_TYPE_RESOLVER = Map.of(
BooleanFieldMapper.CONTENT_TYPE,
(builder, context) -> {
builder.formatAndLocaleNotSupported();
BooleanScriptFieldScript.Factory factory = builder.scriptCompiler.compile(
builder.script.getValue(),
BooleanScriptFieldScript.CONTEXT
);
return new ScriptBooleanMappedFieldType(
builder.buildFullName(context),
builder.script.getValue(),
factory,
builder.meta.getValue()
);
},
DateFieldMapper.CONTENT_TYPE,
(builder, context) -> {
DateScriptFieldScript.Factory factory = builder.scriptCompiler.compile(
Expand Down
Loading