diff --git a/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlActionTestCase.java b/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlActionTestCase.java index 6c6328528a1..60e72d1e2d3 100644 --- a/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlActionTestCase.java +++ b/x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/CommonEqlActionTestCase.java @@ -9,6 +9,7 @@ package org.elasticsearch.test.eql; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.elasticsearch.Build; +import org.elasticsearch.client.EqlClient; import org.elasticsearch.client.Request; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; @@ -51,8 +52,8 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase { @After public void cleanup() throws Exception { - if (--counter == 0) { - deleteIndex(testIndexName); + if (--counter == 0) { + deleteIndex(testIndexName); } } @@ -143,7 +144,11 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase { EqlSearchRequest request = new EqlSearchRequest(testIndexName, query); request.isCaseSensitive(isCaseSensitive); request.tiebreakerField("event.sequence"); - return highLevelClient().eql().search(request, RequestOptions.DEFAULT); + return eqlClient().search(request, RequestOptions.DEFAULT); + } + + private EqlClient eqlClient() { + return highLevelClient().eql(); } protected void assertSearchHits(List events) { diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/RequestDefaults.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/RequestDefaults.java index e5159169514..6f66c621a91 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/RequestDefaults.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/RequestDefaults.java @@ -14,5 +14,5 @@ public final class RequestDefaults { public static final String FIELD_EVENT_CATEGORY = "event.category"; public static final String FIELD_IMPLICIT_JOIN_KEY = "agent.id"; - public static int FETCH_SIZE = 50; + public static int FETCH_SIZE = 10; } diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/LogicalPlanBuilder.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/LogicalPlanBuilder.java index e6252f548ed..c43b36bee55 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/LogicalPlanBuilder.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/LogicalPlanBuilder.java @@ -24,6 +24,7 @@ import org.elasticsearch.xpack.eql.parser.EqlBaseParser.SubqueryContext; import org.elasticsearch.xpack.eql.plan.logical.Head; import org.elasticsearch.xpack.eql.plan.logical.Join; import org.elasticsearch.xpack.eql.plan.logical.KeyedFilter; +import org.elasticsearch.xpack.eql.plan.logical.LimitWithOffset; import org.elasticsearch.xpack.eql.plan.logical.Sequence; import org.elasticsearch.xpack.eql.plan.logical.Tail; import org.elasticsearch.xpack.eql.plan.physical.LocalRelation; @@ -33,6 +34,7 @@ import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.expression.Expressions; import org.elasticsearch.xpack.ql.expression.Literal; import org.elasticsearch.xpack.ql.expression.Order; +import org.elasticsearch.xpack.ql.expression.Order.NullsPosition; import org.elasticsearch.xpack.ql.expression.Order.OrderDirection; import org.elasticsearch.xpack.ql.expression.UnresolvedAttribute; import org.elasticsearch.xpack.ql.expression.predicate.logical.And; @@ -55,24 +57,26 @@ import java.util.concurrent.TimeUnit; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.elasticsearch.xpack.ql.tree.Source.synthetic; public abstract class LogicalPlanBuilder extends ExpressionBuilder { private static final Set SUPPORTED_PIPES = Sets.newHashSet("count", "filter", "head", "sort", "tail", "unique", "unique_count"); - private final UnresolvedRelation RELATION = new UnresolvedRelation(Source.EMPTY, null, "", false, ""); - private final EmptyAttribute UNSPECIFIED_FIELD = new EmptyAttribute(Source.EMPTY); + private final UnresolvedRelation RELATION = new UnresolvedRelation(synthetic(""), null, "", false, ""); + private final EmptyAttribute UNSPECIFIED_FIELD = new EmptyAttribute(synthetic("")); public LogicalPlanBuilder(ParserParams params) { super(params); } private Attribute fieldTimestamp() { - return new UnresolvedAttribute(Source.EMPTY, params.fieldTimestamp()); + return new UnresolvedAttribute(synthetic(""), params.fieldTimestamp()); } private Attribute fieldTiebreaker() { - return params.fieldTiebreaker() != null ? new UnresolvedAttribute(Source.EMPTY, params.fieldTiebreaker()) : UNSPECIFIED_FIELD; + return params.fieldTiebreaker() != null ? + new UnresolvedAttribute(synthetic(""), params.fieldTiebreaker()) : UNSPECIFIED_FIELD; } private OrderDirection defaultDirection() { @@ -84,19 +88,49 @@ public abstract class LogicalPlanBuilder extends ExpressionBuilder { LogicalPlan plan = plan(ctx.query()); // the first pipe will be the implicit order + boolean asc = defaultDirection() == OrderDirection.ASC; + NullsPosition position = asc ? NullsPosition.FIRST : NullsPosition.LAST; + List orders = new ArrayList<>(2); - Source source = plan.source(); - orders.add(new Order(source, fieldTimestamp(), defaultDirection(), Order.NullsPosition.FIRST)); + Source defaultOrderSource = synthetic(""); + orders.add(new Order(defaultOrderSource, fieldTimestamp(), defaultDirection(), position)); // make sure to add the tiebreaker as well Attribute tiebreaker = fieldTiebreaker(); if (Expressions.isPresent(tiebreaker)) { - orders.add(new Order(source, tiebreaker, defaultDirection(), Order.NullsPosition.FIRST)); + orders.add(new Order(defaultOrderSource, tiebreaker, defaultDirection(), position)); } - plan = new OrderBy(source, plan, orders); - // add the actual declared pipes + plan = new OrderBy(defaultOrderSource, plan, orders); + + // add the default limit only if specified + Literal defaultSize = new Literal(synthetic(""), params.fetchSize(), DataTypes.INTEGER); + Source defaultLimitSource = synthetic(""); + + LogicalPlan previous = plan; + boolean missingLimit = true; + for (PipeContext pipeCtx : ctx.pipe()) { - plan = pipe(pipeCtx, plan); + plan = pipe(pipeCtx, previous); + if (missingLimit && plan instanceof LimitWithOffset) { + missingLimit = false; + if (plan instanceof Head) { + previous = new Head(defaultLimitSource, defaultSize, previous); + } else { + previous = new Tail(defaultLimitSource, defaultSize, previous); + } + plan = plan.replaceChildren(singletonList(previous)); + } + previous = plan; } + + // add limit based on the default order if no tail/head was specified + if (missingLimit) { + if (asc) { + plan = new Head(defaultLimitSource, defaultSize, plan); + } else { + plan = new Tail(defaultLimitSource, defaultSize, plan); + } + } + return plan; } diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/ParserParams.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/ParserParams.java index 76bc47e8abb..56a9aa367bc 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/ParserParams.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/ParserParams.java @@ -10,6 +10,7 @@ import java.time.ZoneId; import java.util.List; import static java.util.Collections.emptyList; +import static org.elasticsearch.xpack.eql.action.RequestDefaults.FETCH_SIZE; import static org.elasticsearch.xpack.eql.action.RequestDefaults.FIELD_EVENT_CATEGORY; import static org.elasticsearch.xpack.eql.action.RequestDefaults.FIELD_IMPLICIT_JOIN_KEY; import static org.elasticsearch.xpack.eql.action.RequestDefaults.FIELD_TIMESTAMP; @@ -21,6 +22,7 @@ public class ParserParams { private String fieldTimestamp = FIELD_TIMESTAMP; private String fieldTiebreaker = null; private String implicitJoinKey = FIELD_IMPLICIT_JOIN_KEY; + private int fetchSize = FETCH_SIZE; private List queryParams = emptyList(); public ParserParams(ZoneId zoneId) { @@ -63,6 +65,15 @@ public class ParserParams { return this; } + public int fetchSize() { + return fetchSize; + } + + public ParserParams fetchSize(int fetchSize) { + this.fetchSize = fetchSize; + return this; + } + public List params() { return queryParams; } @@ -75,4 +86,4 @@ public class ParserParams { public ZoneId zoneId() { return zoneId; } -} +} \ No newline at end of file diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java index 51b41f74808..e9544c76abc 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlSearchAction.java @@ -115,7 +115,8 @@ public class TransportEqlSearchAction extends HandledTransportAction