Compare commits

...
Author SHA1 Message Date
Dominik Schmidt 4636a9c696 fix(search): index a resource with out-of-range coordinates without a geopoint
Corrupt EXIF (latitude beyond 90, longitude beyond 180) made OpenSearch reject the whole document on the geo_point sibling while bleve indexed it at the pole. Both engines now skip the sibling and keep the location object; the parity suite pins it.
2026-09-08 18:38:12 +02:00
4 changed files with 51 additions and 1 deletions

No files matched your search

+5
View File
@@ -47,5 +47,10 @@ func addGeopointSibling(m map[string]any, dottedPath string) {
if !hasLon || !hasLat {
return
}
// corrupt EXIF: OpenSearch rejects the whole document on an out-of-range
// geo_point, bleve would index it at the pole; both index it without one
if lat < -90 || lat > 90 || lon < -180 || lon > 180 {
return
}
parent[leaf+GeopointSuffix] = map[string]any{"lat": lat, "lon": lon}
}
+21
View File
@@ -64,6 +64,27 @@ var _ = Describe("PrepareForIndex geopoint", func() {
Expect(gp["lon"]).To(Equal(lon))
})
It("skips out-of-range geopoints but keeps the object", func() {
type geoDoc struct {
Location *struct {
Longitude *float64 `json:"longitude,omitempty"`
Latitude *float64 `json:"latitude,omitempty"`
} `json:"location,omitempty"`
}
for _, c := range [][2]float64{{11.1, 100}, {11.1, -90.5}, {180.5, 49.4}, {-181, 49.4}} {
lon, lat := c[0], c[1]
doc := geoDoc{Location: &struct {
Longitude *float64 `json:"longitude,omitempty"`
Latitude *float64 `json:"latitude,omitempty"`
}{Longitude: &lon, Latitude: &lat}}
m, err := PrepareForIndex(doc, map[string]FieldOpts{"location": {Type: TypeGeopoint}})
Expect(err).ToNot(HaveOccurred())
Expect(m).To(HaveKey("location"), "lon=%v lat=%v", lon, lat)
Expect(m).ToNot(HaveKey("location"+GeopointSuffix), "lon=%v lat=%v", lon, lat)
}
})
It("skips incomplete geopoints", func() {
type geoDoc struct {
Location *struct {
+2
View File
@@ -687,9 +687,11 @@ Fixtures:
- `some_song.mp3`, ID = 1$1!5, MimeType = audio/mpeg
- `team.jpg`, ID = 1$1!6, MimeType = image/jpeg
- `lost.jpg`, ID = 1$1!7, MimeType = image/jpeg
| Case | Query | expected | bleve | OpenSearch | same? |
|---|---|---|---|---|---|
| METADATA-01 | `*song*` reads `Audio` | all 16 fields unchanged | all 16 fields unchanged | all 16 fields unchanged | ✅ |
| METADATA-02 | `*team*` reads `Location` | all 3 fields unchanged | all 3 fields unchanged | all 3 fields unchanged | ✅ |
| METADATA-04 | `*lost*` reads `Location` | Latitude=100, Longitude=11.1 | Latitude=100, Longitude=11.1 | Latitude=100, Longitude=11.1 | ✅ |
| METADATA-03 | `*team*` reads `Audio` | none | none | none | ✅ |
@@ -43,6 +43,17 @@ func metadataGroup() responseGroup {
}),
)
// corrupt EXIF: latitude beyond 90 must not lose the file from the index
lost := fixtureDoc("lost.jpg",
withID("1$1!7"),
withMime("image/jpeg"),
withLocation(&libregraph.GeoCoordinates{
Altitude: libregraph.PtrFloat64(0),
Latitude: libregraph.PtrFloat64(100),
Longitude: libregraph.PtrFloat64(11.1),
}),
)
indexed := []string{
"Album=Some Album",
"AlbumArtist=Some AlbumArtist",
@@ -70,7 +81,7 @@ func metadataGroup() responseGroup {
return responseGroup{
name: "metadata",
fixtures: []search.Resource{song, team},
fixtures: []search.Resource{song, team, lost},
cases: []responseCase{
{
id: 1, query: `*song*`, reads: "Audio", want: unchanged(indexed),
@@ -109,6 +120,17 @@ func metadataGroup() responseGroup {
})
}),
},
{
id: 4, query: `*lost*`, reads: "Location", want: []string{"Latitude=100", "Longitude=11.1"},
read: readsMany(func(m *searchMessage.Match) []string {
location := m.GetEntity().GetLocation()
return []string{
fmt.Sprintf("Latitude=%v", location.GetLatitude()),
fmt.Sprintf("Longitude=%v", location.GetLongitude()),
}
}),
},
{
id: 3, query: `*team*`, reads: "Audio", want: []string{"none"},
read: reads(func(m *searchMessage.Match) string {