@@ -35,28 +35,29 @@ protected Object expected(Map<String, Object> fieldMapping, Object value, TestCo
35
35
var values = extractedFieldValues .values ();
36
36
37
37
var nullValue = switch (fieldMapping .get ("null_value" )) {
38
- case String s -> convert (s , null );
38
+ case String s -> convert (s , null , false );
39
39
case null -> null ;
40
40
default -> throw new IllegalStateException ("Unexpected null_value format" );
41
41
};
42
42
43
43
if (params .preference () == MappedFieldType .FieldExtractPreference .DOC_VALUES && hasDocValues (fieldMapping , true )) {
44
44
if (values instanceof List <?> == false ) {
45
- var point = convert (values , nullValue );
45
+ var point = convert (values , nullValue , testContext . isMultifield () );
46
46
return point != null ? point .getEncoded () : null ;
47
47
}
48
48
49
49
var resultList = ((List <Object >) values ).stream ()
50
- .map (v -> convert (v , nullValue ))
50
+ .map (v -> convert (v , nullValue , testContext . isMultifield () ))
51
51
.filter (Objects ::nonNull )
52
52
.map (GeoPoint ::getEncoded )
53
53
.sorted ()
54
54
.toList ();
55
55
return maybeFoldList (resultList );
56
56
}
57
57
58
+ // stored source is used
58
59
if (params .syntheticSource () == false ) {
59
- return exactValuesFromSource (values , nullValue );
60
+ return exactValuesFromSource (values , nullValue , false );
60
61
}
61
62
62
63
// Usually implementation of block loader from source adjusts values read from source
@@ -67,25 +68,25 @@ protected Object expected(Map<String, Object> fieldMapping, Object value, TestCo
67
68
// That is unless "synthetic_source_keep" forces fallback synthetic source again.
68
69
69
70
if (testContext .forceFallbackSyntheticSource ()) {
70
- return exactValuesFromSource (values , nullValue );
71
+ return exactValuesFromSource (values , nullValue , false );
71
72
}
72
73
73
74
String syntheticSourceKeep = (String ) fieldMapping .getOrDefault ("synthetic_source_keep" , "none" );
74
75
if (syntheticSourceKeep .equals ("all" )) {
75
- return exactValuesFromSource (values , nullValue );
76
+ return exactValuesFromSource (values , nullValue , false );
76
77
}
77
78
if (syntheticSourceKeep .equals ("arrays" ) && extractedFieldValues .documentHasObjectArrays ()) {
78
- return exactValuesFromSource (values , nullValue );
79
+ return exactValuesFromSource (values , nullValue , false );
79
80
}
80
81
81
82
// synthetic source and doc_values are present
82
83
if (hasDocValues (fieldMapping , true )) {
83
84
if (values instanceof List <?> == false ) {
84
- return toWKB (normalize (convert (values , nullValue )));
85
+ return toWKB (normalize (convert (values , nullValue , false )));
85
86
}
86
87
87
88
var resultList = ((List <Object >) values ).stream ()
88
- .map (v -> convert (v , nullValue ))
89
+ .map (v -> convert (v , nullValue , false ))
89
90
.filter (Objects ::nonNull )
90
91
.sorted (Comparator .comparingLong (GeoPoint ::getEncoded ))
91
92
.map (p -> toWKB (normalize (p )))
@@ -94,16 +95,20 @@ protected Object expected(Map<String, Object> fieldMapping, Object value, TestCo
94
95
}
95
96
96
97
// synthetic source but no doc_values so using fallback synthetic source
97
- return exactValuesFromSource (values , nullValue );
98
+ return exactValuesFromSource (values , nullValue , false );
98
99
}
99
100
100
101
@ SuppressWarnings ("unchecked" )
101
- private Object exactValuesFromSource (Object value , GeoPoint nullValue ) {
102
+ private Object exactValuesFromSource (Object value , GeoPoint nullValue , boolean needsMultifieldAdjustment ) {
102
103
if (value instanceof List <?> == false ) {
103
- return toWKB (convert (value , nullValue ));
104
+ return toWKB (convert (value , nullValue , needsMultifieldAdjustment ));
104
105
}
105
106
106
- var resultList = ((List <Object >) value ).stream ().map (v -> convert (v , nullValue )).filter (Objects ::nonNull ).map (this ::toWKB ).toList ();
107
+ var resultList = ((List <Object >) value ).stream ()
108
+ .map (v -> convert (v , nullValue , needsMultifieldAdjustment ))
109
+ .filter (Objects ::nonNull )
110
+ .map (this ::toWKB )
111
+ .toList ();
107
112
return maybeFoldList (resultList );
108
113
}
109
114
@@ -163,14 +168,17 @@ private void processLeafLevel(Object value, ArrayList<Object> extracted) {
163
168
}
164
169
165
170
@ SuppressWarnings ("unchecked" )
166
- private GeoPoint convert (Object value , GeoPoint nullValue ) {
171
+ private GeoPoint convert (Object value , GeoPoint nullValue , boolean needsMultifieldAdjustment ) {
167
172
if (value == null ) {
168
- return nullValue ;
173
+ if (nullValue == null ) {
174
+ return null ;
175
+ }
176
+ return possiblyAdjustMultifieldValue (nullValue , needsMultifieldAdjustment );
169
177
}
170
178
171
179
if (value instanceof String s ) {
172
180
try {
173
- return new GeoPoint (s );
181
+ return possiblyAdjustMultifieldValue ( new GeoPoint (s ), needsMultifieldAdjustment );
174
182
} catch (Exception e ) {
175
183
return null ;
176
184
}
@@ -180,16 +188,30 @@ private GeoPoint convert(Object value, GeoPoint nullValue) {
180
188
if (m .get ("type" ) != null ) {
181
189
var coordinates = (List <Double >) m .get ("coordinates" );
182
190
// Order is GeoJSON is lon,lat
183
- return new GeoPoint (coordinates .get (1 ), coordinates .get (0 ));
191
+ return possiblyAdjustMultifieldValue ( new GeoPoint (coordinates .get (1 ), coordinates .get (0 )), needsMultifieldAdjustment );
184
192
} else {
185
- return new GeoPoint ((Double ) m .get ("lat" ), (Double ) m .get ("lon" ));
193
+ return possiblyAdjustMultifieldValue ( new GeoPoint ((Double ) m .get ("lat" ), (Double ) m .get ("lon" )), needsMultifieldAdjustment );
186
194
}
187
195
}
188
196
189
197
// Malformed values are excluded
190
198
return null ;
191
199
}
192
200
201
+ private GeoPoint possiblyAdjustMultifieldValue (GeoPoint point , boolean isMultifield ) {
202
+ // geo_point multifields are parsed from a geohash representation of the original point (GeoPointFieldMapper#index)
203
+ // and it's not exact.
204
+ // So if this is a multifield we need another adjustment here.
205
+ // Note that this does not apply to block loader from source because in this case we parse raw original values.
206
+ // Same thing happens with synthetic source since it is generated from the parent field data that didn't go through multi field
207
+ // parsing logic.
208
+ if (isMultifield ) {
209
+ return point .resetFromString (point .geohash ());
210
+ }
211
+
212
+ return point ;
213
+ }
214
+
193
215
private GeoPoint normalize (GeoPoint point ) {
194
216
if (point == null ) {
195
217
return null ;
0 commit comments