Skip to content

Commit 16acd4e

Browse files
committed
Fix legacy GeoPointField decoding in FieldStats
LegacyGeoPointField was using the wrong decoding for min/max prefix coded GeoPoint Terms. This commit applies the correcct decoding. Note that the min/max values, though, are likely useless anyway since they map to a low resolution morton encoded version of the point; not something that is really of value for field stats.
1 parent 1e8dd17 commit 16acd4e

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

core/src/main/java/org/elasticsearch/index/mapper/BaseGeoPointFieldMapper.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525
import org.apache.lucene.index.IndexableField;
2626
import org.apache.lucene.index.Terms;
2727
import org.apache.lucene.search.Query;
28+
import org.apache.lucene.spatial.util.MortonEncoder;
29+
import org.apache.lucene.util.BytesRef;
2830
import org.apache.lucene.util.LegacyNumericUtils;
29-
import org.apache.lucene.util.NumericUtils;
3031
import org.elasticsearch.ElasticsearchParseException;
3132
import org.elasticsearch.Version;
3233
import org.elasticsearch.action.fieldstats.FieldStats;
@@ -306,6 +307,7 @@ public static class LegacyGeoPointFieldType extends GeoPointFieldType {
306307

307308
protected MappedFieldType latFieldType;
308309
protected MappedFieldType lonFieldType;
310+
protected boolean numericEncoded;
309311

310312
LegacyGeoPointFieldType() {}
311313

@@ -316,6 +318,7 @@ public static class LegacyGeoPointFieldType extends GeoPointFieldType {
316318
this.geoHashPrefixEnabled = ref.geoHashPrefixEnabled;
317319
this.latFieldType = ref.latFieldType; // copying ref is ok, this can never be modified
318320
this.lonFieldType = ref.lonFieldType; // copying ref is ok, this can never be modified
321+
this.numericEncoded = ref.numericEncoded;
319322
}
320323

321324
@Override
@@ -329,15 +332,16 @@ public boolean equals(Object o) {
329332
LegacyGeoPointFieldType that = (LegacyGeoPointFieldType) o;
330333
return geoHashPrecision == that.geoHashPrecision &&
331334
geoHashPrefixEnabled == that.geoHashPrefixEnabled &&
335+
numericEncoded == that.numericEncoded &&
332336
java.util.Objects.equals(geoHashFieldType, that.geoHashFieldType) &&
333337
java.util.Objects.equals(latFieldType, that.latFieldType) &&
334338
java.util.Objects.equals(lonFieldType, that.lonFieldType);
335339
}
336340

337341
@Override
338342
public int hashCode() {
339-
return java.util.Objects.hash(super.hashCode(), geoHashFieldType, geoHashPrecision, geoHashPrefixEnabled, latFieldType,
340-
lonFieldType);
343+
return java.util.Objects.hash(super.hashCode(), geoHashFieldType, geoHashPrecision, geoHashPrefixEnabled,
344+
numericEncoded, latFieldType, lonFieldType);
341345
}
342346

343347
@Override
@@ -437,10 +441,9 @@ public FieldStats.GeoPoint stats(IndexReader reader) throws IOException {
437441
if (terms == null) {
438442
return new FieldStats.GeoPoint(reader.maxDoc(), 0L, -1L, -1L, isSearchable(), isAggregatable());
439443
}
440-
GeoPoint minPt = GeoPoint.fromGeohash(NumericUtils.sortableBytesToLong(terms.getMin().bytes, terms.getMin().offset));
441-
GeoPoint maxPt = GeoPoint.fromGeohash(NumericUtils.sortableBytesToLong(terms.getMax().bytes, terms.getMax().offset));
442444
return new FieldStats.GeoPoint(reader.maxDoc(), terms.getDocCount(), -1L, terms.getSumTotalTermFreq(), isSearchable(),
443-
isAggregatable(), minPt, maxPt);
445+
isAggregatable(), prefixCodedToGeoPoint(terms.getMin(), numericEncoded),
446+
prefixCodedToGeoPoint(terms.getMax(), numericEncoded));
444447
}
445448
}
446449

@@ -657,4 +660,19 @@ public FieldMapper updateFieldType(Map<String, MappedFieldType> fullNameToFieldT
657660
updated.lonMapper = lonUpdated;
658661
return updated;
659662
}
663+
664+
private static GeoPoint prefixCodedToGeoPoint(BytesRef val, boolean isGeoCoded) {
665+
final long encoded = isGeoCoded ? prefixCodedToGeoCoded(val) : LegacyNumericUtils.prefixCodedToLong(val);
666+
return new GeoPoint(MortonEncoder.decodeLatitude(encoded), MortonEncoder.decodeLongitude(encoded));
667+
}
668+
669+
private static long prefixCodedToGeoCoded(BytesRef val) {
670+
long result = fromBytes((byte)0, (byte)0, (byte)0, (byte)0, val.bytes[val.offset + 0], val.bytes[val.offset + 1],
671+
val.bytes[val.offset + 2], val.bytes[val.offset + 3]);
672+
return result << 32;
673+
}
674+
675+
private static long fromBytes(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8) {
676+
return ((long)b1 & 255L) << 56 | ((long)b2 & 255L) << 48 | ((long)b3 & 255L) << 40 | ((long)b4 & 255L) << 32 | ((long)b5 & 255L) << 24 | ((long)b6 & 255L) << 16 | ((long)b7 & 255L) << 8 | (long)b8 & 255L;
677+
}
660678
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public GeoPointFieldMapper build(BuilderContext context, String simpleName, Mapp
7979
if (context.indexCreatedVersion().before(Version.V_2_3_0)) {
8080
fieldType.setNumericPrecisionStep(GeoPointField.PRECISION_STEP);
8181
fieldType.setNumericType(FieldType.LegacyNumericType.LONG);
82+
((LegacyGeoPointFieldType)fieldType).numericEncoded = true;
8283
}
8384
setupFieldType(context);
8485
return new GeoPointFieldMapper(simpleName, fieldType, defaultFieldType, indexSettings, latMapper, lonMapper,

core/src/test/java/org/elasticsearch/fieldstats/FieldStatsTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,30 @@
1919

2020
package org.elasticsearch.fieldstats;
2121

22+
import org.apache.lucene.geo.GeoTestUtil;
2223
import org.apache.lucene.util.BytesRef;
2324
import org.elasticsearch.Version;
2425
import org.elasticsearch.action.fieldstats.FieldStats;
2526
import org.elasticsearch.action.fieldstats.FieldStatsResponse;
2627
import org.elasticsearch.action.fieldstats.IndexConstraint;
28+
import org.elasticsearch.cluster.metadata.IndexMetaData;
2729
import org.elasticsearch.common.io.stream.BytesStreamOutput;
2830
import org.elasticsearch.common.io.stream.StreamInput;
2931
import org.elasticsearch.common.joda.Joda;
3032
import org.elasticsearch.common.settings.Settings;
3133
import org.elasticsearch.index.mapper.DateFieldMapper;
34+
import org.elasticsearch.plugins.Plugin;
3235
import org.elasticsearch.test.ESSingleNodeTestCase;
36+
import org.elasticsearch.test.InternalSettingsPlugin;
37+
import org.elasticsearch.test.VersionUtils;
3338
import org.joda.time.DateTime;
3439
import org.joda.time.DateTimeZone;
3540

3641
import java.io.IOException;
3742
import java.net.InetAddress;
3843
import java.net.UnknownHostException;
3944
import java.util.ArrayList;
45+
import java.util.Collection;
4046
import java.util.Date;
4147
import java.util.List;
4248
import java.util.Locale;
@@ -52,6 +58,11 @@
5258
/**
5359
*/
5460
public class FieldStatsTests extends ESSingleNodeTestCase {
61+
@Override
62+
protected Collection<Class<? extends Plugin>> getPlugins() {
63+
return pluginList(InternalSettingsPlugin.class);
64+
}
65+
5566
public void testByte() {
5667
testNumberRange("field1", "byte", 12, 18);
5768
testNumberRange("field1", "byte", -5, 5);
@@ -676,6 +687,31 @@ public static FieldStats randomFieldStats(boolean withNullMinMax) throws Unknown
676687
}
677688
}
678689

690+
public void testGeopoint() {
691+
Version version = VersionUtils.randomVersionBetween(random(), Version.V_2_0_0, Version.CURRENT);
692+
Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, version).build();
693+
createIndex("test", settings, "test",
694+
"field_index", makeType("geo_point", true, false, false));
695+
version = Version.CURRENT;
696+
settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, version).build();
697+
createIndex("test5x", settings, "test",
698+
"field_index", makeType("geo_point", true, false, false));
699+
int numDocs = random().nextInt(20);
700+
for (int i = 0; i <= numDocs; ++i) {
701+
double lat = GeoTestUtil.nextLatitude();
702+
double lon = GeoTestUtil.nextLongitude();
703+
client().prepareIndex(random().nextBoolean() ? "test" : "test5x", "test").setSource("field_index", lat + "," + lon).get();
704+
}
705+
706+
client().admin().indices().prepareRefresh().get();
707+
FieldStatsResponse result = client().prepareFieldStats().setFields("field_index").get();
708+
FieldStats stats = result.getAllFieldStats().get("field_index");
709+
assertEquals(stats.getDisplayType(), "geo_point");
710+
// min/max random testing is not straightforward; there are 3 different encodings since V_2_0
711+
// e.g., before V2_3 used legacy numeric encoding which is wildly different from V_2_3 which is morton encoded
712+
// which is wildly different from V_5_0 which is point encoded. Skipping min/max in favor of testing
713+
}
714+
679715
private void assertSerialization(FieldStats stats, Version version) throws IOException {
680716
BytesStreamOutput output = new BytesStreamOutput();
681717
output.setVersion(version);

0 commit comments

Comments
 (0)