Skip to content

Commit 0589d2e

Browse files
authored
Remaining queries for long script field (#59816)
Adds the `terms` and `range` queries for `long` typed `script` fields. It also fixes a bug in a few of `toString` tests for `keyword` typed `script` fields.
1 parent 9f5f314 commit 0589d2e

File tree

14 files changed

+524
-42
lines changed

14 files changed

+524
-42
lines changed

server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.fasterxml.jackson.core.JsonParseException;
2323
import com.fasterxml.jackson.core.exc.InputCoercionException;
24+
2425
import org.apache.lucene.document.DoublePoint;
2526
import org.apache.lucene.document.Field;
2627
import org.apache.lucene.document.FieldType;
@@ -66,6 +67,7 @@
6667
import java.util.List;
6768
import java.util.Map;
6869
import java.util.Objects;
70+
import java.util.function.BiFunction;
6971
import java.util.function.Function;
7072

7173
/** A {@link FieldMapper} for numeric types: byte, short, int, long, float and double. */
@@ -748,44 +750,17 @@ public Query termsQuery(String field, List<Object> values) {
748750
public Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
749751
boolean includeLower, boolean includeUpper,
750752
boolean hasDocValues, QueryShardContext context) {
751-
long l = Long.MIN_VALUE;
752-
long u = Long.MAX_VALUE;
753-
if (lowerTerm != null) {
754-
l = parse(lowerTerm, true);
755-
// if the lower bound is decimal:
756-
// - if the bound is positive then we increment it:
757-
// if lowerTerm=1.5 then the (inclusive) bound becomes 2
758-
// - if the bound is negative then we leave it as is:
759-
// if lowerTerm=-1.5 then the (inclusive) bound becomes -1 due to the call to longValue
760-
boolean lowerTermHasDecimalPart = hasDecimalPart(lowerTerm);
761-
if ((lowerTermHasDecimalPart == false && includeLower == false) ||
762-
(lowerTermHasDecimalPart && signum(lowerTerm) > 0)) {
763-
if (l == Long.MAX_VALUE) {
764-
return new MatchNoDocsQuery();
765-
}
766-
++l;
767-
}
768-
}
769-
if (upperTerm != null) {
770-
u = parse(upperTerm, true);
771-
boolean upperTermHasDecimalPart = hasDecimalPart(upperTerm);
772-
if ((upperTermHasDecimalPart == false && includeUpper == false) ||
773-
(upperTermHasDecimalPart && signum(upperTerm) < 0)) {
774-
if (u == Long.MIN_VALUE) {
775-
return new MatchNoDocsQuery();
753+
return longRangeQuery(lowerTerm, upperTerm, includeLower, includeUpper, (l, u) -> {
754+
Query query = LongPoint.newRangeQuery(field, l, u);
755+
if (hasDocValues) {
756+
Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(field, l, u);
757+
query = new IndexOrDocValuesQuery(query, dvQuery);
758+
if (context.indexSortedOnField(field)) {
759+
query = new IndexSortSortedNumericDocValuesRangeQuery(field, l, u, query);
776760
}
777-
--u;
778761
}
779-
}
780-
Query query = LongPoint.newRangeQuery(field, l, u);
781-
if (hasDocValues) {
782-
Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(field, l, u);
783-
query = new IndexOrDocValuesQuery(query, dvQuery);
784-
if (context.indexSortedOnField(field)) {
785-
query = new IndexSortSortedNumericDocValuesRangeQuery(field, l, u, query);
786-
}
787-
}
788-
return query;
762+
return query;
763+
});
789764
}
790765

791766
@Override
@@ -855,7 +830,7 @@ public static boolean hasDecimalPart(Object number) {
855830
/**
856831
* Returns -1, 0, or 1 if the value is lower than, equal to, or greater than 0
857832
*/
858-
double signum(Object value) {
833+
static double signum(Object value) {
859834
if (value instanceof Number) {
860835
double doubleValue = ((Number) value).doubleValue();
861836
return Math.signum(doubleValue);
@@ -906,6 +881,47 @@ public static long objectToLong(Object value, boolean coerce) {
906881
String stringValue = (value instanceof BytesRef) ? ((BytesRef) value).utf8ToString() : value.toString();
907882
return Numbers.toLong(stringValue, coerce);
908883
}
884+
885+
/**
886+
* Processes query bounds into {@code long}s and delegates the
887+
* provided {@code builder} to build a range query.
888+
*/
889+
public static Query longRangeQuery(
890+
Object lowerTerm,
891+
Object upperTerm,
892+
boolean includeLower,
893+
boolean includeUpper,
894+
BiFunction<Long, Long, Query> builder
895+
) {
896+
long l = Long.MIN_VALUE;
897+
long u = Long.MAX_VALUE;
898+
if (lowerTerm != null) {
899+
l = objectToLong(lowerTerm, true);
900+
// if the lower bound is decimal:
901+
// - if the bound is positive then we increment it:
902+
// if lowerTerm=1.5 then the (inclusive) bound becomes 2
903+
// - if the bound is negative then we leave it as is:
904+
// if lowerTerm=-1.5 then the (inclusive) bound becomes -1 due to the call to longValue
905+
boolean lowerTermHasDecimalPart = hasDecimalPart(lowerTerm);
906+
if ((lowerTermHasDecimalPart == false && includeLower == false) || (lowerTermHasDecimalPart && signum(lowerTerm) > 0)) {
907+
if (l == Long.MAX_VALUE) {
908+
return new MatchNoDocsQuery();
909+
}
910+
++l;
911+
}
912+
}
913+
if (upperTerm != null) {
914+
u = objectToLong(upperTerm, true);
915+
boolean upperTermHasDecimalPart = hasDecimalPart(upperTerm);
916+
if ((upperTermHasDecimalPart == false && includeUpper == false) || (upperTermHasDecimalPart && signum(upperTerm) < 0)) {
917+
if (u == Long.MIN_VALUE) {
918+
return new MatchNoDocsQuery();
919+
}
920+
--u;
921+
}
922+
}
923+
return builder.apply(l, u);
924+
}
909925
}
910926

911927
public static final class NumberFieldType extends SimpleMappedFieldType {

x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/ScriptLongMappedFieldType.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,25 @@
66

77
package org.elasticsearch.xpack.runtimefields.mapper;
88

9+
import com.carrotsearch.hppc.LongHashSet;
10+
import com.carrotsearch.hppc.LongSet;
11+
912
import org.apache.lucene.search.Query;
13+
import org.elasticsearch.common.geo.ShapeRelation;
1014
import org.elasticsearch.common.lucene.search.Queries;
15+
import org.elasticsearch.common.time.DateMathParser;
1116
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
1217
import org.elasticsearch.index.query.QueryShardContext;
1318
import org.elasticsearch.script.Script;
1419
import org.elasticsearch.xpack.runtimefields.LongScriptFieldScript;
1520
import org.elasticsearch.xpack.runtimefields.fielddata.ScriptLongFieldData;
1621
import org.elasticsearch.xpack.runtimefields.query.LongScriptFieldExistsQuery;
22+
import org.elasticsearch.xpack.runtimefields.query.LongScriptFieldRangeQuery;
1723
import org.elasticsearch.xpack.runtimefields.query.LongScriptFieldTermQuery;
24+
import org.elasticsearch.xpack.runtimefields.query.LongScriptFieldTermsQuery;
1825

26+
import java.time.ZoneId;
27+
import java.util.List;
1928
import java.util.Map;
2029

2130
public class ScriptLongMappedFieldType extends AbstractScriptMappedFieldType {
@@ -52,6 +61,27 @@ public Query existsQuery(QueryShardContext context) {
5261
return new LongScriptFieldExistsQuery(script, leafFactory(context), name());
5362
}
5463

64+
@Override
65+
public Query rangeQuery(
66+
Object lowerTerm,
67+
Object upperTerm,
68+
boolean includeLower,
69+
boolean includeUpper,
70+
ShapeRelation relation,
71+
ZoneId timeZone,
72+
DateMathParser parser,
73+
QueryShardContext context
74+
) {
75+
checkAllowExpensiveQueries(context);
76+
return NumberType.longRangeQuery(
77+
lowerTerm,
78+
upperTerm,
79+
includeLower,
80+
includeUpper,
81+
(l, u) -> new LongScriptFieldRangeQuery(script, leafFactory(context), name(), l, u)
82+
);
83+
}
84+
5585
@Override
5686
public Query termQuery(Object value, QueryShardContext context) {
5787
if (NumberType.hasDecimalPart(value)) {
@@ -60,4 +90,23 @@ public Query termQuery(Object value, QueryShardContext context) {
6090
checkAllowExpensiveQueries(context);
6191
return new LongScriptFieldTermQuery(script, leafFactory(context), name(), NumberType.objectToLong(value, true));
6292
}
93+
94+
@Override
95+
public Query termsQuery(List<?> values, QueryShardContext context) {
96+
if (values.isEmpty()) {
97+
return Queries.newMatchAllQuery();
98+
}
99+
LongSet terms = new LongHashSet(values.size());
100+
for (Object value : values) {
101+
if (NumberType.hasDecimalPart(value)) {
102+
continue;
103+
}
104+
terms.add(NumberType.objectToLong(value, true));
105+
}
106+
if (terms.isEmpty()) {
107+
return Queries.newMatchNoDocsQuery("All values have a decimal part");
108+
}
109+
checkAllowExpensiveQueries(context);
110+
return new LongScriptFieldTermsQuery(script, leafFactory(context), name(), terms);
111+
}
63112
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.runtimefields.query;
8+
9+
import org.elasticsearch.script.Script;
10+
import org.elasticsearch.xpack.runtimefields.LongScriptFieldScript;
11+
12+
import java.util.Objects;
13+
14+
public class LongScriptFieldRangeQuery extends AbstractLongScriptFieldQuery {
15+
private final long lowerValue;
16+
private final long upperValue;
17+
18+
public LongScriptFieldRangeQuery(
19+
Script script,
20+
LongScriptFieldScript.LeafFactory leafFactory,
21+
String fieldName,
22+
long lowerValue,
23+
long upperValue
24+
) {
25+
super(script, leafFactory, fieldName);
26+
this.lowerValue = lowerValue;
27+
this.upperValue = upperValue;
28+
assert lowerValue <= upperValue;
29+
}
30+
31+
@Override
32+
protected boolean matches(long[] values, int count) {
33+
for (int i = 0; i < count; i++) {
34+
if (lowerValue <= values[i] && values[i] <= upperValue) {
35+
return true;
36+
}
37+
}
38+
return false;
39+
}
40+
41+
@Override
42+
public final String toString(String field) {
43+
StringBuilder b = new StringBuilder();
44+
if (false == fieldName().contentEquals(field)) {
45+
b.append(fieldName()).append(':');
46+
}
47+
b.append('[').append(lowerValue).append(" TO ").append(upperValue).append(']');
48+
return b.toString();
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return Objects.hash(super.hashCode(), lowerValue, upperValue);
54+
}
55+
56+
@Override
57+
public boolean equals(Object obj) {
58+
if (false == super.equals(obj)) {
59+
return false;
60+
}
61+
LongScriptFieldRangeQuery other = (LongScriptFieldRangeQuery) obj;
62+
return lowerValue == other.lowerValue && upperValue == other.upperValue;
63+
}
64+
65+
long lowerValue() {
66+
return lowerValue;
67+
}
68+
69+
long upperValue() {
70+
return upperValue;
71+
}
72+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.runtimefields.query;
8+
9+
import com.carrotsearch.hppc.LongSet;
10+
11+
import org.elasticsearch.script.Script;
12+
import org.elasticsearch.xpack.runtimefields.LongScriptFieldScript;
13+
14+
import java.util.Objects;
15+
16+
public class LongScriptFieldTermsQuery extends AbstractLongScriptFieldQuery {
17+
private final LongSet terms;
18+
19+
public LongScriptFieldTermsQuery(Script script, LongScriptFieldScript.LeafFactory leafFactory, String fieldName, LongSet terms) {
20+
super(script, leafFactory, fieldName);
21+
this.terms = terms;
22+
}
23+
24+
@Override
25+
protected boolean matches(long[] values, int count) {
26+
for (int i = 0; i < count; i++) {
27+
if (terms.contains(values[i])) {
28+
return true;
29+
}
30+
}
31+
return false;
32+
}
33+
34+
@Override
35+
public final String toString(String field) {
36+
if (fieldName().contentEquals(field)) {
37+
return terms.toString();
38+
}
39+
return fieldName() + ":" + terms;
40+
}
41+
42+
@Override
43+
public int hashCode() {
44+
return Objects.hash(super.hashCode(), terms);
45+
}
46+
47+
@Override
48+
public boolean equals(Object obj) {
49+
if (false == super.equals(obj)) {
50+
return false;
51+
}
52+
LongScriptFieldTermsQuery other = (LongScriptFieldTermsQuery) obj;
53+
return terms.equals(other.terms);
54+
}
55+
56+
LongSet terms() {
57+
return terms;
58+
}
59+
}

0 commit comments

Comments
 (0)