Check for runtime field loops in queries (backport of #61927) (#62421)

We were checking for loops in queries before, but we had an "off by one"
error where we wouldn't notice the "top level" runtime field when
detecting a loop. So the error message would be wrong.

I also caught a few bugs with query generation caused by missing
`@Override` annotations and fixed a few of them. There is a bug with
`regexp` queries with match options that I'm not fixing in this PR but
will get to later.

Relates to #59332
This commit is contained in:
Nik Everett 2020-09-15 17:24:19 -04:00 committed by GitHub
parent 0a7f335215
commit e5ad3a41f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 417 additions and 291 deletions

View File

@ -12,6 +12,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
import org.apache.lucene.search.spans.SpanQuery;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.TriFunction;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.common.unit.Fuzziness;
@ -19,6 +20,7 @@ import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.TextSearchInfo;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.lookup.SearchLookup;
import java.io.IOException;
import java.time.ZoneId;
@ -31,12 +33,19 @@ import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;
/**
* Abstract base {@linkplain MappedFieldType} for scripted fields.
*/
abstract class AbstractScriptMappedFieldType extends MappedFieldType {
abstract class AbstractScriptMappedFieldType<LeafFactory> extends MappedFieldType {
protected final Script script;
private final TriFunction<String, Map<String, Object>, SearchLookup, LeafFactory> factory;
AbstractScriptMappedFieldType(String name, Script script, Map<String, String> meta) {
AbstractScriptMappedFieldType(
String name,
Script script,
TriFunction<String, Map<String, Object>, SearchLookup, LeafFactory> factory,
Map<String, String> meta
) {
super(name, false, false, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
this.script = script;
this.factory = factory;
}
protected abstract String runtimeType();
@ -61,6 +70,26 @@ abstract class AbstractScriptMappedFieldType extends MappedFieldType {
return true;
}
/**
* Create a script leaf factory.
*/
protected final LeafFactory leafFactory(SearchLookup searchLookup) {
return factory.apply(name(), script.getParams(), searchLookup);
}
/**
* Create a script leaf factory for queries.
*/
protected final LeafFactory leafFactory(QueryShardContext context) {
/*
* Forking here causes us to count this field in the field data loop
* detection code as though we were resolving field data for this field.
* We're not, but running the query is close enough.
*/
return leafFactory(context.lookup().forkAndTrackFieldReferences(name()));
}
@Override
public abstract Query termsQuery(List<?> values, QueryShardContext context);
@Override
@ -149,8 +178,15 @@ abstract class AbstractScriptMappedFieldType extends MappedFieldType {
}
private String unsupported(String query, String supported) {
String thisField = "[" + name() + "] which is of type [script] with runtime_type [" + runtimeType() + "]";
return "Can only use " + query + " queries on " + supported + " fields - not on " + thisField;
return String.format(
Locale.ROOT,
"Can only use %s queries on %s fields - not on [%s] which is of type [%s] with runtime_type [%s]",
query,
supported,
name(),
RuntimeFieldMapper.CONTENT_TYPE,
runtimeType()
);
}
protected final void checkAllowExpensiveQueries(QueryShardContext context) {

View File

@ -51,7 +51,7 @@ public final class RuntimeFieldMapper extends ParametrizedFieldMapper {
protected RuntimeFieldMapper(
String simpleName,
AbstractScriptMappedFieldType mappedFieldType,
AbstractScriptMappedFieldType<?> mappedFieldType,
MultiFields multiFields,
CopyTo copyTo,
String runtimeType,
@ -86,7 +86,7 @@ public final class RuntimeFieldMapper extends ParametrizedFieldMapper {
public static class Builder extends ParametrizedFieldMapper.Builder {
static final Map<String, BiFunction<Builder, BuilderContext, AbstractScriptMappedFieldType>> FIELD_TYPE_RESOLVER =
static final Map<String, BiFunction<Builder, BuilderContext, AbstractScriptMappedFieldType<?>>> FIELD_TYPE_RESOLVER =
org.elasticsearch.common.collect.Map.of(BooleanFieldMapper.CONTENT_TYPE, (builder, context) -> {
builder.formatAndLocaleNotSupported();
BooleanScriptFieldScript.Factory factory = builder.scriptCompiler.compile(
@ -199,7 +199,7 @@ public final class RuntimeFieldMapper extends ParametrizedFieldMapper {
private final Parameter<String> format = Parameter.stringParam(
"format",
true,
mapper -> ((AbstractScriptMappedFieldType) mapper.fieldType()).format(),
mapper -> ((AbstractScriptMappedFieldType<?>) mapper.fieldType()).format(),
null
).setSerializer((b, n, v) -> {
if (v != null && false == v.equals(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.pattern())) {
@ -211,7 +211,7 @@ public final class RuntimeFieldMapper extends ParametrizedFieldMapper {
true,
() -> null,
(n, c, o) -> o == null ? null : LocaleUtils.parse(o.toString()),
mapper -> ((AbstractScriptMappedFieldType) mapper.fieldType()).formatLocale()
mapper -> ((AbstractScriptMappedFieldType<?>) mapper.fieldType()).formatLocale()
).setSerializer((b, n, v) -> {
if (v != null && false == v.equals(Locale.ROOT)) {
b.field(n, v.toString());
@ -232,7 +232,7 @@ public final class RuntimeFieldMapper extends ParametrizedFieldMapper {
@Override
public RuntimeFieldMapper build(BuilderContext context) {
BiFunction<Builder, BuilderContext, AbstractScriptMappedFieldType> fieldTypeResolver = Builder.FIELD_TYPE_RESOLVER.get(
BiFunction<Builder, BuilderContext, AbstractScriptMappedFieldType<?>> fieldTypeResolver = Builder.FIELD_TYPE_RESOLVER.get(
runtimeType.getValue()
);
if (fieldTypeResolver == null) {

View File

@ -27,12 +27,9 @@ import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class ScriptBooleanMappedFieldType extends AbstractScriptMappedFieldType {
private final BooleanScriptFieldScript.Factory scriptFactory;
public class ScriptBooleanMappedFieldType extends AbstractScriptMappedFieldType<BooleanScriptFieldScript.LeafFactory> {
ScriptBooleanMappedFieldType(String name, Script script, BooleanScriptFieldScript.Factory scriptFactory, Map<String, String> meta) {
super(name, script, meta);
this.scriptFactory = scriptFactory;
super(name, script, scriptFactory::newFactory, meta);
}
@Override
@ -71,14 +68,10 @@ public class ScriptBooleanMappedFieldType extends AbstractScriptMappedFieldType
return new ScriptBooleanFieldData.Builder(name(), leafFactory(searchLookup.get()));
}
private BooleanScriptFieldScript.LeafFactory leafFactory(SearchLookup searchLookup) {
return scriptFactory.newFactory(name(), script.getParams(), searchLookup);
}
@Override
public Query existsQuery(QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new BooleanScriptFieldExistsQuery(script, leafFactory(context.lookup()), name());
return new BooleanScriptFieldExistsQuery(script, leafFactory(context), name());
}
@Override
@ -149,7 +142,7 @@ public class ScriptBooleanMappedFieldType extends AbstractScriptMappedFieldType
@Override
public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new BooleanScriptFieldTermQuery(script, leafFactory(context.lookup()), name(), toBoolean(value));
return new BooleanScriptFieldTermQuery(script, leafFactory(context), name(), toBoolean(value));
}
@Override
@ -176,11 +169,11 @@ public class ScriptBooleanMappedFieldType extends AbstractScriptMappedFieldType
return existsQuery(context);
}
checkAllowExpensiveQueries(context);
return new BooleanScriptFieldTermQuery(script, leafFactory(context.lookup()), name(), true);
return new BooleanScriptFieldTermQuery(script, leafFactory(context), name(), true);
}
if (falseAllowed) {
checkAllowExpensiveQueries(context);
return new BooleanScriptFieldTermQuery(script, leafFactory(context.lookup()), name(), false);
return new BooleanScriptFieldTermQuery(script, leafFactory(context), name(), false);
}
return new MatchNoDocsQuery("neither true nor false allowed");
}

View File

@ -37,8 +37,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
private final DateScriptFieldScript.Factory scriptFactory;
public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType<DateScriptFieldScript.LeafFactory> {
private final DateFormatter dateTimeFormatter;
ScriptDateMappedFieldType(
@ -48,8 +47,7 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
DateFormatter dateTimeFormatter,
Map<String, String> meta
) {
super(name, script, meta);
this.scriptFactory = scriptFactory;
super(name, script, (n, params, ctx) -> scriptFactory.newFactory(n, params, ctx, dateTimeFormatter), meta);
this.dateTimeFormatter = dateTimeFormatter;
}
@ -84,10 +82,6 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
return new ScriptDateFieldData.Builder(name(), leafFactory(lookup.get()));
}
private DateScriptFieldScript.LeafFactory leafFactory(SearchLookup lookup) {
return scriptFactory.newFactory(name(), script.getParams(), lookup, dateTimeFormatter);
}
@Override
public Query distanceFeatureQuery(Object origin, String pivot, float boost, QueryShardContext context) {
checkAllowExpensiveQueries(context);
@ -103,7 +97,7 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
TimeValue pivotTime = TimeValue.parseTimeValue(pivot, "distance_feature.pivot");
return new LongScriptFieldDistanceFeatureQuery(
script,
leafFactory(context.lookup())::newInstance,
leafFactory(context)::newInstance,
name(),
originLong,
pivotTime.getMillis(),
@ -115,7 +109,7 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
@Override
public Query existsQuery(QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new LongScriptFieldExistsQuery(script, leafFactory(context.lookup())::newInstance, name());
return new LongScriptFieldExistsQuery(script, leafFactory(context)::newInstance, name());
}
@Override
@ -139,7 +133,7 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
parser,
context,
DateFieldMapper.Resolution.MILLISECONDS,
(l, u) -> new LongScriptFieldRangeQuery(script, leafFactory(context.lookup())::newInstance, name(), l, u)
(l, u) -> new LongScriptFieldRangeQuery(script, leafFactory(context)::newInstance, name(), l, u)
);
}
@ -155,7 +149,7 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
DateFieldMapper.Resolution.MILLISECONDS
);
checkAllowExpensiveQueries(context);
return new LongScriptFieldTermQuery(script, leafFactory(context.lookup())::newInstance, name(), l);
return new LongScriptFieldTermQuery(script, leafFactory(context)::newInstance, name(), l);
});
}
@ -179,7 +173,7 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType {
);
}
checkAllowExpensiveQueries(context);
return new LongScriptFieldTermsQuery(script, leafFactory(context.lookup())::newInstance, name(), terms);
return new LongScriptFieldTermsQuery(script, leafFactory(context)::newInstance, name(), terms);
});
}

View File

@ -29,12 +29,9 @@ import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class ScriptDoubleMappedFieldType extends AbstractScriptMappedFieldType {
private final DoubleScriptFieldScript.Factory scriptFactory;
public class ScriptDoubleMappedFieldType extends AbstractScriptMappedFieldType<DoubleScriptFieldScript.LeafFactory> {
ScriptDoubleMappedFieldType(String name, Script script, DoubleScriptFieldScript.Factory scriptFactory, Map<String, String> meta) {
super(name, script, meta);
this.scriptFactory = scriptFactory;
super(name, script, scriptFactory::newFactory, meta);
}
@Override
@ -63,14 +60,10 @@ public class ScriptDoubleMappedFieldType extends AbstractScriptMappedFieldType {
return new ScriptDoubleFieldData.Builder(name(), leafFactory(searchLookup.get()));
}
private DoubleScriptFieldScript.LeafFactory leafFactory(SearchLookup searchLookup) {
return scriptFactory.newFactory(name(), script.getParams(), searchLookup);
}
@Override
public Query existsQuery(QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new DoubleScriptFieldExistsQuery(script, leafFactory(context.lookup()), name());
return new DoubleScriptFieldExistsQuery(script, leafFactory(context), name());
}
@Override
@ -89,14 +82,14 @@ public class ScriptDoubleMappedFieldType extends AbstractScriptMappedFieldType {
upperTerm,
includeLower,
includeUpper,
(l, u) -> new DoubleScriptFieldRangeQuery(script, leafFactory(context.lookup()), name(), l, u)
(l, u) -> new DoubleScriptFieldRangeQuery(script, leafFactory(context), name(), l, u)
);
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new DoubleScriptFieldTermQuery(script, leafFactory(context.lookup()), name(), NumberType.objectToDouble(value));
return new DoubleScriptFieldTermQuery(script, leafFactory(context), name(), NumberType.objectToDouble(value));
}
@Override
@ -109,6 +102,6 @@ public class ScriptDoubleMappedFieldType extends AbstractScriptMappedFieldType {
terms.add(Double.doubleToLongBits(NumberType.objectToDouble(value)));
}
checkAllowExpensiveQueries(context);
return new DoubleScriptFieldTermsQuery(script, leafFactory(context.lookup()), name(), terms);
return new DoubleScriptFieldTermsQuery(script, leafFactory(context), name(), terms);
}
}

View File

@ -37,15 +37,9 @@ import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
public final class ScriptIpMappedFieldType extends AbstractScriptMappedFieldType {
private final Script script;
private final IpScriptFieldScript.Factory scriptFactory;
public final class ScriptIpMappedFieldType extends AbstractScriptMappedFieldType<IpScriptFieldScript.LeafFactory> {
ScriptIpMappedFieldType(String name, Script script, IpScriptFieldScript.Factory scriptFactory, Map<String, String> meta) {
super(name, script, meta);
this.script = script;
this.scriptFactory = scriptFactory;
super(name, script, scriptFactory::newFactory, meta);
}
@Override
@ -79,14 +73,10 @@ public final class ScriptIpMappedFieldType extends AbstractScriptMappedFieldType
return new ScriptIpFieldData.Builder(name(), leafFactory(searchLookup.get()));
}
private IpScriptFieldScript.LeafFactory leafFactory(SearchLookup searchLookup) {
return scriptFactory.newFactory(name(), script.getParams(), searchLookup);
}
@Override
public Query existsQuery(QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new IpScriptFieldExistsQuery(script, leafFactory(context.lookup()), name());
return new IpScriptFieldExistsQuery(script, leafFactory(context), name());
}
@Override
@ -107,7 +97,7 @@ public final class ScriptIpMappedFieldType extends AbstractScriptMappedFieldType
includeUpper,
(lower, upper) -> new IpScriptFieldRangeQuery(
script,
leafFactory(context.lookup()),
leafFactory(context),
name(),
new BytesRef(InetAddressPoint.encode(lower)),
new BytesRef(InetAddressPoint.encode(upper))
@ -119,14 +109,18 @@ public final class ScriptIpMappedFieldType extends AbstractScriptMappedFieldType
public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
if (value instanceof InetAddress) {
return InetAddressPoint.newExactQuery(name(), (InetAddress) value);
return inetAddressQuery((InetAddress) value, context);
}
String term = BytesRefs.toString(value);
if (term.contains("/")) {
return cidrQuery(term, context);
}
InetAddress address = InetAddresses.forString(term);
return new IpScriptFieldTermQuery(script, leafFactory(context.lookup()), name(), new BytesRef(InetAddressPoint.encode(address)));
return inetAddressQuery(address, context);
}
private Query inetAddressQuery(InetAddress address, QueryShardContext context) {
return new IpScriptFieldTermQuery(script, leafFactory(context), name(), new BytesRef(InetAddressPoint.encode(address)));
}
@Override
@ -149,7 +143,7 @@ public final class ScriptIpMappedFieldType extends AbstractScriptMappedFieldType
}
cidrQueries.add(cidrQuery(term, context));
}
Query termsQuery = new IpScriptFieldTermsQuery(script, leafFactory(context.lookup()), name(), terms);
Query termsQuery = new IpScriptFieldTermsQuery(script, leafFactory(context), name(), terms);
if (cidrQueries == null) {
return termsQuery;
}
@ -176,6 +170,6 @@ public final class ScriptIpMappedFieldType extends AbstractScriptMappedFieldType
// Force the terms into IPv6
BytesRef lowerBytes = new BytesRef(InetAddressPoint.encode(InetAddressPoint.decode(lower)));
BytesRef upperBytes = new BytesRef(InetAddressPoint.encode(InetAddressPoint.decode(upper)));
return new IpScriptFieldRangeQuery(script, leafFactory(context.lookup()), name(), lowerBytes, upperBytes);
return new IpScriptFieldRangeQuery(script, leafFactory(context), name(), lowerBytes, upperBytes);
}
}

View File

@ -36,15 +36,9 @@ import java.util.function.Supplier;
import static java.util.stream.Collectors.toSet;
public final class ScriptKeywordMappedFieldType extends AbstractScriptMappedFieldType {
private final Script script;
private final StringScriptFieldScript.Factory scriptFactory;
public final class ScriptKeywordMappedFieldType extends AbstractScriptMappedFieldType<StringScriptFieldScript.LeafFactory> {
ScriptKeywordMappedFieldType(String name, Script script, StringScriptFieldScript.Factory scriptFactory, Map<String, String> meta) {
super(name, script, meta);
this.script = script;
this.scriptFactory = scriptFactory;
super(name, script, scriptFactory::newFactory, meta);
}
@Override
@ -67,14 +61,10 @@ public final class ScriptKeywordMappedFieldType extends AbstractScriptMappedFiel
return new ScriptStringFieldData.Builder(name(), leafFactory(searchLookup.get()));
}
private StringScriptFieldScript.LeafFactory leafFactory(SearchLookup searchLookup) {
return scriptFactory.newFactory(name(), script.getParams(), searchLookup);
}
@Override
public Query existsQuery(QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldExistsQuery(script, leafFactory(context.lookup()), name());
return new StringScriptFieldExistsQuery(script, leafFactory(context), name());
}
@Override
@ -89,7 +79,7 @@ public final class ScriptKeywordMappedFieldType extends AbstractScriptMappedFiel
checkAllowExpensiveQueries(context);
return StringScriptFieldFuzzyQuery.build(
script,
leafFactory(context.lookup()),
leafFactory(context),
name(),
BytesRefs.toString(Objects.requireNonNull(value)),
fuzziness.asDistance(BytesRefs.toString(value)),
@ -101,7 +91,7 @@ public final class ScriptKeywordMappedFieldType extends AbstractScriptMappedFiel
@Override
public Query prefixQuery(String value, RewriteMethod method, org.elasticsearch.index.query.QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldPrefixQuery(script, leafFactory(context.lookup()), name(), value);
return new StringScriptFieldPrefixQuery(script, leafFactory(context), name(), value);
}
@Override
@ -117,7 +107,7 @@ public final class ScriptKeywordMappedFieldType extends AbstractScriptMappedFiel
checkAllowExpensiveQueries(context);
return new StringScriptFieldRangeQuery(
script,
leafFactory(context.lookup()),
leafFactory(context),
name(),
BytesRefs.toString(Objects.requireNonNull(lowerTerm)),
BytesRefs.toString(Objects.requireNonNull(upperTerm)),
@ -139,30 +129,25 @@ public final class ScriptKeywordMappedFieldType extends AbstractScriptMappedFiel
if (matchFlags != 0) {
throw new IllegalArgumentException("Match flags not yet implemented [" + matchFlags + "]");
}
return new StringScriptFieldRegexpQuery(script, leafFactory(context.lookup()), name(), value, syntaxFlags, maxDeterminizedStates);
return new StringScriptFieldRegexpQuery(script, leafFactory(context), name(), value, syntaxFlags, maxDeterminizedStates);
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldTermQuery(
script,
leafFactory(context.lookup()),
name(),
BytesRefs.toString(Objects.requireNonNull(value))
);
return new StringScriptFieldTermQuery(script, leafFactory(context), name(), BytesRefs.toString(Objects.requireNonNull(value)));
}
@Override
public Query termsQuery(List<?> values, QueryShardContext context) {
checkAllowExpensiveQueries(context);
Set<String> terms = values.stream().map(v -> BytesRefs.toString(Objects.requireNonNull(v))).collect(toSet());
return new StringScriptFieldTermsQuery(script, leafFactory(context.lookup()), name(), terms);
return new StringScriptFieldTermsQuery(script, leafFactory(context), name(), terms);
}
@Override
public Query wildcardQuery(String value, RewriteMethod method, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldWildcardQuery(script, leafFactory(context.lookup()), name(), value);
return new StringScriptFieldWildcardQuery(script, leafFactory(context), name(), value);
}
}

View File

@ -29,12 +29,9 @@ import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType {
private final LongScriptFieldScript.Factory scriptFactory;
public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType<LongScriptFieldScript.LeafFactory> {
ScriptLongMappedFieldType(String name, Script script, LongScriptFieldScript.Factory scriptFactory, Map<String, String> meta) {
super(name, script, meta);
this.scriptFactory = scriptFactory;
super(name, script, scriptFactory::newFactory, meta);
}
@Override
@ -63,14 +60,10 @@ public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType {
return new ScriptLongFieldData.Builder(name(), leafFactory(searchLookup.get()));
}
private LongScriptFieldScript.LeafFactory leafFactory(SearchLookup searchLookup) {
return scriptFactory.newFactory(name(), script.getParams(), searchLookup);
}
@Override
public Query existsQuery(QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new LongScriptFieldExistsQuery(script, leafFactory(context.lookup())::newInstance, name());
return new LongScriptFieldExistsQuery(script, leafFactory(context)::newInstance, name());
}
@Override
@ -89,7 +82,7 @@ public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType {
upperTerm,
includeLower,
includeUpper,
(l, u) -> new LongScriptFieldRangeQuery(script, leafFactory(context.lookup())::newInstance, name(), l, u)
(l, u) -> new LongScriptFieldRangeQuery(script, leafFactory(context)::newInstance, name(), l, u)
);
}
@ -99,12 +92,7 @@ public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType {
return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part");
}
checkAllowExpensiveQueries(context);
return new LongScriptFieldTermQuery(
script,
leafFactory(context.lookup())::newInstance,
name(),
NumberType.objectToLong(value, true)
);
return new LongScriptFieldTermQuery(script, leafFactory(context)::newInstance, name(), NumberType.objectToLong(value, true));
}
@Override
@ -123,6 +111,6 @@ public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType {
return Queries.newMatchNoDocsQuery("All values have a decimal part");
}
checkAllowExpensiveQueries(context);
return new LongScriptFieldTermsQuery(script, leafFactory(context.lookup())::newInstance, name(), terms);
return new LongScriptFieldTermsQuery(script, leafFactory(context)::newInstance, name(), terms);
}
}

View File

@ -43,7 +43,7 @@ abstract class AbstractNonTextScriptMappedFieldTypeTestCase extends AbstractScri
equalTo(
"Can only use "
+ queryName
+ " queries on keyword and text fields - not on [test] which is of type [script] with runtime_type ["
+ " queries on keyword and text fields - not on [test] which is of type [runtime] with runtime_type ["
+ runtimeType()
+ "]"
)
@ -57,7 +57,7 @@ abstract class AbstractNonTextScriptMappedFieldTypeTestCase extends AbstractScri
equalTo(
"Can only use "
+ queryName
+ " queries on keyword, text and wildcard fields - not on [test] which is of type [script] with runtime_type ["
+ " queries on keyword, text and wildcard fields - not on [test] which is of type [runtime] with runtime_type ["
+ runtimeType()
+ "]"
)

View File

@ -7,13 +7,17 @@
package org.elasticsearch.xpack.runtimefields.mapper;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Query;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.function.BiConsumer;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.any;
@ -22,7 +26,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
abstract class AbstractScriptMappedFieldTypeTestCase extends ESTestCase {
protected abstract AbstractScriptMappedFieldType simpleMappedFieldType() throws IOException;
protected abstract MappedFieldType simpleMappedFieldType() throws IOException;
protected abstract MappedFieldType loopFieldType() throws IOException;
protected abstract String runtimeType();
@ -38,26 +44,20 @@ abstract class AbstractScriptMappedFieldTypeTestCase extends ESTestCase {
@SuppressWarnings("unused")
public abstract void testExistsQuery() throws IOException;
@SuppressWarnings("unused")
public abstract void testExistsQueryIsExpensive() throws IOException;
@SuppressWarnings("unused")
public abstract void testRangeQuery() throws IOException;
@SuppressWarnings("unused")
public abstract void testRangeQueryIsExpensive() throws IOException;
protected abstract Query randomRangeQuery(MappedFieldType ft, QueryShardContext ctx);
@SuppressWarnings("unused")
public abstract void testTermQuery() throws IOException;
@SuppressWarnings("unused")
public abstract void testTermQueryIsExpensive() throws IOException;
protected abstract Query randomTermQuery(MappedFieldType ft, QueryShardContext ctx);
@SuppressWarnings("unused")
public abstract void testTermsQuery() throws IOException;
@SuppressWarnings("unused")
public abstract void testTermsQueryIsExpensive() throws IOException;
protected abstract Query randomTermsQuery(MappedFieldType ft, QueryShardContext ctx);
protected static QueryShardContext mockContext() {
return mockContext(true);
@ -67,7 +67,7 @@ abstract class AbstractScriptMappedFieldTypeTestCase extends ESTestCase {
return mockContext(allowExpensiveQueries, null);
}
protected static QueryShardContext mockContext(boolean allowExpensiveQueries, AbstractScriptMappedFieldType mappedFieldType) {
protected static QueryShardContext mockContext(boolean allowExpensiveQueries, MappedFieldType mappedFieldType) {
MapperService mapperService = mock(MapperService.class);
when(mapperService.fieldType(anyString())).thenReturn(mappedFieldType);
QueryShardContext context = mock(QueryShardContext.class);
@ -86,6 +86,14 @@ abstract class AbstractScriptMappedFieldTypeTestCase extends ESTestCase {
return context;
}
public void testExistsQueryIsExpensive() throws IOException {
checkExpensiveQuery(MappedFieldType::existsQuery);
}
public void testExistsQueryInLoop() throws IOException {
checkLoop(MappedFieldType::existsQuery);
}
public void testRangeQueryWithShapeRelationIsError() throws IOException {
Exception e = expectThrows(
IllegalArgumentException.class,
@ -97,6 +105,30 @@ abstract class AbstractScriptMappedFieldTypeTestCase extends ESTestCase {
);
}
public void testRangeQueryIsExpensive() throws IOException {
checkExpensiveQuery(this::randomRangeQuery);
}
public void testRangeQueryInLoop() throws IOException {
checkLoop(this::randomRangeQuery);
}
public void testTermQueryIsExpensive() throws IOException {
checkExpensiveQuery(this::randomTermQuery);
}
public void testTermQueryInLoop() throws IOException {
checkLoop(this::randomTermQuery);
}
public void testTermsQueryIsExpensive() throws IOException {
checkExpensiveQuery(this::randomTermsQuery);
}
public void testTermsQueryInLoop() throws IOException {
checkLoop(this::randomTermsQuery);
}
public void testPhraseQueryIsError() {
assertQueryOnlyOnText("phrase", () -> simpleMappedFieldType().phraseQuery(null, 1, false));
}
@ -120,7 +152,7 @@ abstract class AbstractScriptMappedFieldTypeTestCase extends ESTestCase {
equalTo(
"Can only use "
+ queryName
+ " queries on text fields - not on [test] which is of type [script] with runtime_type ["
+ " queries on text fields - not on [test] which is of type [runtime] with runtime_type ["
+ runtimeType()
+ "]"
)
@ -130,4 +162,17 @@ abstract class AbstractScriptMappedFieldTypeTestCase extends ESTestCase {
protected String readSource(IndexReader reader, int docId) throws IOException {
return reader.document(docId).getBinaryValue("_source").utf8ToString();
}
protected final void checkExpensiveQuery(BiConsumer<MappedFieldType, QueryShardContext> queryBuilder) throws IOException {
Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(simpleMappedFieldType(), mockContext(false)));
assertThat(
e.getMessage(),
equalTo("queries cannot be executed against [runtime] fields while [search.allow_expensive_queries] is set to [false].")
);
}
protected final void checkLoop(BiConsumer<MappedFieldType, QueryShardContext> queryBuilder) throws IOException {
Exception e = expectThrows(IllegalArgumentException.class, () -> queryBuilder.accept(loopFieldType(), mockContext()));
assertThat(e.getMessage(), equalTo("Cyclic dependency detected while resolving runtime fields: test -> test"));
}
}

View File

@ -52,6 +52,7 @@ import java.util.Set;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockito.Mockito.mock;
public class RuntimeFieldMapperTests extends MapperTestCase {
@ -316,7 +317,12 @@ public class RuntimeFieldMapperTests extends MapperTestCase {
IllegalArgumentException iae = expectThrows(
IllegalArgumentException.class,
() -> config.buildIndexSort(
field -> new ScriptKeywordMappedFieldType(field, new Script(""), null, Collections.emptyMap()),
field -> new ScriptKeywordMappedFieldType(
field,
new Script(""),
mock(StringScriptFieldScript.Factory.class),
Collections.emptyMap()
),
(fieldType, searchLookupSupplier) -> indexFieldDataService.getForField(fieldType, "index", searchLookupSupplier)
)
);

View File

@ -24,7 +24,6 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
@ -61,7 +60,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
@ -171,10 +169,6 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
}
@Override
public void testExistsQueryIsExpensive() throws IOException {
checkExpensiveQuery(ScriptBooleanMappedFieldType::existsQuery);
}
public void testRangeQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(org.elasticsearch.common.collect.List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
@ -213,11 +207,7 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
}
}
public void testRangeQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.rangeQuery(true, true, true, true, null, null, null, ctx));
checkExpensiveQuery((ft, ctx) -> ft.rangeQuery(false, true, true, true, null, null, null, ctx));
checkExpensiveQuery((ft, ctx) -> ft.rangeQuery(false, true, false, true, null, null, null, ctx));
// These are not expensive queries
public void testRangeQueryDegeneratesIntoNotExpensive() throws IOException {
assertThat(
simpleMappedFieldType().rangeQuery(true, true, false, false, null, null, null, mockContext()),
instanceOf(MatchNoDocsQuery.class)
@ -226,6 +216,30 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
simpleMappedFieldType().rangeQuery(false, false, false, false, null, null, null, mockContext()),
instanceOf(MatchNoDocsQuery.class)
);
// Even if the running the field would blow up because it loops the query *still* just returns none.
assertThat(
loopFieldType().rangeQuery(true, true, false, false, null, null, null, mockContext()),
instanceOf(MatchNoDocsQuery.class)
);
assertThat(
loopFieldType().rangeQuery(false, false, false, false, null, null, null, mockContext()),
instanceOf(MatchNoDocsQuery.class)
);
}
@Override
protected Query randomRangeQuery(MappedFieldType ft, QueryShardContext ctx) {
// Builds a random range query that doesn't degenerate into match none
switch (randomInt(2)) {
case 0:
return ft.rangeQuery(true, true, true, true, null, null, null, ctx);
case 1:
return ft.rangeQuery(false, true, true, true, null, null, null, ctx);
case 2:
return ft.rangeQuery(false, true, false, true, null, null, null, ctx);
default:
throw new UnsupportedOperationException();
}
}
@Override
@ -264,8 +278,8 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
}
@Override
public void testTermQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termQuery(randomBoolean(), ctx));
protected Query randomTermQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termQuery(randomBoolean(), ctx);
}
@Override
@ -329,18 +343,27 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
}
}
@Override
public void testTermsQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(org.elasticsearch.common.collect.List.of(true), ctx));
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(org.elasticsearch.common.collect.List.of(false), ctx));
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(org.elasticsearch.common.collect.List.of(false, true), ctx));
// This is not an expensive query
public void randomTermsQueryDegeneratesIntoMatchNone() throws IOException {
assertThat(
simpleMappedFieldType().termsQuery(org.elasticsearch.common.collect.List.of(), mockContext()),
instanceOf(MatchNoDocsQuery.class)
);
}
@Override
protected Query randomTermsQuery(MappedFieldType ft, QueryShardContext ctx) {
switch (randomInt(2)) {
case 0:
return ft.termsQuery(org.elasticsearch.common.collect.List.of(true), ctx);
case 1:
return ft.termsQuery(org.elasticsearch.common.collect.List.of(false), ctx);
case 2:
return ft.termsQuery(org.elasticsearch.common.collect.List.of(false, true), ctx);
default:
throw new UnsupportedOperationException();
}
}
public void testDualingQueries() throws IOException {
BooleanFieldMapper ootb = new BooleanFieldMapper.Builder("foo").build(new BuilderContext(Settings.EMPTY, new ContentPath()));
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
@ -419,6 +442,11 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
return build("read_foo", org.elasticsearch.common.collect.Map.of());
}
@Override
protected MappedFieldType loopFieldType() throws IOException {
return build("loop", org.elasticsearch.common.collect.Map.of());
}
@Override
protected String runtimeType() {
return "boolean";
@ -485,6 +513,12 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
}
}
};
case "loop":
return (fieldName, params, lookup) -> {
// Indicate that this script wants the field call "test", which *is* the name of this field
lookup.forkAndTrackFieldReferences("test");
throw new IllegalStateException("shoud have thrown on the line above");
};
default:
throw new IllegalArgumentException("unsupported script [" + code + "]");
}
@ -501,13 +535,4 @@ public class ScriptBooleanMappedFieldTypeTests extends AbstractNonTextScriptMapp
return new ScriptBooleanMappedFieldType("test", script, factory, emptyMap());
}
}
private void checkExpensiveQuery(BiConsumer<ScriptBooleanMappedFieldType, QueryShardContext> queryBuilder) throws IOException {
ScriptBooleanMappedFieldType ft = simpleMappedFieldType();
Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(ft, mockContext(false)));
assertThat(
e.getMessage(),
equalTo("queries cannot be executed against [runtime] fields while [search.allow_expensive_queries] is set to [false].")
);
}
}

View File

@ -26,7 +26,6 @@ import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
@ -59,7 +58,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.arrayWithSize;
@ -93,7 +91,7 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
);
DateFieldMapper.DateFieldType indexed = new DateFieldMapper.DateFieldType("test", formatter);
for (int i = 0; i < 100; i++) {
long date = randomLongBetween(0, 3000000000000L); // Maxes out in the year 2065
long date = randomDate();
assertThat(indexed.docValueFormat(null, null).format(date), equalTo(scripted.docValueFormat(null, null).format(date)));
String format = randomDateFormatterPattern();
assertThat(indexed.docValueFormat(format, null).format(date), equalTo(scripted.docValueFormat(format, null).format(date)));
@ -246,7 +244,15 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
public void testDistanceFeatureQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.distanceFeatureQuery(randomLong(), randomAlphaOfLength(5), randomFloat(), ctx));
checkExpensiveQuery(this::randomDistanceFeatureQuery);
}
public void testDistanceFeatureQueryInLoop() throws IOException {
checkLoop(this::randomDistanceFeatureQuery);
}
private Query randomDistanceFeatureQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.distanceFeatureQuery(randomDate(), randomTimeValue(), randomFloat(), ctx);
}
@Override
@ -263,11 +269,6 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
}
@Override
public void testExistsQueryIsExpensive() throws IOException {
checkExpensiveQuery(ScriptDateMappedFieldType::existsQuery);
}
@Override
public void testRangeQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
@ -345,10 +346,15 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
@Override
public void testRangeQueryIsExpensive() throws IOException {
checkExpensiveQuery(
(ft, ctx) -> ft.rangeQuery(randomLong(), randomLong(), randomBoolean(), randomBoolean(), null, null, null, ctx)
);
protected Query randomRangeQuery(MappedFieldType ft, QueryShardContext ctx) {
long d1 = randomDate();
long d2 = randomValueOtherThan(d1, ScriptDateMappedFieldTypeTests::randomDate);
if (d1 > d2) {
long backup = d2;
d2 = d1;
d1 = backup;
}
return ft.rangeQuery(d1, d2, randomBoolean(), randomBoolean(), null, null, null, ctx);
}
@Override
@ -382,8 +388,8 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
@Override
public void testTermQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termQuery(0, ctx));
protected Query randomTermQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termQuery(randomDate(), ctx);
}
@Override
@ -448,8 +454,8 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
@Override
public void testTermsQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(org.elasticsearch.common.collect.List.of(0), ctx));
protected Query randomTermsQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termsQuery(randomList(1, 100, ScriptDateMappedFieldTypeTests::randomDate), ctx);
}
@Override
@ -457,6 +463,11 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
return build("read_timestamp");
}
@Override
protected MappedFieldType loopFieldType() throws IOException {
return build("loop");
}
private ScriptDateMappedFieldType coolFormattedFieldType() throws IOException {
return build(simpleMappedFieldType().script, DateFormatter.forPattern("yyyy-MM-dd(-■_■)HH:mm:ss.SSSz||epoch_millis"));
}
@ -537,6 +548,12 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
}
};
case "loop":
return (fieldName, params, lookup, formatter) -> {
// Indicate that this script wants the field call "test", which *is* the name of this field
lookup.forkAndTrackFieldReferences("test");
throw new IllegalStateException("shoud have thrown on the line above");
};
default:
throw new IllegalArgumentException("unsupported script [" + code + "]");
}
@ -554,13 +571,8 @@ public class ScriptDateMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
}
private void checkExpensiveQuery(BiConsumer<ScriptDateMappedFieldType, QueryShardContext> queryBuilder) throws IOException {
ScriptDateMappedFieldType ft = simpleMappedFieldType();
Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(ft, mockContext(false)));
assertThat(
e.getMessage(),
equalTo("queries cannot be executed against [runtime] fields while [search.allow_expensive_queries] is set to [false].")
);
private static long randomDate() {
return Math.abs(randomLong() % (2 * (long) 10e11)); // 1970-01-01T00:00:00Z - 2033-05-18T05:33:20.000+02:00
}
private void checkBadDate(ThrowingRunnable queryBuilder) {

View File

@ -14,6 +14,7 @@ import org.apache.lucene.search.Collector;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LeafCollector;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Sort;
@ -21,7 +22,6 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
import org.elasticsearch.common.settings.Settings;
@ -48,7 +48,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.equalTo;
@ -163,10 +162,6 @@ public class ScriptDoubleMappedFieldTypeTests extends AbstractNonTextScriptMappe
}
@Override
public void testExistsQueryIsExpensive() throws IOException {
checkExpensiveQuery(ScriptDoubleMappedFieldType::existsQuery);
}
public void testRangeQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(org.elasticsearch.common.collect.List.of(new StoredField("_source", new BytesRef("{\"foo\": [1]}"))));
@ -186,10 +181,9 @@ public class ScriptDoubleMappedFieldTypeTests extends AbstractNonTextScriptMappe
}
}
public void testRangeQueryIsExpensive() throws IOException {
checkExpensiveQuery(
(ft, ctx) -> ft.rangeQuery(randomLong(), randomLong(), randomBoolean(), randomBoolean(), null, null, null, ctx)
);
@Override
protected Query randomRangeQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.rangeQuery(randomLong(), randomLong(), randomBoolean(), randomBoolean(), null, null, null, ctx);
}
@Override
@ -211,8 +205,8 @@ public class ScriptDoubleMappedFieldTypeTests extends AbstractNonTextScriptMappe
}
@Override
public void testTermQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termQuery(randomLong(), ctx));
protected Query randomTermQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termQuery(randomLong(), ctx);
}
@Override
@ -247,8 +241,8 @@ public class ScriptDoubleMappedFieldTypeTests extends AbstractNonTextScriptMappe
}
@Override
public void testTermsQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(org.elasticsearch.common.collect.List.of(randomLong()), ctx));
protected Query randomTermsQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termsQuery(org.elasticsearch.common.collect.List.of(randomLong()), ctx);
}
@Override
@ -256,6 +250,11 @@ public class ScriptDoubleMappedFieldTypeTests extends AbstractNonTextScriptMappe
return build("read_foo", org.elasticsearch.common.collect.Map.of());
}
@Override
protected MappedFieldType loopFieldType() throws IOException {
return build("loop", org.elasticsearch.common.collect.Map.of());
}
@Override
protected String runtimeType() {
return "double";
@ -312,6 +311,12 @@ public class ScriptDoubleMappedFieldTypeTests extends AbstractNonTextScriptMappe
}
}
};
case "loop":
return (fieldName, params, lookup) -> {
// Indicate that this script wants the field call "test", which *is* the name of this field
lookup.forkAndTrackFieldReferences("test");
throw new IllegalStateException("shoud have thrown on the line above");
};
default:
throw new IllegalArgumentException("unsupported script [" + code + "]");
}
@ -328,13 +333,4 @@ public class ScriptDoubleMappedFieldTypeTests extends AbstractNonTextScriptMappe
return new ScriptDoubleMappedFieldType("test", script, factory, emptyMap());
}
}
private void checkExpensiveQuery(BiConsumer<ScriptDoubleMappedFieldType, QueryShardContext> queryBuilder) throws IOException {
ScriptDoubleMappedFieldType ft = simpleMappedFieldType();
Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(ft, mockContext(false)));
assertThat(
e.getMessage(),
equalTo("queries cannot be executed against [runtime] fields while [search.allow_expensive_queries] is set to [false].")
);
}
}

View File

@ -14,6 +14,7 @@ import org.apache.lucene.search.Collector;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LeafCollector;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Sort;
@ -21,11 +22,11 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.plugins.ScriptPlugin;
import org.elasticsearch.script.ScoreScript;
@ -50,7 +51,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.equalTo;
@ -194,11 +194,6 @@ public class ScriptIpMappedFieldTypeTests extends AbstractScriptMappedFieldTypeT
}
}
@Override
public void testExistsQueryIsExpensive() throws IOException {
checkExpensiveQuery(ScriptIpMappedFieldType::existsQuery);
}
@Override
public void testRangeQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
@ -222,8 +217,8 @@ public class ScriptIpMappedFieldTypeTests extends AbstractScriptMappedFieldTypeT
}
@Override
public void testRangeQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.rangeQuery("192.0.0.0", "200.0.0.0", randomBoolean(), randomBoolean(), null, null, null, ctx));
protected Query randomRangeQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.rangeQuery("192.0.0.0", "200.0.0.0", randomBoolean(), randomBoolean(), null, null, null, ctx);
}
@Override
@ -248,8 +243,8 @@ public class ScriptIpMappedFieldTypeTests extends AbstractScriptMappedFieldTypeT
}
@Override
public void testTermQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termQuery(randomIp(randomBoolean()), ctx));
protected Query randomTermQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termQuery(randomIp(randomBoolean()), ctx);
}
@Override
@ -290,8 +285,8 @@ public class ScriptIpMappedFieldTypeTests extends AbstractScriptMappedFieldTypeT
}
@Override
public void testTermsQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(randomList(100, () -> randomAlphaOfLengthBetween(1, 1000)), ctx));
protected Query randomTermsQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termsQuery(randomList(100, () -> randomIp(randomBoolean())), ctx);
}
@Override
@ -299,6 +294,11 @@ public class ScriptIpMappedFieldTypeTests extends AbstractScriptMappedFieldTypeT
return build("read_foo", org.elasticsearch.common.collect.Map.of());
}
@Override
protected MappedFieldType loopFieldType() throws IOException {
return build("loop", org.elasticsearch.common.collect.Map.of());
}
@Override
protected String runtimeType() {
return "ip";
@ -355,6 +355,12 @@ public class ScriptIpMappedFieldTypeTests extends AbstractScriptMappedFieldTypeT
}
}
};
case "loop":
return (fieldName, params, lookup) -> {
// Indicate that this script wants the field call "test", which *is* the name of this field
lookup.forkAndTrackFieldReferences("test");
throw new IllegalStateException("shoud have thrown on the line above");
};
default:
throw new IllegalArgumentException("unsupported script [" + code + "]");
}
@ -371,13 +377,4 @@ public class ScriptIpMappedFieldTypeTests extends AbstractScriptMappedFieldTypeT
return new ScriptIpMappedFieldType("test", script, factory, emptyMap());
}
}
private void checkExpensiveQuery(BiConsumer<ScriptIpMappedFieldType, QueryShardContext> queryBuilder) throws IOException {
ScriptIpMappedFieldType ft = simpleMappedFieldType();
Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(ft, mockContext(false)));
assertThat(
e.getMessage(),
equalTo("queries cannot be executed against [runtime] fields while [search.allow_expensive_queries] is set to [false].")
);
}
}

View File

@ -23,13 +23,13 @@ import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.plugins.ScriptPlugin;
@ -52,7 +52,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.equalTo;
@ -157,11 +156,6 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
}
@Override
public void testExistsQueryIsExpensive() throws IOException {
checkExpensiveQuery(ScriptKeywordMappedFieldType::existsQuery);
}
public void testFuzzyQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
// No edits, matches
@ -185,15 +179,21 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
public void testFuzzyQueryIsExpensive() throws IOException {
checkExpensiveQuery(
(ft, ctx) -> ft.fuzzyQuery(
checkExpensiveQuery(this::randomFuzzyQuery);
}
public void testFuzzyQueryInLoop() throws IOException {
checkLoop(this::randomFuzzyQuery);
}
private Query randomFuzzyQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.fuzzyQuery(
randomAlphaOfLengthBetween(1, 1000),
randomFrom(Fuzziness.AUTO, Fuzziness.ZERO, Fuzziness.ONE, Fuzziness.TWO),
randomInt(),
randomInt(),
randomBoolean(),
ctx
)
);
}
@ -210,7 +210,15 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
public void testPrefixQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.prefixQuery(randomAlphaOfLengthBetween(1, 1000), null, ctx));
checkExpensiveQuery(this::randomPrefixQuery);
}
public void testPrefixQueryInLoop() throws IOException {
checkLoop(this::randomPrefixQuery);
}
private Query randomPrefixQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.prefixQuery(randomAlphaOfLengthBetween(1, 1000), null, ctx);
}
@Override
@ -230,18 +238,16 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
@Override
public void testRangeQueryIsExpensive() throws IOException {
checkExpensiveQuery(
(ft, ctx) -> ft.rangeQuery(
"a" + randomAlphaOfLengthBetween(0, 1000),
"b" + randomAlphaOfLengthBetween(0, 1000),
protected Query randomRangeQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.rangeQuery(
randomAlphaOfLengthBetween(0, 1000),
randomAlphaOfLengthBetween(0, 1000),
randomBoolean(),
randomBoolean(),
null,
null,
null,
ctx
)
);
}
@ -262,8 +268,12 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
}
public void testRegexpQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.regexpQuery(randomAlphaOfLengthBetween(1, 1000), randomInt(0xFFFF), 0, randomInt(), null, ctx));
public void testRegexpQueryInLoop() throws IOException {
checkLoop(this::randomRegexpQuery);
}
private Query randomRegexpQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.regexpQuery(randomAlphaOfLengthBetween(1, 1000), randomInt(0xFFFF), 0, Integer.MAX_VALUE, null, ctx);
}
@Override
@ -280,8 +290,8 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
@Override
public void testTermQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termQuery(randomAlphaOfLengthBetween(1, 1000), ctx));
protected Query randomTermQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termQuery(randomAlphaOfLengthBetween(1, 1000), ctx);
}
@Override
@ -302,8 +312,8 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
@Override
public void testTermsQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(randomList(100, () -> randomAlphaOfLengthBetween(1, 1000)), ctx));
protected Query randomTermsQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termsQuery(randomList(100, () -> randomAlphaOfLengthBetween(1, 1000)), ctx);
}
public void testWildcardQuery() throws IOException {
@ -318,7 +328,15 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
public void testWildcardQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.wildcardQuery(randomAlphaOfLengthBetween(1, 1000), null, ctx));
checkExpensiveQuery(this::randomWildcardQuery);
}
public void testWildcardQueryInLoop() throws IOException {
checkLoop(this::randomWildcardQuery);
}
private Query randomWildcardQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.wildcardQuery(randomAlphaOfLengthBetween(1, 1000), null, ctx);
}
public void testMatchQuery() throws IOException {
@ -340,6 +358,11 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
return build("read_foo", org.elasticsearch.common.collect.Map.of());
}
@Override
protected ScriptKeywordMappedFieldType loopFieldType() throws IOException {
return build("loop", org.elasticsearch.common.collect.Map.of());
}
@Override
protected String runtimeType() {
return "keyword";
@ -396,6 +419,12 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
}
}
};
case "loop":
return (fieldName, params, lookup) -> {
// Indicate that this script wants the field call "test", which *is* the name of this field
lookup.forkAndTrackFieldReferences("test");
throw new IllegalStateException("shoud have thrown on the line above");
};
default:
throw new IllegalArgumentException("unsupported script [" + code + "]");
}
@ -412,13 +441,4 @@ public class ScriptKeywordMappedFieldTypeTests extends AbstractScriptMappedField
return new ScriptKeywordMappedFieldType("test", script, factory, emptyMap());
}
}
private void checkExpensiveQuery(BiConsumer<ScriptKeywordMappedFieldType, QueryShardContext> queryBuilder) throws IOException {
ScriptKeywordMappedFieldType ft = simpleMappedFieldType();
Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(ft, mockContext(false)));
assertThat(
e.getMessage(),
equalTo("queries cannot be executed against [runtime] fields while [search.allow_expensive_queries] is set to [false].")
);
}
}

View File

@ -16,6 +16,7 @@ import org.apache.lucene.search.FieldDoc;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LeafCollector;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Sort;
@ -23,7 +24,6 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.common.lucene.search.function.ScriptScoreQuery;
import org.elasticsearch.common.settings.Settings;
@ -50,7 +50,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import static java.util.Collections.emptyMap;
import static org.hamcrest.Matchers.equalTo;
@ -192,11 +191,6 @@ public class ScriptLongMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
}
@Override
public void testExistsQueryIsExpensive() throws IOException {
checkExpensiveQuery(ScriptLongMappedFieldType::existsQuery);
}
@Override
public void testRangeQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
@ -215,10 +209,8 @@ public class ScriptLongMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
@Override
public void testRangeQueryIsExpensive() throws IOException {
checkExpensiveQuery(
(ft, ctx) -> ft.rangeQuery(randomLong(), randomLong(), randomBoolean(), randomBoolean(), null, null, null, ctx)
);
protected Query randomRangeQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.rangeQuery(randomLong(), randomLong(), randomBoolean(), randomBoolean(), null, null, null, ctx);
}
@Override
@ -240,8 +232,8 @@ public class ScriptLongMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
@Override
public void testTermQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termQuery(randomLong(), ctx));
protected Query randomTermQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termQuery(randomLong(), ctx);
}
@Override
@ -276,8 +268,8 @@ public class ScriptLongMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
@Override
public void testTermsQueryIsExpensive() throws IOException {
checkExpensiveQuery((ft, ctx) -> ft.termsQuery(org.elasticsearch.common.collect.List.of(randomLong()), ctx));
protected Query randomTermsQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.termsQuery(org.elasticsearch.common.collect.List.of(randomLong()), ctx);
}
@Override
@ -285,6 +277,11 @@ public class ScriptLongMappedFieldTypeTests extends AbstractNonTextScriptMappedF
return build("read_foo", Collections.emptyMap());
}
@Override
protected ScriptLongMappedFieldType loopFieldType() throws IOException {
return build("loop", org.elasticsearch.common.collect.Map.of());
}
@Override
protected String runtimeType() {
return "long";
@ -352,6 +349,12 @@ public class ScriptLongMappedFieldTypeTests extends AbstractNonTextScriptMappedF
}
}
};
case "loop":
return (fieldName, params, lookup) -> {
// Indicate that this script wants the field call "test", which *is* the name of this field
lookup.forkAndTrackFieldReferences("test");
throw new IllegalStateException("shoud have thrown on the line above");
};
default:
throw new IllegalArgumentException("unsupported script [" + code + "]");
}
@ -368,13 +371,4 @@ public class ScriptLongMappedFieldTypeTests extends AbstractNonTextScriptMappedF
return new ScriptLongMappedFieldType("test", script, factory, emptyMap());
}
}
private void checkExpensiveQuery(BiConsumer<ScriptLongMappedFieldType, QueryShardContext> queryBuilder) throws IOException {
ScriptLongMappedFieldType ft = simpleMappedFieldType();
Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(ft, mockContext(false)));
assertThat(
e.getMessage(),
equalTo("queries cannot be executed against [runtime] fields while [search.allow_expensive_queries] is set to [false].")
);
}
}

View File

@ -82,6 +82,7 @@ setup:
body:
sort: timestamp
docvalue_fields: [tight_loop]
---
"tight loop - aggs":
- do:
@ -94,6 +95,7 @@ setup:
loop:
terms:
field: tight_loop
---
"tight loop - sort":
- do:
@ -103,6 +105,17 @@ setup:
body:
sort: tight_loop
---
"tight loop - queries":
- do:
catch: '/Cyclic dependency detected while resolving runtime fields: tight_loop -> tight_loop/'
search:
index: sensor
body:
query:
match:
tight_loop: foo
---
"loose loop - aggs":
- do:
@ -134,6 +147,17 @@ setup:
sort: timestamp
docvalue_fields: [loose_loop]
---
"loose loop - queries":
- do:
catch: '/Cyclic dependency detected while resolving runtime fields: loose_loop -> lla -> llb -> llc -> loose_loop/'
search:
index: sensor
body:
query:
match:
loose_loop: foo
---
"loose loop - begins within":
- do:
@ -148,7 +172,7 @@ setup:
field: lla
---
"Max chain depth - 5 is allowed":
"Max chain depth for fetch - 5 is allowed":
- do:
search:
index: sensor
@ -159,6 +183,17 @@ setup:
- match: {hits.total.value: 1}
- match: {hits.hits.0.fields.over_max_depth_1.0: test}
---
"Max chain depth for query - 5 is allowed":
- do:
search:
index: sensor
body:
query:
match:
over_max_depth_1: test
- match: {hits.total.value: 1}
---
"Max chain depth - direct references are not counted":
- do:
@ -183,6 +218,7 @@ setup:
loop:
terms:
field: over_max_depth
---
"Max chain depth - sort":
- do:
@ -191,6 +227,7 @@ setup:
index: sensor
body:
sort: over_max_depth
---
"Max chain depth - docvalue_fields":
- do:
@ -200,3 +237,14 @@ setup:
body:
sort: timestamp
docvalue_fields: [over_max_depth]
---
"Max chain depth - query":
- do:
catch: '/Field requires resolving too many dependent fields: over_max_depth -> over_max_depth_1 -> over_max_depth_2 -> over_max_depth_3 -> over_max_depth_4 -> over_max_depth_5/'
search:
index: sensor
body:
query:
match:
over_max_depth: foo