From e5ad3a41f10d330542ed2859cafae150ff08cee5 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 15 Sep 2020 17:24:19 -0400 Subject: [PATCH] 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 --- .../mapper/AbstractScriptMappedFieldType.java | 44 ++++++- .../mapper/RuntimeFieldMapper.java | 10 +- .../mapper/ScriptBooleanMappedFieldType.java | 19 +-- .../mapper/ScriptDateMappedFieldType.java | 20 ++-- .../mapper/ScriptDoubleMappedFieldType.java | 19 +-- .../mapper/ScriptIpMappedFieldType.java | 30 ++--- .../mapper/ScriptKeywordMappedFieldType.java | 35 ++---- .../mapper/ScriptLongMappedFieldType.java | 24 +--- ...tNonTextScriptMappedFieldTypeTestCase.java | 4 +- ...AbstractScriptMappedFieldTypeTestCase.java | 69 +++++++++-- .../mapper/RuntimeFieldMapperTests.java | 8 +- .../ScriptBooleanMappedFieldTypeTests.java | 81 ++++++++----- .../ScriptDateMappedFieldTypeTests.java | 60 ++++++---- .../ScriptDoubleMappedFieldTypeTests.java | 42 +++---- .../mapper/ScriptIpMappedFieldTypeTests.java | 41 +++---- .../ScriptKeywordMappedFieldTypeTests.java | 110 +++++++++++------- .../ScriptLongMappedFieldTypeTests.java | 42 +++---- .../test/runtime_fields/90_loops.yml | 50 +++++++- 18 files changed, 417 insertions(+), 291 deletions(-) diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldType.java index ffaec996e7480..4604c9f29109a 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldType.java @@ -12,6 +12,7 @@ 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.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 @@ /** * Abstract base {@linkplain MappedFieldType} for scripted fields. */ -abstract class AbstractScriptMappedFieldType extends MappedFieldType { +abstract class AbstractScriptMappedFieldType extends MappedFieldType { protected final Script script; + private final TriFunction, SearchLookup, LeafFactory> factory; - AbstractScriptMappedFieldType(String name, Script script, Map meta) { + AbstractScriptMappedFieldType( + String name, + Script script, + TriFunction, SearchLookup, LeafFactory> factory, + Map meta + ) { super(name, false, false, TextSearchInfo.SIMPLE_MATCH_ONLY, meta); this.script = script; + this.factory = factory; } protected abstract String runtimeType(); @@ -61,6 +70,26 @@ public final boolean isAggregatable() { 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 @@ public SpanQuery spanPrefixQuery(String value, SpanMultiTermQueryWrapper.SpanRew } 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) { diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapper.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapper.java index 76340d332f5df..5176aaad5bafd 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapper.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapper.java @@ -51,7 +51,7 @@ public FactoryType compile(Script script, ScriptContext mappedFieldType, MultiFields multiFields, CopyTo copyTo, String runtimeType, @@ -86,7 +86,7 @@ protected String contentType() { public static class Builder extends ParametrizedFieldMapper.Builder { - static final Map> FIELD_TYPE_RESOLVER = + static final Map>> 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 @@ private static RuntimeFieldMapper toType(FieldMapper in) { private final Parameter 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 @@ private static RuntimeFieldMapper toType(FieldMapper in) { 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 @@ protected List> getParameters() { @Override public RuntimeFieldMapper build(BuilderContext context) { - BiFunction fieldTypeResolver = Builder.FIELD_TYPE_RESOLVER.get( + BiFunction> fieldTypeResolver = Builder.FIELD_TYPE_RESOLVER.get( runtimeType.getValue() ); if (fieldTypeResolver == null) { diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldType.java index 98b5820078233..4f97401c0847a 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldType.java @@ -27,12 +27,9 @@ import java.util.Map; import java.util.function.Supplier; -public class ScriptBooleanMappedFieldType extends AbstractScriptMappedFieldType { - private final BooleanScriptFieldScript.Factory scriptFactory; - +public class ScriptBooleanMappedFieldType extends AbstractScriptMappedFieldType { ScriptBooleanMappedFieldType(String name, Script script, BooleanScriptFieldScript.Factory scriptFactory, Map meta) { - super(name, script, meta); - this.scriptFactory = scriptFactory; + super(name, script, scriptFactory::newFactory, meta); } @Override @@ -71,14 +68,10 @@ public ScriptBooleanFieldData.Builder fielddataBuilder(String fullyQualifiedInde 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 Query rangeQuery( @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 @@ private Query termsQuery(boolean trueAllowed, boolean falseAllowed, QueryShardCo 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"); } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldType.java index 8395508e91675..8257723a836a1 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldType.java @@ -37,8 +37,7 @@ import java.util.Map; import java.util.function.Supplier; -public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType { - private final DateScriptFieldScript.Factory scriptFactory; +public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType { private final DateFormatter dateTimeFormatter; ScriptDateMappedFieldType( @@ -48,8 +47,7 @@ public class ScriptDateMappedFieldType extends AbstractScriptMappedFieldType { DateFormatter dateTimeFormatter, Map 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 ScriptDateFieldData.Builder fielddataBuilder(String fullyQualifiedIndexNa 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 Query distanceFeatureQuery(Object origin, String pivot, float boost, Quer 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 Query distanceFeatureQuery(Object origin, String pivot, float boost, Quer @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 Query rangeQuery( 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 Query termQuery(Object value, QueryShardContext context) { 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 Query termsQuery(List values, QueryShardContext context) { ); } checkAllowExpensiveQueries(context); - return new LongScriptFieldTermsQuery(script, leafFactory(context.lookup())::newInstance, name(), terms); + return new LongScriptFieldTermsQuery(script, leafFactory(context)::newInstance, name(), terms); }); } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldType.java index bc898bbfa23f3..0cd6313d3d543 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldType.java @@ -29,12 +29,9 @@ import java.util.Map; import java.util.function.Supplier; -public class ScriptDoubleMappedFieldType extends AbstractScriptMappedFieldType { - private final DoubleScriptFieldScript.Factory scriptFactory; - +public class ScriptDoubleMappedFieldType extends AbstractScriptMappedFieldType { ScriptDoubleMappedFieldType(String name, Script script, DoubleScriptFieldScript.Factory scriptFactory, Map meta) { - super(name, script, meta); - this.scriptFactory = scriptFactory; + super(name, script, scriptFactory::newFactory, meta); } @Override @@ -63,14 +60,10 @@ public ScriptDoubleFieldData.Builder fielddataBuilder(String fullyQualifiedIndex 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 Query rangeQuery( 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 Query termsQuery(List values, QueryShardContext context) { 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); } } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldType.java index 0257fa116c2dd..eaf9940e65d35 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldType.java @@ -37,15 +37,9 @@ 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 { ScriptIpMappedFieldType(String name, Script script, IpScriptFieldScript.Factory scriptFactory, Map meta) { - super(name, script, meta); - this.script = script; - this.scriptFactory = scriptFactory; + super(name, script, scriptFactory::newFactory, meta); } @Override @@ -79,14 +73,10 @@ public ScriptIpFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName 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 Query rangeQuery( 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 Query rangeQuery( 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 Query termsQuery(List values, QueryShardContext context) { } 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 @@ private Query cidrQuery(String term, QueryShardContext context) { // 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); } } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldType.java index 39926e21a113f..7cf7a5be595c3 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldType.java @@ -36,15 +36,9 @@ 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 { ScriptKeywordMappedFieldType(String name, Script script, StringScriptFieldScript.Factory scriptFactory, Map meta) { - super(name, script, meta); - this.script = script; - this.scriptFactory = scriptFactory; + super(name, script, scriptFactory::newFactory, meta); } @Override @@ -67,14 +61,10 @@ public ScriptStringFieldData.Builder fielddataBuilder(String fullyQualifiedIndex 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 Query fuzzyQuery( 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 Query fuzzyQuery( @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 Query rangeQuery( 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 Query regexpQuery( 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 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); } } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldType.java index 0568ee4e9e7ba..2f4a44bc3fe7a 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldType.java @@ -29,12 +29,9 @@ import java.util.Map; import java.util.function.Supplier; -public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType { - private final LongScriptFieldScript.Factory scriptFactory; - +public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType { ScriptLongMappedFieldType(String name, Script script, LongScriptFieldScript.Factory scriptFactory, Map meta) { - super(name, script, meta); - this.scriptFactory = scriptFactory; + super(name, script, scriptFactory::newFactory, meta); } @Override @@ -63,14 +60,10 @@ public ScriptLongFieldData.Builder fielddataBuilder(String fullyQualifiedIndexNa 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 Query rangeQuery( 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 Query termQuery(Object value, QueryShardContext context) { 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 Query termsQuery(List values, QueryShardContext context) { 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); } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractNonTextScriptMappedFieldTypeTestCase.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractNonTextScriptMappedFieldTypeTestCase.java index 8c56984824154..f14924b41a8cd 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractNonTextScriptMappedFieldTypeTestCase.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractNonTextScriptMappedFieldTypeTestCase.java @@ -43,7 +43,7 @@ private void assertQueryOnlyOnTextAndKeyword(String queryName, ThrowingRunnable 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 @@ private void assertQueryOnlyOnTextKeywordAndWildcard(String queryName, ThrowingR 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() + "]" ) diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java index 85cb72be87ff4..f5f9e2663e2f3 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/AbstractScriptMappedFieldTypeTestCase.java @@ -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.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 @@ protected static QueryShardContext mockContext(boolean allowExpensiveQueries) { 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 @@ protected static QueryShardContext mockContext(boolean allowExpensiveQueries, Ab 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 @@ public void testRangeQueryWithShapeRelationIsError() throws IOException { ); } + 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 @@ private void assertQueryOnlyOnText(String queryName, ThrowingRunnable buildQuery 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 @@ private void assertQueryOnlyOnText(String queryName, ThrowingRunnable buildQuery protected String readSource(IndexReader reader, int docId) throws IOException { return reader.document(docId).getBinaryValue("_source").utf8ToString(); } + + protected final void checkExpensiveQuery(BiConsumer 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 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")); + } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapperTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapperTests.java index 6681c8eddd305..c2c7ffcea818e 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapperTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeFieldMapperTests.java @@ -52,6 +52,7 @@ 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 void testIndexSorting() { 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) ) ); diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldTypeTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldTypeTests.java index fa4af13b0176b..37928e97f198a 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldTypeTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptBooleanMappedFieldTypeTests.java @@ -24,7 +24,6 @@ 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.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 void testExistsQuery() throws IOException { } @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 void testRangeQuery() throws IOException { } } - 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 void testRangeQueryIsExpensive() throws IOException { 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 void testTermQuery() throws IOException { } @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 void testTermsQuery() throws IOException { } } - @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 @@ protected ScriptBooleanMappedFieldType simpleMappedFieldType() throws IOExceptio 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 void execute() { } } }; + 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 void execute() { return new ScriptBooleanMappedFieldType("test", script, factory, emptyMap()); } } - - private void checkExpensiveQuery(BiConsumer 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].") - ); - } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldTypeTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldTypeTests.java index 04785df39dce7..8291f190a8548 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldTypeTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDateMappedFieldTypeTests.java @@ -26,7 +26,6 @@ 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.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 void testFormatDuel() throws IOException { ); 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 void testDistanceFeatureQuery() throws IOException { } 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 void testExistsQuery() throws IOException { } } - @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 void testRangeQuery() throws IOException { } @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 void testTermQuery() throws IOException { } @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 void testTermsQuery() throws IOException { } @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 @@ protected ScriptDateMappedFieldType simpleMappedFieldType() throws IOException { 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 void execute() { } } }; + 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 void execute() { } } - private void checkExpensiveQuery(BiConsumer 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) { diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldTypeTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldTypeTests.java index 5f1d3e089035f..1bc706475ac12 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldTypeTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptDoubleMappedFieldTypeTests.java @@ -14,6 +14,7 @@ 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.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.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 void testExistsQuery() throws IOException { } @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 void testRangeQuery() throws IOException { } } - 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 void testTermQuery() throws IOException { } @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 void testTermsQuery() throws IOException { } @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 @@ protected ScriptDoubleMappedFieldType simpleMappedFieldType() throws IOException 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 void execute() { } } }; + 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 void execute() { return new ScriptDoubleMappedFieldType("test", script, factory, emptyMap()); } } - - private void checkExpensiveQuery(BiConsumer 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].") - ); - } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldTypeTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldTypeTests.java index bd668283fd254..817ee032c24d6 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldTypeTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptIpMappedFieldTypeTests.java @@ -14,6 +14,7 @@ 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.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.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 void testExistsQuery() throws IOException { } } - @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 void testRangeQuery() throws IOException { } @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 void testTermQuery() throws IOException { } @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 void testTermsQuery() throws IOException { } @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 @@ protected ScriptIpMappedFieldType simpleMappedFieldType() throws IOException { 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 void execute() { } } }; + 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 void execute() { return new ScriptIpMappedFieldType("test", script, factory, emptyMap()); } } - - private void checkExpensiveQuery(BiConsumer 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].") - ); - } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldTypeTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldTypeTests.java index 2d7e835b36413..39852b2a79378 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldTypeTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptKeywordMappedFieldTypeTests.java @@ -23,13 +23,13 @@ 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.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 void testExistsQuery() throws IOException { } } - @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 void testFuzzyQuery() throws IOException { } public void testFuzzyQueryIsExpensive() throws IOException { - checkExpensiveQuery( - (ft, ctx) -> ft.fuzzyQuery( - randomAlphaOfLengthBetween(1, 1000), - randomFrom(Fuzziness.AUTO, Fuzziness.ZERO, Fuzziness.ONE, Fuzziness.TWO), - randomInt(), - randomInt(), - randomBoolean(), - ctx - ) + 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 void testPrefixQuery() throws IOException { } 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 void testRangeQuery() throws IOException { } @Override - public void testRangeQueryIsExpensive() throws IOException { - checkExpensiveQuery( - (ft, ctx) -> ft.rangeQuery( - "a" + randomAlphaOfLengthBetween(0, 1000), - "b" + randomAlphaOfLengthBetween(0, 1000), - randomBoolean(), - randomBoolean(), - null, - null, - null, - ctx - ) + 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 void testRegexpQuery() throws IOException { } } - 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 void testTermQuery() throws IOException { } @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 void testTermsQuery() throws IOException { } @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 void testWildcardQuery() throws IOException { } 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 @@ protected ScriptKeywordMappedFieldType simpleMappedFieldType() throws IOExceptio 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 void execute() { } } }; + 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 void execute() { return new ScriptKeywordMappedFieldType("test", script, factory, emptyMap()); } } - - private void checkExpensiveQuery(BiConsumer 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].") - ); - } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldTypeTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldTypeTests.java index 2127b93c061d0..5355a42ce3d75 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldTypeTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldTypeTests.java @@ -16,6 +16,7 @@ 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.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.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 void testExistsQuery() throws IOException { } } - @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 void testRangeQuery() throws IOException { } @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 void testTermQuery() throws IOException { } @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 void testTermsQuery() throws IOException { } @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 @@ protected ScriptLongMappedFieldType simpleMappedFieldType() throws IOException { 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 void execute() { } } }; + 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 void execute() { return new ScriptLongMappedFieldType("test", script, factory, emptyMap()); } } - - private void checkExpensiveQuery(BiConsumer 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].") - ); - } } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/90_loops.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/90_loops.yml index 7d8ee4c680d5a..75ddcc3a5740d 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/90_loops.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/90_loops.yml @@ -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