Skip to content

Commit 31d2d01

Browse files
committed
Correct search_after handling (elastic#50629)
1 parent 79875ce commit 31d2d01

File tree

4 files changed

+82
-64
lines changed

4 files changed

+82
-64
lines changed

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/action/EqlSearchRequest.java

Lines changed: 23 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@
1919
import org.elasticsearch.common.xcontent.XContentParser;
2020
import org.elasticsearch.index.query.AbstractQueryBuilder;
2121
import org.elasticsearch.index.query.QueryBuilder;
22+
import org.elasticsearch.search.searchafter.SearchAfterBuilder;
2223

2324
import java.io.IOException;
2425
import java.util.Arrays;
25-
import java.util.Collections;
26-
import java.util.List;
2726
import java.util.Objects;
2827
import java.util.function.Supplier;
2928

@@ -40,7 +39,7 @@ public class EqlSearchRequest extends ActionRequest implements IndicesRequest.Re
4039
private String eventTypeField = "event.category";
4140
private String implicitJoinKeyField = "agent.id";
4241
private int fetchSize = 50;
43-
private List<String> searchAfter = Collections.emptyList();
42+
private SearchAfterBuilder searchAfterBuilder;
4443
private String rule;
4544

4645
static final String KEY_QUERY = "query";
@@ -74,23 +73,10 @@ public EqlSearchRequest(StreamInput in) throws IOException {
7473
eventTypeField = in.readString();
7574
implicitJoinKeyField = in.readString();
7675
fetchSize = in.readVInt();
77-
searchAfter = in.readList(StreamInput::readString);
76+
searchAfterBuilder = in.readOptionalWriteable(SearchAfterBuilder::new);
7877
rule = in.readString();
7978
}
8079

81-
public EqlSearchRequest(String[] indices, QueryBuilder query,
82-
String timestampField, String eventTypeField, String implicitJoinKeyField,
83-
int fetchSize, List<String> searchAfter, String rule) {
84-
this.indices = indices;
85-
this.query = query;
86-
this.timestampField = timestampField;
87-
this.eventTypeField = eventTypeField;
88-
this.implicitJoinKeyField = implicitJoinKeyField;
89-
this.fetchSize = fetchSize;
90-
this.searchAfter = searchAfter;
91-
this.rule = rule;
92-
}
93-
9480
@Override
9581
public ActionRequestValidationException validate() {
9682
ActionRequestValidationException validationException = null;
@@ -122,10 +108,6 @@ public ActionRequestValidationException validate() {
122108
validationException = addValidationError("size must be more than 0.", validationException);
123109
}
124110

125-
if (searchAfter == null) {
126-
validationException = addValidationError("search after is null", validationException);
127-
}
128-
129111
return validationException;
130112
}
131113

@@ -141,13 +123,10 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
141123
}
142124
builder.field(KEY_SIZE, fetchSize());
143125

144-
if (this.searchAfter != null && !this.searchAfter.isEmpty()) {
145-
builder.startArray(KEY_SEARCH_AFTER);
146-
for (String val : this.searchAfter) {
147-
builder.value(val);
148-
}
149-
builder.endArray();
126+
if (searchAfterBuilder != null) {
127+
builder.array(SEARCH_AFTER.getPreferredName(), searchAfterBuilder.getSortValues());
150128
}
129+
151130
builder.field(KEY_RULE, rule);
152131

153132
return builder;
@@ -165,11 +144,13 @@ protected static <R extends EqlSearchRequest> ObjectParser<R, Void> objectParser
165144
parser.declareString(EqlSearchRequest::eventTypeField, EVENT_TYPE_FIELD);
166145
parser.declareString(EqlSearchRequest::implicitJoinKeyField, IMPLICIT_JOIN_KEY_FIELD);
167146
parser.declareInt(EqlSearchRequest::fetchSize, SIZE);
168-
parser.declareStringArray(EqlSearchRequest::searchAfter, SEARCH_AFTER);
147+
parser.declareField(EqlSearchRequest::setSearchAfter, SearchAfterBuilder::fromXContent, SEARCH_AFTER,
148+
ObjectParser.ValueType.OBJECT_ARRAY);
169149
parser.declareString(EqlSearchRequest::rule, RULE);
170150
return parser;
171151
}
172152

153+
@Override
173154
public EqlSearchRequest indices(String... indices) {
174155
this.indices = indices;
175156
return this;
@@ -219,22 +200,26 @@ public EqlSearchRequest fetchSize(int size) {
219200
return this;
220201
}
221202

222-
public List<String> searchAfter() {
223-
return searchAfter;
203+
public Object[] searchAfter() {
204+
if (searchAfterBuilder == null) {
205+
return null;
206+
}
207+
return searchAfterBuilder.getSortValues();
224208
}
225209

226-
public EqlSearchRequest searchAfter(List<String> searchAfter) {
227-
if (searchAfter != null && !searchAfter.isEmpty()) {
228-
this.searchAfter = searchAfter;
229-
}
210+
public EqlSearchRequest searchAfter(Object[] values) {
211+
this.searchAfterBuilder = new SearchAfterBuilder().setSortValues(values);
230212
return this;
231213
}
232214

215+
private EqlSearchRequest setSearchAfter(SearchAfterBuilder builder) {
216+
this.searchAfterBuilder = builder;
217+
return this;
218+
}
233219

234220
public String rule() { return this.rule; }
235221

236222
public EqlSearchRequest rule(String rule) {
237-
// TODO: possibly attempt to parse the rule here
238223
this.rule = rule;
239224
return this;
240225
}
@@ -249,7 +234,7 @@ public void writeTo(StreamOutput out) throws IOException {
249234
out.writeString(eventTypeField);
250235
out.writeString(implicitJoinKeyField);
251236
out.writeVInt(fetchSize);
252-
out.writeStringCollection(searchAfter);
237+
out.writeOptionalWriteable(searchAfterBuilder);
253238
out.writeString(rule);
254239
}
255240

@@ -270,7 +255,7 @@ public boolean equals(Object o) {
270255
Objects.equals(timestampField, that.timestampField) &&
271256
Objects.equals(eventTypeField, that.eventTypeField) &&
272257
Objects.equals(implicitJoinKeyField, that.implicitJoinKeyField) &&
273-
Objects.equals(searchAfter, that.searchAfter) &&
258+
Objects.equals(searchAfterBuilder, that.searchAfterBuilder) &&
274259
Objects.equals(rule, that.rule);
275260
}
276261

@@ -284,7 +269,7 @@ public int hashCode() {
284269
timestampField,
285270
eventTypeField,
286271
implicitJoinKeyField,
287-
searchAfter,
272+
searchAfterBuilder,
288273
rule);
289274
}
290275

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/RestEqlSearchAction.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@
2828
import static org.elasticsearch.rest.RestRequest.Method.POST;
2929

3030
public class RestEqlSearchAction extends BaseRestHandler {
31-
public RestEqlSearchAction(RestController controller) {
32-
// Not sure yet if we will always have index in the path or not
33-
// TODO: finalize the endpoints
34-
controller.registerHandler(GET, "/{index}/_eql/search", this);
35-
controller.registerHandler(POST, "/{index}/_eql/search", this);
31+
private static final String SEARCH_PATH = "/{index}/_eql/search";
3632

33+
public RestEqlSearchAction(RestController controller) {
34+
controller.registerHandler(GET, SEARCH_PATH, this);
35+
controller.registerHandler(POST, SEARCH_PATH, this);
3736
}
3837

3938
@Override

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlRequestParserTests.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.elasticsearch.test.ESTestCase;
1717

1818
import java.io.IOException;
19-
import java.util.Arrays;
2019
import java.util.List;
2120
import java.util.function.Consumer;
2221
import java.util.function.Function;
@@ -54,7 +53,7 @@ public void testSearchRequestParser() throws IOException {
5453
+ "\"timestamp_field\" : \"tsf\", "
5554
+ "\"event_type_field\" : \"etf\","
5655
+ "\"implicit_join_key_field\" : \"imjf\","
57-
+ "\"search_after\" : [ \"device-20184\", \"/user/local/foo.exe\", \"2019-11-26T00:45:43.542\" ],"
56+
+ "\"search_after\" : [ 12345678, \"device-20184\", \"/user/local/foo.exe\", \"2019-11-26T00:45:43.542\" ],"
5857
+ "\"size\" : \"101\","
5958
+ "\"rule\" : \"file where user != 'SYSTEM' by file_path\""
6059
+ "}", EqlSearchRequest::fromXContent);
@@ -67,8 +66,7 @@ public void testSearchRequestParser() throws IOException {
6766
assertEquals("tsf", request.timestampField());
6867
assertEquals("etf", request.eventTypeField());
6968
assertEquals("imjf", request.implicitJoinKeyField());
70-
assertArrayEquals(Arrays.asList("device-20184", "/user/local/foo.exe", "2019-11-26T00:45:43.542").toArray(),
71-
request.searchAfter().toArray());
69+
assertArrayEquals(new Object[]{12345678, "device-20184", "/user/local/foo.exe", "2019-11-26T00:45:43.542"}, request.searchAfter());
7270
assertEquals(101, request.fetchSize());
7371
assertEquals("file where user != 'SYSTEM' by file_path", request.rule());
7472
}

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/action/EqlSearchRequestTests.java

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,27 @@
55
*/
66
package org.elasticsearch.xpack.eql.action;
77

8+
import org.elasticsearch.common.bytes.BytesReference;
89
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
910
import org.elasticsearch.common.io.stream.Writeable;
1011
import org.elasticsearch.common.settings.Settings;
12+
import org.elasticsearch.common.text.Text;
1113
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
14+
import org.elasticsearch.common.xcontent.XContentBuilder;
15+
import org.elasticsearch.common.xcontent.XContentFactory;
1216
import org.elasticsearch.common.xcontent.XContentParser;
1317
import org.elasticsearch.common.xcontent.json.JsonXContent;
1418
import org.elasticsearch.index.query.QueryBuilder;
1519
import org.elasticsearch.search.SearchModule;
20+
import org.elasticsearch.search.searchafter.SearchAfterBuilder;
1621
import org.elasticsearch.test.AbstractSerializingTestCase;
22+
import org.elasticsearch.test.ESTestCase;
1723
import org.junit.Before;
1824

1925
import java.io.IOException;
20-
import java.util.ArrayList;
26+
import java.util.Arrays;
2127
import java.util.Collections;
22-
import java.util.List;
28+
import java.util.function.Supplier;
2329

2430
import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder;
2531

@@ -52,15 +58,25 @@ protected NamedXContentRegistry xContentRegistry() {
5258

5359
@Override
5460
protected EqlSearchRequest createTestInstance() {
55-
QueryBuilder query = null;
5661
try {
57-
query = parseQuery(defaultTestQuery);
62+
QueryBuilder query = parseQuery(defaultTestQuery);
63+
EqlSearchRequest request = new EqlSearchRequest()
64+
.indices(new String[]{defaultTestIndex})
65+
.query(query)
66+
.timestampField(randomAlphaOfLength(10))
67+
.eventTypeField(randomAlphaOfLength(10))
68+
.implicitJoinKeyField(randomAlphaOfLength(10))
69+
.fetchSize(randomIntBetween(1, 50))
70+
.rule(randomAlphaOfLength(10));
71+
72+
if (randomBoolean()) {
73+
request.searchAfter(randomJsonSearchFromBuilder());
74+
}
75+
return request;
5876
} catch (IOException ex) {
5977
assertNotNull("unexpected IOException " + ex.getCause().getMessage(), ex);
6078
}
61-
return new EqlSearchRequest(new String[]{defaultTestIndex}, query,
62-
randomAlphaOfLength(10), randomAlphaOfLength(10), randomAlphaOfLength(10),
63-
randomIntBetween(1, 50), randomSearchAfter(), randomAlphaOfLength(10));
79+
return null;
6480
}
6581

6682
protected QueryBuilder parseQuery(String queryAsString) throws IOException {
@@ -74,16 +90,36 @@ protected QueryBuilder parseQuery(XContentParser parser) throws IOException {
7490
return parseInnerQueryBuilder;
7591
}
7692

77-
private List<String> randomSearchAfter() {
78-
if (randomBoolean()) {
79-
return Collections.emptyList();
80-
} else {
81-
int size = randomIntBetween(1, 50);
82-
List<String> arr = new ArrayList<>(size);
83-
for (int i = 0; i < size; i++) {
84-
arr.add(randomAlphaOfLength(randomIntBetween(1, 15)));
85-
}
86-
return Collections.unmodifiableList(arr);
93+
private Object randomValue() {
94+
Supplier<Object> value = randomFrom(Arrays.asList(
95+
ESTestCase::randomInt,
96+
ESTestCase::randomFloat,
97+
ESTestCase::randomLong,
98+
ESTestCase::randomDouble,
99+
() -> randomAlphaOfLengthBetween(5, 20),
100+
ESTestCase::randomBoolean,
101+
ESTestCase::randomByte,
102+
ESTestCase::randomShort,
103+
() -> new Text(randomAlphaOfLengthBetween(5, 20)),
104+
() -> null));
105+
return value.get();
106+
}
107+
108+
private Object[] randomJsonSearchFromBuilder() throws IOException {
109+
int numSearchAfter = randomIntBetween(1, 10);
110+
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
111+
jsonBuilder.startObject();
112+
jsonBuilder.startArray("search_after");
113+
for (int i = 0; i < numSearchAfter; i++) {
114+
jsonBuilder.value(randomValue());
115+
}
116+
jsonBuilder.endArray();
117+
jsonBuilder.endObject();
118+
try (XContentParser parser = createParser(JsonXContent.jsonXContent, BytesReference.bytes(jsonBuilder))) {
119+
parser.nextToken();
120+
parser.nextToken();
121+
parser.nextToken();
122+
return SearchAfterBuilder.fromXContent(parser).getSortValues();
87123
}
88124
}
89125

0 commit comments

Comments
 (0)