Skip to content

Commit eec2682

Browse files
authored
Allow doc-values only search on geo_point fields (#83395)
Similar to #82409, but for geo_point fields. Allows searching on geo_point fields when those fields are not indexed (index: false) but just doc values are enabled. Also adds distance feature query support for date fields (bringing date field to feature parity with runtime fields) This enables searches on archive data, which has access to doc values but not index structures. When combined with searchable snapshots, it allows downloading only data for a given (doc value) field to quickly filter down to a select set of documents. Relates #81210 and #52728
1 parent 7f827bb commit eec2682

File tree

10 files changed

+207
-23
lines changed

10 files changed

+207
-23
lines changed

docs/changelog/83395.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 83395
2+
summary: Allow doc-values only search on geo_point fields
3+
area: Mapping
4+
type: enhancement
5+
issues: []

docs/reference/mapping/params/doc-values.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ sorting and aggregations. Doc values are supported on almost all field types,
1818
with the __notable exception of `text` and `annotated_text` fields__.
1919

2020
<<number,Numeric types>>, <<date,date types>>, the <<boolean,boolean type>>,
21-
the <<ip,ip type>> and the <<keyword,keyword type>>
21+
<<ip,ip type>>, <<geo-point,geo_point type>> and the <<keyword,keyword type>>
2222
can also be queried using term or range-based queries
2323
when they are not <<mapping-index,indexed>> but only have doc values enabled.
2424
Query performance on doc values is much slower than on index structures, but

docs/reference/mapping/types/geo-point.asciidoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ The following parameters are accepted by `geo_point` fields:
137137

138138
<<mapping-index,`index`>>::
139139

140-
Should the field be searchable? Accepts `true` (default) and `false`.
140+
Should the field be quickly searchable? Accepts `true` (default) and
141+
`false`. Fields that only have <<doc-values,`doc_values`>>
142+
enabled can still be queried, albeit slower.
141143

142144
<<null-value,`null_value`>>::
143145

docs/reference/query-dsl.asciidoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ the stability of the cluster. Those queries can be categorised as follows:
3333

3434
* Queries that need to do linear scans to identify matches:
3535
** <<query-dsl-script-query,`script` queries>>
36-
** queries on <<number,numeric>>, <<date,date>>, <<boolean,boolean>>, <<ip,ip>> or <<keyword,keyword>> fields
36+
** queries on <<number,numeric>>, <<date,date>>, <<boolean,boolean>>, <<ip,ip>>,
37+
<<geo-point,geo_point>> or <<keyword,keyword>> fields
3738
that are not indexed but have <<doc-values,doc values>> enabled
3839

3940
* Queries that have a high up-front cost:

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/field_caps/10_basic.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ setup:
9595
non_indexed_ip:
9696
type: ip
9797
index: false
98+
non_indexed_geo_point:
99+
type: geo_point
100+
index: false
98101
geo:
99102
type: keyword
100103
object:
@@ -270,6 +273,19 @@ setup:
270273

271274
- match: {fields.non_indexed_ip.ip.searchable: true}
272275

276+
---
277+
"Field caps for geo_point field with only doc values":
278+
- skip:
279+
version: " - 8.0.99"
280+
reason: "doc values search was added in 8.1.0"
281+
- do:
282+
field_caps:
283+
index: 'test1,test2,test3'
284+
fields: non_indexed_geo_point
285+
286+
- match: {fields.non_indexed_geo_point.geo_point.searchable: true}
287+
288+
273289
---
274290
"Get object and nested field caps":
275291

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search/390_doc_values_search.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ setup:
4545
ip:
4646
type: ip
4747
index: false
48+
geo_point:
49+
type: geo_point
50+
index: false
4851

4952
- do:
5053
index:
@@ -62,6 +65,7 @@ setup:
6265
keyword: "key1"
6366
boolean: "false"
6467
ip: "192.168.0.1"
68+
geo_point: [13.5, 34.89]
6569

6670
- do:
6771
index:
@@ -79,6 +83,7 @@ setup:
7983
keyword: "key2"
8084
boolean: "true"
8185
ip: "192.168.0.2"
86+
geo_point : [-63.24, 31.0]
8287

8388
- do:
8489
indices.refresh: {}
@@ -236,6 +241,27 @@ setup:
236241
body: { query: { range: { date: { gte: "2017/01/01" } } } }
237242
- length: { hits.hits: 2 }
238243

244+
---
245+
"Test distance_feature query on date field where only doc values are enabled":
246+
- skip:
247+
version: " - 8.0.99"
248+
reason: "doc values search was added in 8.1.0"
249+
250+
- do:
251+
search:
252+
index: test
253+
body:
254+
query:
255+
bool:
256+
should:
257+
distance_feature:
258+
field: "date"
259+
pivot: "7d"
260+
origin: "now"
261+
- length: { hits.hits: 2 }
262+
- match: {hits.hits.0._id: "2" }
263+
- match: {hits.hits.1._id: "1" }
264+
239265
---
240266
"Test match query on keyword field where only doc values are enabled":
241267

@@ -316,3 +342,42 @@ setup:
316342
index: test
317343
body: { query: { range: { ip: { gte: "192.168.0.1" } } } }
318344
- length: { hits.hits: 2 }
345+
346+
---
347+
"Test geo shape query on geo_point field where only doc values are enabled":
348+
- skip:
349+
version: " - 8.0.99"
350+
reason: "doc values search was added in 8.1.0"
351+
352+
- do:
353+
search:
354+
index: test
355+
body:
356+
query:
357+
geo_shape:
358+
geo_point:
359+
shape:
360+
type: "envelope"
361+
coordinates: [ [ -70, 32 ], [ -50, 30 ] ]
362+
- match: {hits.total.value: 1}
363+
364+
---
365+
"Test distance_feature query on geo_point field where only doc values are enabled":
366+
- skip:
367+
version: " - 8.0.99"
368+
reason: "doc values search was added in 8.1.0"
369+
370+
- do:
371+
search:
372+
index: test
373+
body:
374+
query:
375+
bool:
376+
should:
377+
distance_feature:
378+
field: geo_point
379+
pivot: "1000km"
380+
origin: [0.0, 0.0]
381+
- length: { hits.hits: 2 }
382+
- match: {hits.hits.0._id: "1" }
383+
- match: {hits.hits.1._id: "2" }

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

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@
4242
import org.elasticsearch.script.ScriptCompiler;
4343
import org.elasticsearch.script.field.DateMillisDocValuesField;
4444
import org.elasticsearch.script.field.DateNanosDocValuesField;
45+
import org.elasticsearch.script.field.SortedNumericDocValuesLongFieldScript;
4546
import org.elasticsearch.script.field.ToScriptField;
4647
import org.elasticsearch.search.DocValueFormat;
4748
import org.elasticsearch.search.lookup.FieldValues;
4849
import org.elasticsearch.search.lookup.SearchLookup;
50+
import org.elasticsearch.search.runtime.LongScriptFieldDistanceFeatureQuery;
4951

5052
import java.io.IOException;
5153
import java.text.NumberFormat;
@@ -86,6 +88,11 @@ public long convert(Instant instant) {
8688
return instant.toEpochMilli();
8789
}
8890

91+
@Override
92+
public long convert(TimeValue timeValue) {
93+
return timeValue.millis();
94+
}
95+
8996
@Override
9097
public Instant toInstant(long value) {
9198
return Instant.ofEpochMilli(value);
@@ -105,18 +112,18 @@ public long roundDownToMillis(long value) {
105112
public long roundUpToMillis(long value) {
106113
return value;
107114
}
108-
109-
@Override
110-
protected Query distanceFeatureQuery(String field, float boost, long origin, TimeValue pivot) {
111-
return LongPoint.newDistanceFeatureQuery(field, boost, origin, pivot.getMillis());
112-
}
113115
},
114116
NANOSECONDS(DATE_NANOS_CONTENT_TYPE, NumericType.DATE_NANOSECONDS, DateNanosDocValuesField::new) {
115117
@Override
116118
public long convert(Instant instant) {
117119
return toLong(instant);
118120
}
119121

122+
@Override
123+
public long convert(TimeValue timeValue) {
124+
return timeValue.nanos();
125+
}
126+
120127
@Override
121128
public Instant toInstant(long value) {
122129
return DateUtils.toInstant(value);
@@ -141,11 +148,6 @@ public long roundUpToMillis(long value) {
141148
return DateUtils.toMilliSeconds(value - 1L) + 1L;
142149
}
143150
}
144-
145-
@Override
146-
protected Query distanceFeatureQuery(String field, float boost, long origin, TimeValue pivot) {
147-
return LongPoint.newDistanceFeatureQuery(field, boost, origin, pivot.getNanos());
148-
}
149151
};
150152

151153
private final String type;
@@ -180,6 +182,11 @@ ToScriptField<SortedNumericDocValues> getDefaultToScriptField() {
180182
*/
181183
public abstract Instant toInstant(long value);
182184

185+
/**
186+
* Convert an {@linkplain TimeValue} into a long value in this resolution.
187+
*/
188+
public abstract long convert(TimeValue timeValue);
189+
183190
/**
184191
* Decode the points representation of this field as milliseconds.
185192
*/
@@ -203,8 +210,6 @@ public static Resolution ofOrdinal(int ord) {
203210
}
204211
throw new IllegalArgumentException("unknown resolution ordinal [" + ord + "]");
205212
}
206-
207-
protected abstract Query distanceFeatureQuery(String field, float boost, long origin, TimeValue pivot);
208213
}
209214

210215
private static DateFieldMapper toType(FieldMapper in) {
@@ -596,10 +601,22 @@ public static long parseToLong(
596601

597602
@Override
598603
public Query distanceFeatureQuery(Object origin, String pivot, SearchExecutionContext context) {
604+
failIfNotIndexedNorDocValuesFallback(context);
599605
long originLong = parseToLong(origin, true, null, null, context::nowInMillis);
600606
TimeValue pivotTime = TimeValue.parseTimeValue(pivot, "distance_feature.pivot");
607+
long pivotLong = resolution.convert(pivotTime);
601608
// As we already apply boost in AbstractQueryBuilder::toQuery, we always passing a boost of 1.0 to distanceFeatureQuery
602-
return resolution.distanceFeatureQuery(name(), 1.0f, originLong, pivotTime);
609+
if (isIndexed()) {
610+
return LongPoint.newDistanceFeatureQuery(name(), 1.0f, originLong, pivotLong);
611+
} else {
612+
return new LongScriptFieldDistanceFeatureQuery(
613+
new Script(""),
614+
ctx -> new SortedNumericDocValuesLongFieldScript(name(), context.lookup(), ctx),
615+
name(),
616+
originLong,
617+
pivotLong
618+
);
619+
}
603620
}
604621

605622
@Override

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
import org.elasticsearch.script.Script;
3838
import org.elasticsearch.script.ScriptCompiler;
3939
import org.elasticsearch.script.field.GeoPointDocValuesField;
40+
import org.elasticsearch.script.field.SortedNumericDocValuesLongFieldScript;
4041
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
4142
import org.elasticsearch.search.lookup.FieldValues;
4243
import org.elasticsearch.search.lookup.SearchLookup;
44+
import org.elasticsearch.search.runtime.GeoPointScriptFieldDistanceFeatureQuery;
4345
import org.elasticsearch.xcontent.XContentBuilder;
4446
import org.elasticsearch.xcontent.XContentParser;
4547
import org.elasticsearch.xcontent.support.MapXContentParser;
@@ -268,6 +270,11 @@ public String typeName() {
268270
return CONTENT_TYPE;
269271
}
270272

273+
@Override
274+
public boolean isSearchable() {
275+
return isIndexed() || hasDocValues();
276+
}
277+
271278
@Override
272279
protected Function<List<GeoPoint>, List<Object>> getFormatter(String format) {
273280
return GEO_FORMATTER_FACTORY.getFormatter(format, p -> new Point(p.getLon(), p.getLat()));
@@ -284,6 +291,7 @@ public ValueFetcher valueFetcher(SearchExecutionContext context, String format)
284291

285292
@Override
286293
public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, SearchExecutionContext context) {
294+
failIfNotIndexedNorDocValuesFallback(context);
287295
final LatLonGeometry[] luceneGeometries = GeoShapeUtils.toLuceneGeometry(fieldName, context, shape, relation);
288296
if (luceneGeometries.length == 0) {
289297
return new MatchNoDocsQuery();
@@ -296,10 +304,15 @@ public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relat
296304
} else {
297305
luceneRelation = relation.getLuceneRelation();
298306
}
299-
Query query = LatLonPoint.newGeometryQuery(fieldName, luceneRelation, luceneGeometries);
300-
if (hasDocValues()) {
301-
Query dvQuery = LatLonDocValuesField.newSlowGeometryQuery(fieldName, luceneRelation, luceneGeometries);
302-
query = new IndexOrDocValuesQuery(query, dvQuery);
307+
Query query;
308+
if (isIndexed()) {
309+
query = LatLonPoint.newGeometryQuery(fieldName, luceneRelation, luceneGeometries);
310+
if (hasDocValues()) {
311+
Query dvQuery = LatLonDocValuesField.newSlowGeometryQuery(fieldName, luceneRelation, luceneGeometries);
312+
query = new IndexOrDocValuesQuery(query, dvQuery);
313+
}
314+
} else {
315+
query = LatLonDocValuesField.newSlowGeometryQuery(fieldName, luceneRelation, luceneGeometries);
303316
}
304317
return query;
305318
}
@@ -312,6 +325,7 @@ public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, S
312325

313326
@Override
314327
public Query distanceFeatureQuery(Object origin, String pivot, SearchExecutionContext context) {
328+
failIfNotIndexedNorDocValuesFallback(context);
315329
GeoPoint originGeoPoint;
316330
if (origin instanceof GeoPoint) {
317331
originGeoPoint = (GeoPoint) origin;
@@ -326,8 +340,19 @@ public Query distanceFeatureQuery(Object origin, String pivot, SearchExecutionCo
326340
);
327341
}
328342
double pivotDouble = DistanceUnit.DEFAULT.parse(pivot, DistanceUnit.DEFAULT);
329-
// As we already apply boost in AbstractQueryBuilder::toQuery, we always passing a boost of 1.0 to distanceFeatureQuery
330-
return LatLonPoint.newDistanceFeatureQuery(name(), 1.0f, originGeoPoint.lat(), originGeoPoint.lon(), pivotDouble);
343+
if (isIndexed()) {
344+
// As we already apply boost in AbstractQueryBuilder::toQuery, we always passing a boost of 1.0 to distanceFeatureQuery
345+
return LatLonPoint.newDistanceFeatureQuery(name(), 1.0f, originGeoPoint.lat(), originGeoPoint.lon(), pivotDouble);
346+
} else {
347+
return new GeoPointScriptFieldDistanceFeatureQuery(
348+
new Script(""),
349+
ctx -> new SortedNumericDocValuesLongFieldScript(name(), context.lookup(), ctx),
350+
name(),
351+
originGeoPoint.lat(),
352+
originGeoPoint.lon(),
353+
pivotDouble
354+
);
355+
}
331356
}
332357
}
333358

0 commit comments

Comments
 (0)