mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-28 07:59:10 +00:00
EQL: Allow null tiebreakers inside ordinals/sequences (#65033)
Align Ordinal comparator to consider nulls last (higher) in tiebreakers. Add unit tests to Ordinal comparisons and criterion extraction. Fix #64706 (cherry picked from commit 93dc883abd6b8855ff1618a574412b7f773b8ff5) (cherry picked from commit 936e5f1a2cc29c1d5662cb8aa90c629af563a987)
This commit is contained in:
parent
20bda8d23a
commit
74fde15833
@ -25,7 +25,7 @@ public class Criterion<Q extends QueryRequest> {
|
|||||||
|
|
||||||
private final boolean descending;
|
private final boolean descending;
|
||||||
|
|
||||||
Criterion(int stage,
|
public Criterion(int stage,
|
||||||
Q queryRequest,
|
Q queryRequest,
|
||||||
List<HitExtractor> keys,
|
List<HitExtractor> keys,
|
||||||
HitExtractor timestamp,
|
HitExtractor timestamp,
|
||||||
@ -83,7 +83,7 @@ public class Criterion<Q extends QueryRequest> {
|
|||||||
|
|
||||||
if (tiebreaker != null) {
|
if (tiebreaker != null) {
|
||||||
Object tb = tiebreaker.extract(hit);
|
Object tb = tiebreaker.extract(hit);
|
||||||
if (tb instanceof Comparable == false) {
|
if (tb != null && tb instanceof Comparable == false) {
|
||||||
throw new EqlIllegalArgumentException("Expected tiebreaker to be Comparable but got {}", tb);
|
throw new EqlIllegalArgumentException("Expected tiebreaker to be Comparable but got {}", tb);
|
||||||
}
|
}
|
||||||
tbreaker = (Comparable<Object>) tb;
|
tbreaker = (Comparable<Object>) tb;
|
||||||
|
@ -58,17 +58,15 @@ public class Ordinal implements Comparable<Ordinal> {
|
|||||||
}
|
}
|
||||||
if (timestamp == o.timestamp) {
|
if (timestamp == o.timestamp) {
|
||||||
if (tiebreaker != null) {
|
if (tiebreaker != null) {
|
||||||
if (o.tiebreaker != null) {
|
// if the other tiebreaker is null, it is higher (nulls are last)
|
||||||
return tiebreaker.compareTo(o.tiebreaker);
|
return o.tiebreaker != null ? tiebreaker.compareTo(o.tiebreaker) : -1;
|
||||||
}
|
|
||||||
// nulls are first - lower than any other value
|
|
||||||
// other tiebreaker is null this one isn't, fall through 1
|
|
||||||
}
|
}
|
||||||
// null tiebreaker
|
// this tiebreaker is null
|
||||||
else {
|
else {
|
||||||
if (o.tiebreaker != null) {
|
// nulls are last so unless both are null (equal)
|
||||||
return -1;
|
// this ordinal is greater (after) then the other tiebreaker
|
||||||
} else {
|
// so fall through to 1
|
||||||
|
if (o.tiebreaker == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.xpack.eql.execution.search;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.document.DocumentField;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.common.text.Text;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.eql.EqlIllegalArgumentException;
|
||||||
|
import org.elasticsearch.xpack.eql.execution.assembler.BoxedQueryRequest;
|
||||||
|
import org.elasticsearch.xpack.eql.execution.assembler.Criterion;
|
||||||
|
import org.elasticsearch.xpack.eql.execution.search.extractor.FieldHitExtractor;
|
||||||
|
import org.elasticsearch.xpack.ql.execution.search.extractor.HitExtractor;
|
||||||
|
import org.elasticsearch.xpack.ql.type.DataTypes;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.emptyMap;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
|
public class CriterionOrdinalExtractionTests extends ESTestCase {
|
||||||
|
private String tsField = "timestamp";
|
||||||
|
private String tbField = "tiebreaker";
|
||||||
|
|
||||||
|
private HitExtractor tsExtractor = new FieldHitExtractor(tsField, tsField, DataTypes.LONG, null, true, null, false);
|
||||||
|
private HitExtractor tbExtractor = new FieldHitExtractor(tbField, tbField, DataTypes.LONG, null, true, null, false);
|
||||||
|
|
||||||
|
public void testTimeOnly() throws Exception {
|
||||||
|
long time = randomLong();
|
||||||
|
Ordinal ordinal = ordinal(searchHit(time, null), false);
|
||||||
|
assertEquals(time, ordinal.timestamp());
|
||||||
|
assertNull(ordinal.tiebreaker());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTimeAndTiebreaker() throws Exception {
|
||||||
|
long time = randomLong();
|
||||||
|
long tb = randomLong();
|
||||||
|
Ordinal ordinal = ordinal(searchHit(time, tb), true);
|
||||||
|
assertEquals(time, ordinal.timestamp());
|
||||||
|
assertEquals(tb, ordinal.tiebreaker());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTimeAndTiebreakerNull() throws Exception {
|
||||||
|
long time = randomLong();
|
||||||
|
Ordinal ordinal = ordinal(searchHit(time, null), true);
|
||||||
|
assertEquals(time, ordinal.timestamp());
|
||||||
|
assertNull(ordinal.tiebreaker());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTimeNotComparable() throws Exception {
|
||||||
|
HitExtractor badExtractor = new FieldHitExtractor(tsField, tsField, DataTypes.BINARY, null, true, null, false);
|
||||||
|
SearchHit hit = searchHit(randomAlphaOfLength(10), null);
|
||||||
|
Criterion<BoxedQueryRequest> criterion = new Criterion<BoxedQueryRequest>(0, null, emptyList(), badExtractor, null, false);
|
||||||
|
EqlIllegalArgumentException exception = expectThrows(EqlIllegalArgumentException.class, () -> criterion.ordinal(hit));
|
||||||
|
assertTrue(exception.getMessage().startsWith("Expected timestamp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTiebreakerNotComparable() throws Exception {
|
||||||
|
final Object o = randomDateTimeZone();
|
||||||
|
HitExtractor badExtractor = new HitExtractor() {
|
||||||
|
@Override
|
||||||
|
public Object extract(SearchHit hit) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String hitName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SearchHit hit = searchHit(randomLong(), o);
|
||||||
|
Criterion<BoxedQueryRequest> criterion = new Criterion<BoxedQueryRequest>(0, null, emptyList(), tsExtractor, badExtractor, false);
|
||||||
|
EqlIllegalArgumentException exception = expectThrows(EqlIllegalArgumentException.class, () -> criterion.ordinal(hit));
|
||||||
|
assertTrue(exception.getMessage().startsWith("Expected tiebreaker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchHit searchHit(Object timeValue, Object tiebreakerValue) {
|
||||||
|
Map<String, DocumentField> fields = new HashMap<>();
|
||||||
|
fields.put(tsField, new DocumentField(tsField, singletonList(timeValue)));
|
||||||
|
fields.put(tbField, new DocumentField(tsField, singletonList(tiebreakerValue)));
|
||||||
|
|
||||||
|
return new SearchHit(randomInt(), randomAlphaOfLength(10), new Text("_doc"), fields, emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ordinal ordinal(SearchHit hit, boolean withTiebreaker) {
|
||||||
|
return new Criterion<BoxedQueryRequest>(0, null, emptyList(), tsExtractor, withTiebreaker ? tbExtractor : null, false).ordinal(hit);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.xpack.eql.execution.search;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class OrdinalTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testCompareToDifferentTs() {
|
||||||
|
Ordinal one = new Ordinal(randomLong(), (Comparable) randomLong());
|
||||||
|
Ordinal two = new Ordinal(randomLong(), (Comparable) randomLong());
|
||||||
|
|
||||||
|
assertEquals(Long.valueOf(one.timestamp()).compareTo(two.timestamp()), one.compareTo(two));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCompareToSameTsDifferentTie() {
|
||||||
|
Long ts = randomLong();
|
||||||
|
Ordinal one = new Ordinal(ts, (Comparable) randomLong());
|
||||||
|
Ordinal two = new Ordinal(ts, (Comparable) randomLong());
|
||||||
|
|
||||||
|
assertEquals(one.tiebreaker().compareTo(two.tiebreaker()), one.compareTo(two));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCompareToSameTsOneTieNull() {
|
||||||
|
Long ts = randomLong();
|
||||||
|
Ordinal one = new Ordinal(ts, (Comparable) randomLong());
|
||||||
|
Ordinal two = new Ordinal(ts, null);
|
||||||
|
|
||||||
|
assertEquals(-1, one.compareTo(two));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public void testCompareToSameTsSameTie() {
|
||||||
|
Long ts = randomLong();
|
||||||
|
Comparable c = randomLong();
|
||||||
|
Ordinal one = new Ordinal(ts, c);
|
||||||
|
Ordinal two = new Ordinal(ts, c);
|
||||||
|
|
||||||
|
assertEquals(0, one.compareTo(two));
|
||||||
|
assertEquals(0, one.compareTo(one));
|
||||||
|
assertEquals(0, two.compareTo(two));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCompareToSameTsSameTieNull() {
|
||||||
|
Long ts = randomLong();
|
||||||
|
Ordinal one = new Ordinal(ts, null);
|
||||||
|
Ordinal two = new Ordinal(ts, null);
|
||||||
|
|
||||||
|
assertEquals(0, one.compareTo(two));
|
||||||
|
assertEquals(0, one.compareTo(one));
|
||||||
|
assertEquals(0, two.compareTo(two));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTestBetween() {
|
||||||
|
Ordinal before = new Ordinal(randomLongBetween(1000, 2000), (Comparable) randomLong());
|
||||||
|
Ordinal between = new Ordinal(randomLongBetween(3000, 4000), (Comparable) randomLong());
|
||||||
|
Ordinal after = new Ordinal(randomLongBetween(5000, 6000), (Comparable) randomLong());
|
||||||
|
|
||||||
|
assertTrue(before.between(before, after));
|
||||||
|
assertTrue(after.between(before, after));
|
||||||
|
assertTrue(between.between(before, after));
|
||||||
|
|
||||||
|
assertFalse(new Ordinal(randomLongBetween(0, 999), null).between(before, after));
|
||||||
|
assertFalse(new Ordinal(randomLongBetween(7000, 8000), null).between(before, after));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTestBefore() {
|
||||||
|
Ordinal before = new Ordinal(randomLongBetween(1000, 2000), (Comparable) randomLong());
|
||||||
|
Ordinal after = new Ordinal(randomLongBetween(5000, 6000), (Comparable) randomLong());
|
||||||
|
|
||||||
|
assertTrue(before.before(after));
|
||||||
|
assertFalse(before.before(before));
|
||||||
|
assertFalse(after.before(before));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBeforeOrAt() {
|
||||||
|
Ordinal before = new Ordinal(randomLongBetween(1000, 2000), (Comparable) randomLong());
|
||||||
|
Ordinal after = new Ordinal(randomLongBetween(5000, 6000), (Comparable) randomLong());
|
||||||
|
|
||||||
|
assertTrue(before.beforeOrAt(after));
|
||||||
|
assertTrue(before.beforeOrAt(before));
|
||||||
|
assertFalse(after.beforeOrAt(before));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTestAfter() {
|
||||||
|
Ordinal before = new Ordinal(randomLongBetween(1000, 2000), (Comparable) randomLong());
|
||||||
|
Ordinal after = new Ordinal(randomLongBetween(5000, 6000), (Comparable) randomLong());
|
||||||
|
|
||||||
|
assertTrue(after.after(before));
|
||||||
|
assertFalse(after.after(after));
|
||||||
|
assertFalse(before.after(after));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAfterOrAt() {
|
||||||
|
Ordinal before = new Ordinal(randomLongBetween(1000, 2000), (Comparable) randomLong());
|
||||||
|
Ordinal after = new Ordinal(randomLongBetween(5000, 6000), (Comparable) randomLong());
|
||||||
|
|
||||||
|
assertTrue(after.afterOrAt(before));
|
||||||
|
assertTrue(after.afterOrAt(after));
|
||||||
|
assertFalse(before.afterOrAt(after));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user