Skip to content

Commit 32073da

Browse files
committed
fix: resolve NoSuchElementException when using Point in projection interfaces (#661)
Point fields in projection interfaces were causing NoSuchElementException due to incorrect JSON formatting during result parsing. The Point values stored as 'lon,lat' strings in Redis needed to be properly quoted for Gson's PointTypeAdapter to deserialize them correctly.
1 parent b700ad1 commit 32073da

File tree

4 files changed

+174
-1
lines changed

4 files changed

+174
-1
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,8 +945,22 @@ private Object parseDocumentResult(redis.clients.jedis.search.Document doc) {
945945

946946
jsonBuilder.append("\"").append(fieldName).append("\":");
947947

948+
// Check if this field is a Point type in the domain class
949+
boolean isPointField = false;
950+
try {
951+
Field domainField = ReflectionUtils.findField(domainType, fieldName);
952+
if (domainField != null && domainField.getType() == Point.class) {
953+
isPointField = true;
954+
}
955+
} catch (Exception e) {
956+
// Ignore - field might not exist in projection
957+
}
958+
948959
// Handle different types based on the raw value from Redis
949-
if (fieldName.equals("name") || (valueStr.startsWith("\"") && valueStr.endsWith("\""))) {
960+
if (isPointField && valueStr.contains(",") && !valueStr.startsWith("\"")) {
961+
// Point field - stored as "lon,lat" in Redis, needs to be quoted for PointTypeAdapter
962+
jsonBuilder.append("\"").append(valueStr).append("\"");
963+
} else if (fieldName.equals("name") || (valueStr.startsWith("\"") && valueStr.endsWith("\""))) {
950964
// String field - quote if not already quoted
951965
if (valueStr.startsWith("\"") && valueStr.endsWith("\"")) {
952966
jsonBuilder.append(valueStr);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.redis.om.spring.annotations.document;
2+
3+
import com.redis.om.spring.AbstractBaseDocumentTest;
4+
import com.redis.om.spring.fixtures.document.model.FlightWithLocation;
5+
import com.redis.om.spring.fixtures.document.repository.FlightWithLocationRepository;
6+
import com.redis.om.spring.fixtures.document.repository.FlightWithLocationRepository.FlightProjection;
7+
import com.redis.om.spring.fixtures.document.repository.FlightWithLocationRepository.FlightProjectionWithoutPoint;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.data.geo.Point;
12+
13+
import java.util.NoSuchElementException;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
17+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
18+
19+
/**
20+
* Test for issue #661 - NoSuchElementException when using Point in projection interface
21+
* https://github.com/redis/redis-om-spring/issues/661
22+
*/
23+
class PointProjectionTest extends AbstractBaseDocumentTest {
24+
25+
@Autowired
26+
private FlightWithLocationRepository repository;
27+
28+
@BeforeEach
29+
void setup() {
30+
repository.deleteAll();
31+
32+
// Create test data
33+
FlightWithLocation flight1 = new FlightWithLocation("AA123", "Flight to Paris", new Point(2.3522, 48.8566));
34+
FlightWithLocation flight2 = new FlightWithLocation("BA456", "Flight to London", new Point(-0.1276, 51.5074));
35+
FlightWithLocation flight3 = new FlightWithLocation("LH789", "Flight to Berlin", new Point(13.4050, 52.5200));
36+
37+
repository.save(flight1);
38+
repository.save(flight2);
39+
repository.save(flight3);
40+
}
41+
42+
@Test
43+
void testProjectionWithoutPointWorks() {
44+
// This should work fine - projection without Point field
45+
FlightProjectionWithoutPoint projection = repository.findByName("Flight to Paris");
46+
47+
assertThat(projection).isNotNull();
48+
assertThat(projection.getNumber()).isEqualTo("AA123");
49+
assertThat(projection.getName()).isEqualTo("Flight to Paris");
50+
}
51+
52+
@Test
53+
void testProjectionWithPointNowWorks() {
54+
// This test verifies that issue #661 has been fixed
55+
// Previously would throw NoSuchElementException, now it works
56+
FlightProjection projection = repository.findByNumber("AA123");
57+
58+
assertThat(projection).isNotNull();
59+
assertThat(projection.getNumber()).isEqualTo("AA123");
60+
assertThat(projection.getName()).isEqualTo("Flight to Paris");
61+
62+
// After fix, accessing Point field in projection should work
63+
Point location = projection.getLocation();
64+
assertThat(location).isNotNull();
65+
assertThat(location.getX()).isEqualTo(2.3522);
66+
assertThat(location.getY()).isEqualTo(48.8566);
67+
}
68+
69+
@Test
70+
void testDirectRepositoryAccessWithPointWorks() {
71+
// Direct repository access should work fine
72+
FlightWithLocation flight = repository.findById(
73+
repository.findAll().iterator().next().getId()
74+
).orElseThrow();
75+
76+
assertThat(flight.getLocation()).isNotNull();
77+
assertThat(flight.getLocation().getX()).isEqualTo(2.3522);
78+
assertThat(flight.getLocation().getY()).isEqualTo(48.8566);
79+
}
80+
81+
@Test
82+
void testProjectionWithPointShouldWork() {
83+
// After fix, this should work without throwing exception
84+
FlightProjection projection = repository.findByNumber("BA456");
85+
86+
assertThat(projection).isNotNull();
87+
assertThat(projection.getNumber()).isEqualTo("BA456");
88+
assertThat(projection.getName()).isEqualTo("Flight to London");
89+
90+
// After fix, this should NOT throw exception
91+
assertDoesNotThrow(() -> {
92+
Point location = projection.getLocation();
93+
assertThat(location).isNotNull();
94+
assertThat(location.getX()).isEqualTo(-0.1276);
95+
assertThat(location.getY()).isEqualTo(51.5074);
96+
});
97+
}
98+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.redis.om.spring.fixtures.document.model;
2+
3+
import com.redis.om.spring.annotations.Document;
4+
import com.redis.om.spring.annotations.Indexed;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
import org.springframework.data.annotation.Id;
9+
import org.springframework.data.geo.Point;
10+
11+
/**
12+
* Test model for issue #661 - NoSuchElementException when using Point in projection interface
13+
*/
14+
@Data
15+
@NoArgsConstructor
16+
@AllArgsConstructor
17+
@Document
18+
public class FlightWithLocation {
19+
@Id
20+
private String id;
21+
22+
@Indexed
23+
private String number;
24+
25+
private String name;
26+
27+
@Indexed
28+
private Point location;
29+
30+
public FlightWithLocation(String number, String name, Point location) {
31+
this.number = number;
32+
this.name = name;
33+
this.location = location;
34+
}
35+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.redis.om.spring.fixtures.document.repository;
2+
3+
import com.redis.om.spring.fixtures.document.model.FlightWithLocation;
4+
import com.redis.om.spring.repository.RedisDocumentRepository;
5+
import org.springframework.data.geo.Point;
6+
7+
public interface FlightWithLocationRepository extends RedisDocumentRepository<FlightWithLocation, String> {
8+
9+
// Projection interface for testing issue #661
10+
interface FlightProjection {
11+
String getNumber();
12+
String getName();
13+
Point getLocation(); // This causes NoSuchElementException
14+
}
15+
16+
// Projection without Point for comparison
17+
interface FlightProjectionWithoutPoint {
18+
String getNumber();
19+
String getName();
20+
}
21+
22+
// Find methods using projections
23+
FlightProjection findByNumber(String number);
24+
25+
FlightProjectionWithoutPoint findByName(String name);
26+
}

0 commit comments

Comments
 (0)